diff --git a/VIPSWeb/static/css/3rdparty/ol.css b/VIPSWeb/static/css/3rdparty/ol.css index cce81215fffecf6a8427494db1c742aed22a4f11..1f80aeb96ae40e4e3547d096756d3e5de817ae98 100644 --- a/VIPSWeb/static/css/3rdparty/ol.css +++ b/VIPSWeb/static/css/3rdparty/ol.css @@ -1 +1 @@ -.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:#95b9e6;background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;padding:0 2px}.ol-unsupported{display:none}.ol-viewport .ol-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-control{position:absolute;background-color:#eee;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}@media print{.ol-control{display:none}}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:#7b98bc;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-family:Arial;font-weight:400;font-size:1.2em}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:#4c6079;background-color:rgba(0,60,136,.7)}.ol-zoom-extent button:after{content:"E"}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}button.ol-full-screen-false:after{content:"\2194"}button.ol-full-screen-true:after{content:"\00d7"}.ol-has-tooltip [role=tooltip]{position:absolute;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);padding:0;border:0;height:1px;width:1px;overflow:hidden;font-weight:400;font-size:14px;text-shadow:0 0 2px #fff}.ol-has-tooltip:focus [role=tooltip],.ol-has-tooltip:hover [role=tooltip]{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;clip:auto;padding:0 .4em;font-size:.8em;height:1.2em;width:auto;line-height:1.2em;z-index:1100;max-height:100px;white-space:nowrap;display:inline-block;background:#FFF;background:rgba(255,255,255,.6);color:#000;border:3px solid rgba(255,255,255,0);border-left-width:0;border-radius:0 4px 4px 0;bottom:.3em;left:2.2em}.ol-touch .ol-has-tooltip:focus [role=tooltip],.ol-touch .ol-has-tooltip:hover [role=tooltip]{display:none}.ol-zoom .ol-has-tooltip:focus [role=tooltip],.ol-zoom .ol-has-tooltip:hover [role=tooltip]{top:1.1em}.ol-attribution .ol-has-tooltip:focus [role=tooltip],.ol-attribution .ol-has-tooltip:hover [role=tooltip],.ol-full-screen .ol-has-tooltip:focus [role=tooltip],.ol-full-screen .ol-has-tooltip:hover [role=tooltip],.ol-rotate .ol-has-tooltip:focus [role=tooltip],.ol-rotate .ol-has-tooltip:hover [role=tooltip]{right:2.2em;left:auto;border-radius:4px 0 0 4px;border-left-width:3px;border-right-width:0}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;font-size:.7rem;line-height:1.375em;color:#000;text-shadow:0 0 2px #fff;max-width:calc(100% - 3.6em)}.ol-attribution li{display:inline;list-style:none;line-height:inherit}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul,.ol-attribution:not(.ol-collapsed) button:hover [role=tooltip]{display:none}.ol-attribution.ol-logo-only ul{display:block}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0;height:1.1em;line-height:1em}.ol-attribution.ol-logo-only{background:0 0;bottom:.4em;height:1.1em;line-height:1em}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-logo-only button,.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{position:absolute;top:4.5em;left:.5em;background:#eee;background:rgba(255,255,255,.4);border-radius:4px;outline:0;overflow:hidden;width:1.5675em;height:200px;padding:3px;margin:0}.ol-zoomslider-thumb{position:absolute;display:block;background:#7b98bc;background:rgba(0,60,136,.5);border-radius:2px;outline:0;overflow:hidden;cursor:pointer;font-size:1.14em;height:1em;width:1.375em;margin:3px;padding:0}.ol-touch .ol-zoomslider{top:5.5em;width:2.052em}.ol-touch .ol-zoomslider-thumb{width:1.8em}.ol-attribution,.ol-control button,.ol-has-tooltip [role=tooltip],.ol-scale-line-inner{font-family:'Lucida Grande',Verdana,Geneva,Lucida,Arial,Helvetica,sans-serif} \ No newline at end of file +.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:#95b9e6;background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width}.ol-overlay-container{will-change:left,right,top,bottom}.ol-unsupported{display:none}.ol-viewport .ol-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-control{position:absolute;background-color:#eee;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}@media print{.ol-control{display:none}}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:#7b98bc;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;font-size:1.2em;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:#4c6079;background-color:rgba(0,60,136,.7)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;font-size:.7rem;line-height:1.375em;color:#000;text-shadow:0 0 2px #fff}.ol-attribution li{display:inline;list-style:none;line-height:inherit}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution.ol-logo-only ul{display:block}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0;height:1.1em;line-height:1em}.ol-attribution.ol-logo-only{background:0 0;bottom:.4em;height:1.1em;line-height:1em}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-logo-only button,.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;width:24px;height:200px}.ol-zoomslider-thumb{position:absolute;background:#7b98bc;background:rgba(0,60,136,.5);border-radius:2px;cursor:pointer;height:10px;width:22px;margin:3px}.ol-touch .ol-zoomslider{top:5.5em;width:2.052em}.ol-touch .ol-zoomslider-thumb{width:1.8em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:inline-block}.ol-overviewmap .ol-overviewmap-map{border:1px solid #7b98bc;height:150px;margin:2px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:1px;left:2px;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-overviewmap-box{border:2px dotted rgba(0,60,136,.7)} \ No newline at end of file diff --git a/VIPSWeb/static/js/3rdparty/ol-debug.js b/VIPSWeb/static/js/3rdparty/ol-debug.js index dbcf8926f26606047fa4160969012d1b97b3fb30..ab717966ddb7c5fe9b74c51b5e237d869ebbb2f3 100644 --- a/VIPSWeb/static/js/3rdparty/ol-debug.js +++ b/VIPSWeb/static/js/3rdparty/ol-debug.js @@ -1,12 +1,12 @@ // OpenLayers 3. See http://openlayers.org/ // License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md -// Version: v3.1.1 +// Version: v3.8.2 (function (root, factory) { - if (typeof define === "function" && define.amd) { - define([], factory); - } else if (typeof exports === "object") { + if (typeof exports === "object") { module.exports = factory(); + } else if (typeof define === "function" && define.amd) { + define([], factory); } else { root.ol = factory(); } @@ -4330,7 +4330,7 @@ goog.asserts.doAssertFailure_ = * Sets a custom error handler that can be used to customize the behavior of * assertion failures, for example by turning all assertion failures into log * messages. - * @param {function(goog.asserts.AssertionError)} errorHandler + * @param {function(!goog.asserts.AssertionError)} errorHandler */ goog.asserts.setErrorHandler = function(errorHandler) { if (goog.asserts.ENABLE_ASSERTS) { @@ -4346,7 +4346,7 @@ goog.asserts.setErrorHandler = function(errorHandler) { * @param {T} condition The condition to check. * @param {string=} opt_message Error message in case of failure. * @param {...*} var_args The items to substitute into the failure message. - * @return {!T} The value of the condition. + * @return {T} The value of the condition. * @throws {goog.asserts.AssertionError} When the condition evaluates to false. */ goog.asserts.assert = function(condition, opt_message, var_args) { @@ -4527,7 +4527,7 @@ goog.asserts.assertElement = function(value, opt_message, var_args) { * @param {...*} var_args The items to substitute into the failure message. * @throws {goog.asserts.AssertionError} When the value is not an instance of * type. - * @return {!T} + * @return {T} * @template T */ goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) { @@ -5084,7 +5084,7 @@ goog.array.count = function(arr, f, opt_obj) { * for every element. This function takes 3 arguments (the element, the * index and the array) and should return a boolean. * @param {S=} opt_obj An optional "this" context for the function. - * @return {?T} The first array element that passes the test, or null if no + * @return {T|null} The first array element that passes the test, or null if no * element is found. * @template T,S */ @@ -5130,7 +5130,7 @@ goog.array.findIndex = function(arr, f, opt_obj) { * takes 3 arguments (the element, the index and the array) and should * return a boolean. * @param {S=} opt_obj An optional "this" context for the function. - * @return {?T} The last array element that passes the test, or null if no + * @return {T|null} The last array element that passes the test, or null if no * element is found. * @template T,S */ @@ -5869,7 +5869,8 @@ goog.array.compare3 = function(arr1, arr2, opt_compareFn) { * @param {VALUE} a The first object to be compared. * @param {VALUE} b The second object to be compared. * @return {number} A negative number, zero, or a positive number as the first - * argument is less than, equal to, or greater than the second. + * argument is less than, equal to, or greater than the second, + * respectively. * @template VALUE */ goog.array.defaultCompare = function(a, b) { @@ -5877,6 +5878,21 @@ goog.array.defaultCompare = function(a, b) { }; +/** + * Compares its two arguments for inverse order, using the built in < and > + * operators. + * @param {VALUE} a The first object to be compared. + * @param {VALUE} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is greater than, equal to, or less than the second, + * respectively. + * @template VALUE + */ +goog.array.inverseDefaultCompare = function(a, b) { + return -goog.array.defaultCompare(a, b); +}; + + /** * Compares its two arguments for equality, using the built in === operator. * @param {*} a The first object to compare. @@ -6190,6 +6206,25 @@ goog.array.shuffle = function(arr, opt_randFn) { } }; + +/** + * Returns a new array of elements from arr, based on the indexes of elements + * provided by index_arr. For example, the result of index copying + * ['a', 'b', 'c'] with index_arr [1,0,0,2] is ['b', 'a', 'a', 'c']. + * + * @param {!Array<T>} arr The array to get a indexed copy from. + * @param {!Array<number>} index_arr An array of indexes to get from arr. + * @return {!Array<T>} A new array of elements from arr in index_arr order. + * @template T + */ +goog.array.copyByIndex = function(arr, index_arr) { + var result = []; + goog.array.forEach(index_arr, function(index) { + result.push(arr[index]); + }); + return result; +}; + // Copyright 2013 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -8167,12 +8202,6 @@ goog.provide('ol'); ol.ASSUME_TOUCH = false; -/** - * @define {boolean} Replace unused entries with NaNs. - */ -ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS = goog.DEBUG; - - /** * TODO: rename this to something having to do with tile grids * see https://github.com/openlayers/ol3/issues/2076 @@ -8205,24 +8234,6 @@ ol.DEFAULT_TILE_SIZE = 256; ol.DEFAULT_WMS_VERSION = '1.3.0'; -/** - * @define {number} Drag-rotate-zoom animation duration. - */ -ol.DRAGROTATEANDZOOM_ANIMATION_DURATION = 400; - - -/** - * @define {number} Drag-rotate animation duration. - */ -ol.DRAGROTATE_ANIMATION_DURATION = 250; - - -/** - * @define {number} Drag-zoom animation duration. - */ -ol.DRAGZOOM_ANIMATION_DURATION = 200; - - /** * @define {number} Hysteresis pixels. */ @@ -8310,14 +8321,6 @@ ol.LEGACY_IE_SUPPORT = false; ol.INITIAL_ATLAS_SIZE = 256; -/** - * The page is loaded using HTTPS. - * @const - * @type {boolean} - */ -ol.IS_HTTPS = goog.global.location.protocol === 'https:'; - - /** * Whether the current browser is legacy IE * @const @@ -8327,15 +8330,9 @@ ol.IS_LEGACY_IE = goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9.0') && goog.userAgent.VERSION !== ''; -/** - * @define {number} Keyboard pan duration. - */ -ol.KEYBOARD_PAN_DURATION = 100; - - /** * @define {number} The maximum size in pixels of atlas images. Default is - * `-1`, meaning it is not used (and `ol.ol.WEBGL_MAX_TEXTURE_SIZE` is + * `-1`, meaning it is not used (and `ol.WEBGL_MAX_TEXTURE_SIZE` is * used instead). */ ol.MAX_ATLAS_SIZE = -1; @@ -8367,12 +8364,6 @@ ol.OVERVIEWMAP_MAX_RATIO = 0.75; ol.OVERVIEWMAP_MIN_RATIO = 0.1; -/** - * @define {number} Rotate animation duration. - */ -ol.ROTATE_ANIMATION_DURATION = 250; - - /** * @define {number} Tolerance for geometry simplification in device pixels. */ @@ -8390,7 +8381,6 @@ ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024; * supported, the value is set to `undefined`. * @const * @type {number|undefined} - * @api */ ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has` @@ -8403,12 +8393,6 @@ ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has` ol.WEBGL_EXTENSIONS; // value is set in `ol.has` -/** - * @define {number} Zoom slider animation duration. - */ -ol.ZOOMSLIDER_ANIMATION_DURATION = 200; - - /** * Inherit the prototype methods from one constructor into another. * @@ -9016,8 +9000,10 @@ ol.array.linearFindNearest = function(arr, target, direction) { * @param {number} end End index. */ ol.array.reverseSubArray = function(arr, begin, end) { - goog.asserts.assert(begin >= 0); - goog.asserts.assert(end < arr.length); + goog.asserts.assert(begin >= 0, + 'Array begin index should be equal to or greater than 0'); + goog.asserts.assert(end < arr.length, + 'Array end index should be less than the array length'); while (begin < end) { var tmp = arr[begin]; arr[begin] = arr[end]; @@ -9727,12 +9713,17 @@ goog.Disposable.prototype.registerDisposable = function(disposable) { /** * Invokes a callback function when this object is disposed. Callbacks are - * invoked in the order in which they were added. + * invoked in the order in which they were added. If a callback is added to + * an already disposed Disposable, it will be called immediately. * @param {function(this:T):?} callback The callback function. * @param {T=} opt_scope An optional scope to call the callback in. * @template T */ goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) { + if (this.disposed_) { + callback.call(opt_scope); + return; + } if (!this.onDisposeCallbacks_) { this.onDisposeCallbacks_ = []; } @@ -9976,24 +9967,6 @@ goog.events.Event = function(type, opt_target) { }; -/** - * For backwards compatibility (goog.events.Event used to inherit - * goog.Disposable). - * @deprecated Events don't need to be disposed. - */ -goog.events.Event.prototype.disposeInternal = function() { -}; - - -/** - * For backwards compatibility (goog.events.Event used to inherit - * goog.Disposable). - * @deprecated Events don't need to be disposed. - */ -goog.events.Event.prototype.dispose = function() { -}; - - /** * Stops event propagation. */ @@ -10748,11 +10721,6 @@ goog.events.BrowserEvent.prototype.getBrowserEvent = function() { }; -/** @override */ -goog.events.BrowserEvent.prototype.disposeInternal = function() { -}; - - /** @private @return {number} */ goog.events.BrowserEvent.prototype.getOffsetX_ = function() { // Webkit emits a lame warning whenever layerX/layerY is accessed. @@ -12557,339 +12525,6 @@ goog.debug.entryPointRegistry.register( goog.events.handleBrowserEvent_); }); -// Copyright 2008 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Utilities for creating functions. Loosely inspired by the - * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8. - * - * @author nicksantos@google.com (Nick Santos) - */ - - -goog.provide('goog.functions'); - - -/** - * Creates a function that always returns the same value. - * @param {T} retValue The value to return. - * @return {function():T} The new function. - * @template T - */ -goog.functions.constant = function(retValue) { - return function() { - return retValue; - }; -}; - - -/** - * Always returns false. - * @type {function(...): boolean} - */ -goog.functions.FALSE = goog.functions.constant(false); - - -/** - * Always returns true. - * @type {function(...): boolean} - */ -goog.functions.TRUE = goog.functions.constant(true); - - -/** - * Always returns NULL. - * @type {function(...): null} - */ -goog.functions.NULL = goog.functions.constant(null); - - -/** - * A simple function that returns the first argument of whatever is passed - * into it. - * @param {T=} opt_returnValue The single value that will be returned. - * @param {...*} var_args Optional trailing arguments. These are ignored. - * @return {T} The first argument passed in, or undefined if nothing was passed. - * @template T - */ -goog.functions.identity = function(opt_returnValue, var_args) { - return opt_returnValue; -}; - - -/** - * Creates a function that always throws an error with the given message. - * @param {string} message The error message. - * @return {!Function} The error-throwing function. - */ -goog.functions.error = function(message) { - return function() { - throw Error(message); - }; -}; - - -/** - * Creates a function that throws the given object. - * @param {*} err An object to be thrown. - * @return {!Function} The error-throwing function. - */ -goog.functions.fail = function(err) { - return function() { - throw err; - } -}; - - -/** - * Given a function, create a function that keeps opt_numArgs arguments and - * silently discards all additional arguments. - * @param {Function} f The original function. - * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0. - * @return {!Function} A version of f that only keeps the first opt_numArgs - * arguments. - */ -goog.functions.lock = function(f, opt_numArgs) { - opt_numArgs = opt_numArgs || 0; - return function() { - return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs)); - }; -}; - - -/** - * Creates a function that returns its nth argument. - * @param {number} n The position of the return argument. - * @return {!Function} A new function. - */ -goog.functions.nth = function(n) { - return function() { - return arguments[n]; - }; -}; - - -/** - * Given a function, create a new function that swallows its return value - * and replaces it with a new one. - * @param {Function} f A function. - * @param {T} retValue A new return value. - * @return {function(...[?]):T} A new function. - * @template T - */ -goog.functions.withReturnValue = function(f, retValue) { - return goog.functions.sequence(f, goog.functions.constant(retValue)); -}; - - -/** - * Creates a function that returns whether its arguement equals the given value. - * - * Example: - * var key = goog.object.findKey(obj, goog.functions.equalTo('needle')); - * - * @param {*} value The value to compare to. - * @param {boolean=} opt_useLooseComparison Whether to use a loose (==) - * comparison rather than a strict (===) one. Defaults to false. - * @return {function(*):boolean} The new function. - */ -goog.functions.equalTo = function(value, opt_useLooseComparison) { - return function(other) { - return opt_useLooseComparison ? (value == other) : (value === other); - }; -}; - - -/** - * Creates the composition of the functions passed in. - * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)). - * @param {function(...[?]):T} fn The final function. - * @param {...Function} var_args A list of functions. - * @return {function(...[?]):T} The composition of all inputs. - * @template T - */ -goog.functions.compose = function(fn, var_args) { - var functions = arguments; - var length = functions.length; - return function() { - var result; - if (length) { - result = functions[length - 1].apply(this, arguments); - } - - for (var i = length - 2; i >= 0; i--) { - result = functions[i].call(this, result); - } - return result; - }; -}; - - -/** - * Creates a function that calls the functions passed in in sequence, and - * returns the value of the last function. For example, - * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x). - * @param {...Function} var_args A list of functions. - * @return {!Function} A function that calls all inputs in sequence. - */ -goog.functions.sequence = function(var_args) { - var functions = arguments; - var length = functions.length; - return function() { - var result; - for (var i = 0; i < length; i++) { - result = functions[i].apply(this, arguments); - } - return result; - }; -}; - - -/** - * Creates a function that returns true if each of its components evaluates - * to true. The components are evaluated in order, and the evaluation will be - * short-circuited as soon as a function returns false. - * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x). - * @param {...Function} var_args A list of functions. - * @return {function(...[?]):boolean} A function that ANDs its component - * functions. - */ -goog.functions.and = function(var_args) { - var functions = arguments; - var length = functions.length; - return function() { - for (var i = 0; i < length; i++) { - if (!functions[i].apply(this, arguments)) { - return false; - } - } - return true; - }; -}; - - -/** - * Creates a function that returns true if any of its components evaluates - * to true. The components are evaluated in order, and the evaluation will be - * short-circuited as soon as a function returns true. - * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x). - * @param {...Function} var_args A list of functions. - * @return {function(...[?]):boolean} A function that ORs its component - * functions. - */ -goog.functions.or = function(var_args) { - var functions = arguments; - var length = functions.length; - return function() { - for (var i = 0; i < length; i++) { - if (functions[i].apply(this, arguments)) { - return true; - } - } - return false; - }; -}; - - -/** - * Creates a function that returns the Boolean opposite of a provided function. - * For example, (goog.functions.not(f))(x) is equivalent to !f(x). - * @param {!Function} f The original function. - * @return {function(...[?]):boolean} A function that delegates to f and returns - * opposite. - */ -goog.functions.not = function(f) { - return function() { - return !f.apply(this, arguments); - }; -}; - - -/** - * Generic factory function to construct an object given the constructor - * and the arguments. Intended to be bound to create object factories. - * - * Example: - * - * var factory = goog.partial(goog.functions.create, Class); - * - * @param {function(new:T, ...)} constructor The constructor for the Object. - * @param {...*} var_args The arguments to be passed to the constructor. - * @return {!T} A new instance of the class given in {@code constructor}. - * @template T - */ -goog.functions.create = function(constructor, var_args) { - /** - * @constructor - * @final - */ - var temp = function() {}; - temp.prototype = constructor.prototype; - - // obj will have constructor's prototype in its chain and - // 'obj instanceof constructor' will be true. - var obj = new temp(); - - // obj is initialized by constructor. - // arguments is only array-like so lacks shift(), but can be used with - // the Array prototype function. - constructor.apply(obj, Array.prototype.slice.call(arguments, 1)); - return obj; -}; - - -/** - * @define {boolean} Whether the return value cache should be used. - * This should only be used to disable caches when testing. - */ -goog.define('goog.functions.CACHE_RETURN_VALUE', true); - - -/** - * Gives a wrapper function that caches the return value of a parameterless - * function when first called. - * - * When called for the first time, the given function is called and its - * return value is cached (thus this is only appropriate for idempotent - * functions). Subsequent calls will return the cached return value. This - * allows the evaluation of expensive functions to be delayed until first used. - * - * To cache the return values of functions with parameters, see goog.memoize. - * - * @param {!function():T} fn A function to lazily evaluate. - * @return {!function():T} A wrapped version the function. - * @template T - */ -goog.functions.cacheReturnValue = function(fn) { - var called = false; - var value; - - return function() { - if (!goog.functions.CACHE_RETURN_VALUE) { - return fn(); - } - - if (!called) { - value = fn(); - called = true; - } - - return value; - } -}; - // Copyright 2005 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13303,6 +12938,7 @@ goog.require('goog.events.EventType'); * * @constructor * @extends {goog.events.EventTarget} + * @fires change * @suppress {checkStructDictInheritance} * @struct * @api stable @@ -13332,8 +12968,7 @@ ol.Observable.unByKey = function(key) { /** - * Increases the revision counter and disptches a 'change' event. - * @fires change + * Increases the revision counter and dispatches a 'change' event. * @api */ ol.Observable.prototype.changed = function() { @@ -13342,6 +12977,13 @@ ol.Observable.prototype.changed = function() { }; +/** + * Triggered when the revision counter is increased. + * @event change + * @api + */ + + /** * @return {number} Revision. * @api @@ -13400,21 +13042,13 @@ ol.Observable.prototype.un = function(type, listener, opt_this) { */ ol.Observable.prototype.unByKey = ol.Observable.unByKey; -/** - * An implementation of Google Maps' MVCObject. - * @see https://developers.google.com/maps/articles/mvcfun - * @see https://developers.google.com/maps/documentation/javascript/reference - */ - goog.provide('ol.Object'); goog.provide('ol.ObjectEvent'); goog.provide('ol.ObjectEventType'); goog.require('goog.events'); goog.require('goog.events.Event'); -goog.require('goog.functions'); goog.require('goog.object'); -goog.require('goog.string'); goog.require('ol.Observable'); @@ -13425,7 +13059,7 @@ ol.ObjectEventType = { /** * Triggered when a property is changed. * @event ol.ObjectEvent#propertychange - * @api + * @api stable */ PROPERTYCHANGE: 'propertychange' }; @@ -13449,7 +13083,7 @@ ol.ObjectEvent = function(type, key, oldValue) { /** * The name of the property whose value is changing. * @type {string} - * @api + * @api stable */ this.key = key; @@ -13457,7 +13091,7 @@ ol.ObjectEvent = function(type, key, oldValue) { * The old value. To get the new value use `e.target.get(e.key)` where * `e` is the event object. * @type {*} - * @api + * @api stable */ this.oldValue = oldValue; @@ -13466,63 +13100,6 @@ goog.inherits(ol.ObjectEvent, goog.events.Event); -/** - * @constructor - * @param {ol.Object} source Source object. - * @param {ol.Object} target Target object. - * @param {string} sourceKey Source key. - * @param {string} targetKey Target key. - */ -ol.ObjectAccessor = function(source, target, sourceKey, targetKey) { - - /** - * @type {ol.Object} - */ - this.source = source; - - /** - * @type {ol.Object} - */ - this.target = target; - - /** - * @type {string} - */ - this.sourceKey = sourceKey; - - /** - * @type {string} - */ - this.targetKey = targetKey; - - /** - * @type {function(?): ?} - */ - this.from = goog.functions.identity; - - /** - * @type {function(?): ?} - */ - this.to = goog.functions.identity; -}; - - -/** - * @param {function(?): ?} from A function that transforms the source value - * before it is set to the target. - * @param {function(?): ?} to A function that transforms the target value - * before it is set to the source. - * @api - */ -ol.ObjectAccessor.prototype.transform = function(from, to) { - var oldValue = ol.Object.getKeyValue_(this.source, this.sourceKey); - this.from = from; - this.to = to; - this.source.notify(this.sourceKey, oldValue); -}; - - - /** * @classdesc * Abstract base class; normally only used for creating subclasses and not @@ -13533,7 +13110,7 @@ ol.ObjectAccessor.prototype.transform = function(from, to) { * property is observable as well as the object as a whole. * * Classes that inherit from this have pre-defined properties, to which you can - * add your own. The pre-defined properties are listed in this documentation as + * add your owns. The pre-defined properties are listed in this documentation as * 'Observable Properties', and have their own accessors; for example, * {@link ol.Map} has a `target` property, accessed with `getTarget()` and * changed with `setTarget()`. Not all properties are however settable. There @@ -13559,11 +13136,8 @@ ol.ObjectAccessor.prototype.transform = function(from, to) { * first will be a `hasOwnProperty`; the second will appear in * `getProperties()`. Only the second is observable. * - * The observable properties also implement a form of Key Value Observing. - * Two objects can be bound together such that a change in one will - * automatically be reflected in the other. See `bindTo` method for more - * details, and see {@link ol.dom.Input} for the specific case of binding an - * object with an HTML element. + * Properties can be deleted by using the unset method. E.g. + * object.unset('foo'). * * @constructor * @extends {ol.Observable} @@ -13586,18 +13160,6 @@ ol.Object = function(opt_values) { */ this.values_ = {}; - /** - * @private - * @type {Object.<string, ol.ObjectAccessor>} - */ - this.accessors_ = {}; - - /** - * @private - * @type {Object.<string, goog.events.Key>} - */ - this.listeners_ = {}; - if (goog.isDef(opt_values)) { this.setProperties(opt_values); } @@ -13612,20 +13174,6 @@ goog.inherits(ol.Object, ol.Observable); ol.Object.changeEventTypeCache_ = {}; -/** - * @private - * @type {Object.<string, string>} - */ -ol.Object.getterNameCache_ = {}; - - -/** - * @private - * @type {Object.<string, string>} - */ -ol.Object.setterNameCache_ = {}; - - /** * @param {string} key Key name. * @return {string} Change name. @@ -13637,130 +13185,15 @@ ol.Object.getChangeEventType = function(key) { }; -/** - * @param {string} key String. - * @return {string} Getter name. - */ -ol.Object.getGetterName = function(key) { - return ol.Object.getterNameCache_.hasOwnProperty(key) ? - ol.Object.getterNameCache_[key] : - (ol.Object.getterNameCache_[key] = 'get' + goog.string.capitalize(key)); -}; - - -/** - * @param {string} key String. - * @return {string} Setter name. - */ -ol.Object.getSetterName = function(key) { - return ol.Object.setterNameCache_.hasOwnProperty(key) ? - ol.Object.setterNameCache_[key] : - (ol.Object.setterNameCache_[key] = 'set' + goog.string.capitalize(key)); -}; - - -/** - * Get the value for an object and a key. Use the getter (`getX`) if it exists, - * otherwise use the generic `get` function. - * @param {ol.Object} obj Object. - * @param {string} key Key; - * @return {*} Value; - * @private - */ -ol.Object.getKeyValue_ = function(obj, key) { - var getterName = ol.Object.getGetterName(key); - var getter = /** @type {function(): *|undefined} */ - (goog.object.get(obj, getterName)); - return goog.isDef(getter) ? getter.call(obj) : obj.get(key); -}; - - -/** - * Set the value for an object and a key. Use the setter (`setX`) if it exists, - * otherwise use the generic `set` function. - * @param {ol.Object} obj Object. - * @param {string} key Key. - * @param {*} value Value. - * @private - */ -ol.Object.setKeyValue_ = function(obj, key, value) { - var setterName = ol.Object.getSetterName(key); - var setter = /** @type {function(*)|undefined} */ - (goog.object.get(obj, setterName)); - if (goog.isDef(setter)) { - setter.call(obj, value); - } else { - obj.set(key, value); - } -}; - - -/** - * The bindTo method allows you to set up a two-way binding between a - * `source` and `target` object. The method returns an object with a - * `transform` method that you can use to provide `from` and `to` - * functions to transform values on the way from the source to the - * target and on the way back. -* - * For example, if you had two map views (sourceView and targetView) - * and you wanted the target view to have double the resolution of the - * source view, you could transform the resolution on the way to and - * from the target with the following: - * - * sourceView.bindTo('resolution', targetView) - * .transform( - * function(sourceResolution) { - * // from sourceView.resolution to targetView.resolution - * return 2 * sourceResolution; - * }, - * function(targetResolution) { - * // from targetView.resolution to sourceView.resolution - * return targetResolution / 2; - * } - * ); - * - * @param {string} key Key name. - * @param {ol.Object} target Target. - * @param {string=} opt_targetKey Target key. - * @return {ol.ObjectAccessor} - * @api - */ -ol.Object.prototype.bindTo = function(key, target, opt_targetKey) { - var targetKey = opt_targetKey || key; - this.unbind(key); - - // listen for change:targetkey events - var eventType = ol.Object.getChangeEventType(targetKey); - this.listeners_[key] = goog.events.listen(target, eventType, - /** - * @param {ol.ObjectEvent} e Event. - * @this {ol.Object} - */ - function(e) { - this.notify(key, e.oldValue); - }, undefined, this); - - var accessor = new ol.ObjectAccessor(this, target, key, targetKey); - this.accessors_[key] = accessor; - this.notify(key, this.values_[key]); - return accessor; -}; - - /** * Gets a value. * @param {string} key Key name. * @return {*} Value. - * @api + * @api stable */ ol.Object.prototype.get = function(key) { var value; - var accessors = this.accessors_; - if (accessors.hasOwnProperty(key)) { - var accessor = accessors[key]; - value = ol.Object.getKeyValue_(accessor.target, accessor.targetKey); - value = accessor.to(value); - } else if (this.values_.hasOwnProperty(key)) { + if (this.values_.hasOwnProperty(key)) { value = this.values_[key]; } return value; @@ -13770,39 +13203,17 @@ ol.Object.prototype.get = function(key) { /** * Get a list of object property names. * @return {Array.<string>} List of property names. - * @api + * @api stable */ ol.Object.prototype.getKeys = function() { - var accessors = this.accessors_; - var keysObject; - if (goog.object.isEmpty(this.values_)) { - if (goog.object.isEmpty(accessors)) { - return []; - } else { - keysObject = accessors; - } - } else { - if (goog.object.isEmpty(accessors)) { - keysObject = this.values_; - } else { - keysObject = {}; - var key; - for (key in this.values_) { - keysObject[key] = true; - } - for (key in accessors) { - keysObject[key] = true; - } - } - } - return goog.object.getKeys(keysObject); + return goog.object.getKeys(this.values_); }; /** * Get an object of all property names and values. * @return {Object.<string, *>} Object. - * @api + * @api stable */ ol.Object.prototype.getProperties = function() { var properties = {}; @@ -13810,9 +13221,6 @@ ol.Object.prototype.getProperties = function() { for (key in this.values_) { properties[key] = this.values_[key]; } - for (key in this.accessors_) { - properties[key] = this.get(key); - } return properties; }; @@ -13834,26 +13242,20 @@ ol.Object.prototype.notify = function(key, oldValue) { * Sets a value. * @param {string} key Key name. * @param {*} value Value. - * @api + * @api stable */ ol.Object.prototype.set = function(key, value) { - var accessors = this.accessors_; - if (accessors.hasOwnProperty(key)) { - var accessor = accessors[key]; - value = accessor.from(value); - ol.Object.setKeyValue_(accessor.target, accessor.targetKey, value); - } else { - var oldValue = this.values_[key]; - this.values_[key] = value; - this.notify(key, oldValue); - } + var oldValue = this.values_[key]; + this.values_[key] = value; + this.notify(key, oldValue); }; /** - * Sets a collection of key-value pairs. + * Sets a collection of key-value pairs. Note that this changes any existing + * properties and adds new ones (it does not remove any existing properties). * @param {Object.<string, *>} values Values. - * @api + * @api stable */ ol.Object.prototype.setProperties = function(values) { var key; @@ -13864,31 +13266,15 @@ ol.Object.prototype.setProperties = function(values) { /** - * Removes a binding. Unbinding will set the unbound property to the current - * value. The object will not be notified, as the value has not changed. + * Unsets a property. * @param {string} key Key name. - * @api - */ -ol.Object.prototype.unbind = function(key) { - var listeners = this.listeners_; - var listener = listeners[key]; - if (listener) { - delete listeners[key]; - goog.events.unlistenByKey(listener); - var value = this.get(key); - delete this.accessors_[key]; - this.values_[key] = value; - } -}; - - -/** - * Removes all bindings. - * @api + * @api stable */ -ol.Object.prototype.unbindAll = function() { - for (var key in this.listeners_) { - this.unbind(key); +ol.Object.prototype.unset = function(key) { + if (key in this.values_) { + var oldValue = this.values_[key]; + delete this.values_[key]; + this.notify(key, oldValue); } }; @@ -13896,6 +13282,9 @@ goog.provide('ol.Size'); goog.provide('ol.size'); +goog.require('goog.asserts'); + + /** * An array of numbers representing a size: `[width, height]`. * @typedef {Array.<number>} @@ -13904,6 +13293,23 @@ goog.provide('ol.size'); ol.Size; +/** + * Returns a buffered size. + * @param {ol.Size} size Size. + * @param {number} buffer Buffer. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} + */ +ol.size.buffer = function(size, buffer, opt_size) { + if (!goog.isDef(opt_size)) { + opt_size = [0, 0]; + } + opt_size[0] = size[0] + 2 * buffer; + opt_size[1] = size[1] + 2 * buffer; + return opt_size; +}; + + /** * Compares sizes for equality. * @param {ol.Size} a Size. @@ -13914,11 +13320,64 @@ ol.size.equals = function(a, b) { return a[0] == b[0] && a[1] == b[1]; }; + +/** + * Determines if a size has a positive area. + * @param {ol.Size} size The size to test. + * @return {boolean} The size has a positive area. + */ +ol.size.hasArea = function(size) { + return size[0] > 0 && size[1] > 0; +}; + + +/** + * Returns a size scaled by a ratio. The result will be an array of integers. + * @param {ol.Size} size Size. + * @param {number} ratio Ratio. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} + */ +ol.size.scale = function(size, ratio, opt_size) { + if (!goog.isDef(opt_size)) { + opt_size = [0, 0]; + } + opt_size[0] = (size[0] * ratio + 0.5) | 0; + opt_size[1] = (size[1] * ratio + 0.5) | 0; + return opt_size; +}; + + +/** + * Returns an `ol.Size` array for the passed in number (meaning: square) or + * `ol.Size` array. + * (meaning: non-square), + * @param {number|ol.Size} size Width and height. + * @param {ol.Size=} opt_size Optional reusable size array. + * @return {ol.Size} Size. + * @api stable + */ +ol.size.toSize = function(size, opt_size) { + if (goog.isArray(size)) { + return size; + } else { + goog.asserts.assert(goog.isNumber(size)); + if (!goog.isDef(opt_size)) { + opt_size = [size, size]; + } else { + opt_size[0] = size; + opt_size[1] = size; + } + return opt_size; + } +}; + goog.provide('ol.Coordinate'); goog.provide('ol.CoordinateFormatType'); goog.provide('ol.coordinate'); goog.require('goog.math'); +goog.require('goog.string'); /** @@ -14045,8 +13504,8 @@ ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres) { var normalizedDegrees = goog.math.modulo(degrees + 180, 360) - 180; var x = Math.abs(Math.round(3600 * normalizedDegrees)); return Math.floor(x / 3600) + '\u00b0 ' + - Math.floor((x / 60) % 60) + '\u2032 ' + - Math.floor(x % 60) + '\u2033 ' + + goog.string.padNumber(Math.floor((x / 60) % 60), 2) + '\u2032 ' + + goog.string.padNumber(Math.floor(x % 60), 2) + '\u2033 ' + hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0); }; @@ -14075,7 +13534,7 @@ ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres) { * that will be replaced by first and second coordinate values. * @param {number=} opt_fractionDigits The number of digits to include * after the decimal point. Default is `0`. - * @return {string} Formated coordinate. + * @return {string} Formatted coordinate. * @api stable */ ol.coordinate.format = function(coordinate, template, opt_fractionDigits) { @@ -14196,6 +13655,9 @@ ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) { /** + * Format a geographic coordinate with the hemisphere, degrees, minutes, and + * seconds. + * * Example: * * var coord = [7.85, 47.983333]; @@ -14217,6 +13679,8 @@ ol.coordinate.toStringHDMS = function(coordinate) { /** + * Format a coordinate as a comma delimited string. + * * Example without specifying fractional digits: * * var coord = [7.85, 47.983333]; @@ -17476,7 +16940,7 @@ ol.extent.Relationship = { /** - * Builds an extent that includes all given coordinates. + * Build an extent that includes all given coordinates. * * @param {Array.<ol.Coordinate>} coordinates Coordinates. * @return {ol.Extent} Bounding extent. @@ -17499,8 +16963,8 @@ ol.extent.boundingExtent = function(coordinates) { * @return {ol.Extent} Extent. */ ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) { - goog.asserts.assert(xs.length > 0); - goog.asserts.assert(ys.length > 0); + goog.asserts.assert(xs.length > 0, 'xs length should be larger than 0'); + goog.asserts.assert(ys.length > 0, 'ys length should be larger than 0'); var minX = Math.min.apply(null, xs); var minY = Math.min.apply(null, ys); var maxX = Math.max.apply(null, xs); @@ -17582,11 +17046,11 @@ ol.extent.closestSquaredDistanceXY = function(extent, x, y) { /** - * Checks if the passed coordinate is contained or on the edge of the extent. + * Check if the passed coordinate is contained or on the edge of the extent. * * @param {ol.Extent} extent Extent. * @param {ol.Coordinate} coordinate Coordinate. - * @return {boolean} Contains. + * @return {boolean} The coordinate is contained in the extent. * @api stable */ ol.extent.containsCoordinate = function(extent, coordinate) { @@ -17595,11 +17059,15 @@ ol.extent.containsCoordinate = function(extent, coordinate) { /** - * Checks if `extent2` is contained by or on the edge of `extent1`. + * Check if one extent contains another. + * + * An extent is deemed contained if it lies completely within the other extent, + * including if they share one or more edges. * * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent 2. - * @return {boolean} Contains. + * @return {boolean} The second extent is contained by or on the edge of the + * first. * @api stable */ ol.extent.containsExtent = function(extent1, extent2) { @@ -17609,12 +17077,12 @@ ol.extent.containsExtent = function(extent1, extent2) { /** - * Checks if the passed coordinate is contained or on the edge of the extent. + * Check if the passed coordinate is contained or on the edge of the extent. * * @param {ol.Extent} extent Extent. * @param {number} x X coordinate. * @param {number} y Y coordinate. - * @return {boolean} Contains. + * @return {boolean} The x, y values are contained in the extent. * @api stable */ ol.extent.containsXY = function(extent, x, y) { @@ -17655,6 +17123,7 @@ ol.extent.coordinateRelationship = function(extent, coordinate) { /** + * Create an empty extent. * @return {ol.Extent} Empty extent. * @api stable */ @@ -17664,6 +17133,7 @@ ol.extent.createEmpty = function() { /** + * Create a new extent or update the provided extent. * @param {number} minX Minimum X. * @param {number} minY Minimum Y. * @param {number} maxX Maximum X. @@ -17685,6 +17155,7 @@ ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { /** + * Create a new empty extent or make the provided one empty. * @param {ol.Extent=} opt_extent Extent. * @return {ol.Extent} Extent. */ @@ -17745,7 +17216,7 @@ ol.extent.createOrUpdateFromRings = function(rings, opt_extent) { /** - * Empties extent in place. + * Empty an extent in place. * @param {ol.Extent} extent Extent. * @return {ol.Extent} Extent. */ @@ -17757,9 +17228,10 @@ ol.extent.empty = function(extent) { /** + * Determine if two extents are equivalent. * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent 2. - * @return {boolean} Equals. + * @return {boolean} The two extents are equivalent. * @api stable */ ol.extent.equals = function(extent1, extent2) { @@ -17769,9 +17241,10 @@ ol.extent.equals = function(extent1, extent2) { /** - * @param {ol.Extent} extent1 Extent 1. - * @param {ol.Extent} extent2 Extent 2. - * @return {ol.Extent} Extent. + * Modify an extent to include another extent. + * @param {ol.Extent} extent1 The extent to be modified. + * @param {ol.Extent} extent2 The extent that will be included in the first. + * @return {ol.Extent} A reference to the first (extended) extent. * @api stable */ ol.extent.extend = function(extent1, extent2) { @@ -17894,7 +17367,7 @@ ol.extent.forEachCorner = function(extent, callback, opt_this) { if (val) { return val; } - val = callback.call(opt_this, ol.extent.getBottomRight(extent)); + val = callback.call(opt_this, ol.extent.getTopLeft(extent)); if (val) { return val; } @@ -17916,6 +17389,7 @@ ol.extent.getArea = function(extent) { /** + * Get the bottom left coordinate of an extent. * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Bottom left coordinate. * @api stable @@ -17926,6 +17400,7 @@ ol.extent.getBottomLeft = function(extent) { /** + * Get the bottom right coordinate of an extent. * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Bottom right coordinate. * @api stable @@ -17936,6 +17411,7 @@ ol.extent.getBottomRight = function(extent) { /** + * Get the center coordinate of an extent. * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Center. * @api stable @@ -17964,7 +17440,7 @@ ol.extent.getCorner = function(extent, corner) { } else { goog.asserts.fail('Invalid corner: %s', corner); } - goog.asserts.assert(goog.isDef(coordinate)); + goog.asserts.assert(goog.isDef(coordinate), 'coordinate should be defined'); return coordinate; }; @@ -18013,6 +17489,7 @@ ol.extent.getForViewAndSize = /** + * Get the height of an extent. * @param {ol.Extent} extent Extent. * @return {number} Height. * @api stable @@ -18080,8 +17557,9 @@ ol.extent.getMargin = function(extent) { /** - * @param {ol.Extent} extent Extent. - * @return {ol.Size} Size. + * Get the size (width, height) of an extent. + * @param {ol.Extent} extent The extent. + * @return {ol.Size} The extent size. * @api stable */ ol.extent.getSize = function(extent) { @@ -18090,6 +17568,7 @@ ol.extent.getSize = function(extent) { /** + * Get the top left coordinate of an extent. * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Top left coordinate. * @api stable @@ -18100,6 +17579,7 @@ ol.extent.getTopLeft = function(extent) { /** + * Get the top right coordinate of an extent. * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Top right coordinate. * @api stable @@ -18110,6 +17590,7 @@ ol.extent.getTopRight = function(extent) { /** + * Get the width of an extent. * @param {ol.Extent} extent Extent. * @return {number} Width. * @api stable @@ -18120,9 +17601,10 @@ ol.extent.getWidth = function(extent) { /** + * Determine if one extent intersects another. * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent. - * @return {boolean} Intersects. + * @return {boolean} The two extents intersect. * @api stable */ ol.extent.intersects = function(extent1, extent2) { @@ -18134,6 +17616,7 @@ ol.extent.intersects = function(extent1, extent2) { /** + * Determine if an extent is empty. * @param {ol.Extent} extent Extent. * @return {boolean} Is empty. * @api stable @@ -18229,17 +17712,20 @@ ol.extent.intersectsSegment = function(extent, start, end) { // potentially intersects top x = endX - ((endY - maxY) / slope); intersects = x >= minX && x <= maxX; - } else if (!!(endRel & ol.extent.Relationship.RIGHT) && + } + if (!intersects && !!(endRel & ol.extent.Relationship.RIGHT) && !(startRel & ol.extent.Relationship.RIGHT)) { // potentially intersects right y = endY - ((endX - maxX) * slope); intersects = y >= minY && y <= maxY; - } else if (!!(endRel & ol.extent.Relationship.BELOW) && + } + if (!intersects && !!(endRel & ol.extent.Relationship.BELOW) && !(startRel & ol.extent.Relationship.BELOW)) { // potentially intersects bottom x = endX - ((endY - minY) / slope); intersects = x >= minX && x <= maxX; - } else if (!!(endRel & ol.extent.Relationship.LEFT) && + } + if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) && !(startRel & ol.extent.Relationship.LEFT)) { // potentially intersects left y = endY - ((endX - minX) * slope); @@ -18320,269 +17806,633 @@ ol.extent.transform2D = function(extent, transform, opt_extent) { return dest; }; +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /** - * @license - * Latitude/longitude spherical geodesy formulae taken from - * http://www.movable-type.co.uk/scripts/latlong.html - * Licenced under CC-BY-3.0. + * @fileoverview Utilities for creating functions. Loosely inspired by the + * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8. + * + * @author nicksantos@google.com (Nick Santos) */ -// FIXME add intersection of two paths given start points and bearings -// FIXME add rhumb lines - -goog.provide('ol.Sphere'); - -goog.require('goog.math'); +goog.provide('goog.functions'); /** - * @classdesc - * Class to create objects that can be used with {@link - * ol.geom.Polygon.circular}. - * - * For example to create a sphere whose radius is equal to the semi-major - * axis of the WGS84 ellipsoid: - * - * ```js - * var wgs84Sphere= new ol.Sphere(6378137); - * ``` - * - * @constructor - * @param {number} radius Radius. - * @api + * Creates a function that always returns the same value. + * @param {T} retValue The value to return. + * @return {function():T} The new function. + * @template T */ -ol.Sphere = function(radius) { - - /** - * @type {number} - */ - this.radius = radius; - +goog.functions.constant = function(retValue) { + return function() { + return retValue; + }; }; /** - * Returns the distance from c1 to c2 using the spherical law of cosines. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @return {number} Spherical law of cosines distance. + * Always returns false. + * @type {function(...): boolean} */ -ol.Sphere.prototype.cosineDistance = function(c1, c2) { - var lat1 = goog.math.toRadians(c1[1]); - var lat2 = goog.math.toRadians(c2[1]); - var deltaLon = goog.math.toRadians(c2[0] - c1[0]); - return this.radius * Math.acos( - Math.sin(lat1) * Math.sin(lat2) + - Math.cos(lat1) * Math.cos(lat2) * Math.cos(deltaLon)); -}; +goog.functions.FALSE = goog.functions.constant(false); /** - * Returns the distance of c3 from the great circle path defined by c1 and c2. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @param {ol.Coordinate} c3 Coordinate 3. - * @return {number} Cross-track distance. + * Always returns true. + * @type {function(...): boolean} */ -ol.Sphere.prototype.crossTrackDistance = function(c1, c2, c3) { - var d13 = this.cosineDistance(c1, c2); - var theta12 = goog.math.toRadians(this.initialBearing(c1, c2)); - var theta13 = goog.math.toRadians(this.initialBearing(c1, c3)); - return this.radius * - Math.asin(Math.sin(d13 / this.radius) * Math.sin(theta13 - theta12)); -}; +goog.functions.TRUE = goog.functions.constant(true); /** - * Returns the distance from c1 to c2 using Pythagoras's theorem on an - * equirectangular projection. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @return {number} Equirectangular distance. + * Always returns NULL. + * @type {function(...): null} */ -ol.Sphere.prototype.equirectangularDistance = function(c1, c2) { - var lat1 = goog.math.toRadians(c1[1]); - var lat2 = goog.math.toRadians(c2[1]); - var deltaLon = goog.math.toRadians(c2[0] - c1[0]); - var x = deltaLon * Math.cos((lat1 + lat2) / 2); - var y = lat2 - lat1; - return this.radius * Math.sqrt(x * x + y * y); -}; +goog.functions.NULL = goog.functions.constant(null); /** - * Returns the final bearing from c1 to c2. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @return {number} Initial bearing. + * A simple function that returns the first argument of whatever is passed + * into it. + * @param {T=} opt_returnValue The single value that will be returned. + * @param {...*} var_args Optional trailing arguments. These are ignored. + * @return {T} The first argument passed in, or undefined if nothing was passed. + * @template T */ -ol.Sphere.prototype.finalBearing = function(c1, c2) { - return (this.initialBearing(c2, c1) + 180) % 360; +goog.functions.identity = function(opt_returnValue, var_args) { + return opt_returnValue; }; /** - * Returns the distance from c1 to c2 using the haversine formula. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @return {number} Haversine distance. + * Creates a function that always throws an error with the given message. + * @param {string} message The error message. + * @return {!Function} The error-throwing function. */ -ol.Sphere.prototype.haversineDistance = function(c1, c2) { - var lat1 = goog.math.toRadians(c1[1]); - var lat2 = goog.math.toRadians(c2[1]); - var deltaLatBy2 = (lat2 - lat1) / 2; - var deltaLonBy2 = goog.math.toRadians(c2[0] - c1[0]) / 2; - var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + - Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * - Math.cos(lat1) * Math.cos(lat2); - return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +goog.functions.error = function(message) { + return function() { + throw Error(message); + }; }; /** - * Returns the point at `fraction` along the segment of the great circle passing - * through c1 and c2. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @param {number} fraction Fraction. - * @return {ol.Coordinate} Coordinate between c1 and c2. + * Creates a function that throws the given object. + * @param {*} err An object to be thrown. + * @return {!Function} The error-throwing function. */ -ol.Sphere.prototype.interpolate = function(c1, c2, fraction) { - var lat1 = goog.math.toRadians(c1[1]); - var lon1 = goog.math.toRadians(c1[0]); - var lat2 = goog.math.toRadians(c2[1]); - var lon2 = goog.math.toRadians(c2[0]); - var cosLat1 = Math.cos(lat1); - var sinLat1 = Math.sin(lat1); - var cosLat2 = Math.cos(lat2); - var sinLat2 = Math.sin(lat2); - var cosDeltaLon = Math.cos(lon2 - lon1); - var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon; - if (1 <= d) { - return c2.slice(); +goog.functions.fail = function(err) { + return function() { + throw err; } - d = fraction * Math.acos(d); - var cosD = Math.cos(d); - var sinD = Math.sin(d); - var y = Math.sin(lon2 - lon1) * cosLat2; - var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon; - var theta = Math.atan2(y, x); - var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta)); - var lon = lon1 + Math.atan2(Math.sin(theta) * sinD * cosLat1, - cosD - sinLat1 * Math.sin(lat)); - return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; }; /** - * Returns the initial bearing from c1 to c2. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @return {number} Initial bearing. + * Given a function, create a function that keeps opt_numArgs arguments and + * silently discards all additional arguments. + * @param {Function} f The original function. + * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0. + * @return {!Function} A version of f that only keeps the first opt_numArgs + * arguments. */ -ol.Sphere.prototype.initialBearing = function(c1, c2) { - var lat1 = goog.math.toRadians(c1[1]); - var lat2 = goog.math.toRadians(c2[1]); - var deltaLon = goog.math.toRadians(c2[0] - c1[0]); - var y = Math.sin(deltaLon) * Math.cos(lat2); - var x = Math.cos(lat1) * Math.sin(lat2) - - Math.sin(lat1) * Math.cos(lat2) * Math.cos(deltaLon); - return goog.math.toDegrees(Math.atan2(y, x)); +goog.functions.lock = function(f, opt_numArgs) { + opt_numArgs = opt_numArgs || 0; + return function() { + return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs)); + }; }; /** - * Returns the maximum latitude of the great circle defined by bearing and - * latitude. - * - * @param {number} bearing Bearing. - * @param {number} latitude Latitude. - * @return {number} Maximum latitude. + * Creates a function that returns its nth argument. + * @param {number} n The position of the return argument. + * @return {!Function} A new function. */ -ol.Sphere.prototype.maximumLatitude = function(bearing, latitude) { - return Math.cos(Math.abs(Math.sin(goog.math.toRadians(bearing)) * - Math.cos(goog.math.toRadians(latitude)))); +goog.functions.nth = function(n) { + return function() { + return arguments[n]; + }; }; /** - * Returns the midpoint between c1 and c2. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 2. - * @return {ol.Coordinate} Midpoint. + * Given a function, create a new function that swallows its return value + * and replaces it with a new one. + * @param {Function} f A function. + * @param {T} retValue A new return value. + * @return {function(...?):T} A new function. + * @template T */ -ol.Sphere.prototype.midpoint = function(c1, c2) { - var lat1 = goog.math.toRadians(c1[1]); - var lat2 = goog.math.toRadians(c2[1]); - var lon1 = goog.math.toRadians(c1[0]); - var deltaLon = goog.math.toRadians(c2[0] - c1[0]); - var Bx = Math.cos(lat2) * Math.cos(deltaLon); - var By = Math.cos(lat2) * Math.sin(deltaLon); - var cosLat1PlusBx = Math.cos(lat1) + Bx; - var lat = Math.atan2(Math.sin(lat1) + Math.sin(lat2), - Math.sqrt(cosLat1PlusBx * cosLat1PlusBx + By * By)); - var lon = lon1 + Math.atan2(By, cosLat1PlusBx); - return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; +goog.functions.withReturnValue = function(f, retValue) { + return goog.functions.sequence(f, goog.functions.constant(retValue)); }; /** - * Returns the coordinate at the given distance and bearing from `c1`. + * Creates a function that returns whether its arguement equals the given value. * - * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees). - * @param {number} distance The great-circle distance between the origin - * point and the target point. - * @param {number} bearing The bearing (in radians). - * @return {ol.Coordinate} The target point. + * Example: + * var key = goog.object.findKey(obj, goog.functions.equalTo('needle')); + * + * @param {*} value The value to compare to. + * @param {boolean=} opt_useLooseComparison Whether to use a loose (==) + * comparison rather than a strict (===) one. Defaults to false. + * @return {function(*):boolean} The new function. */ -ol.Sphere.prototype.offset = function(c1, distance, bearing) { - var lat1 = goog.math.toRadians(c1[1]); - var lon1 = goog.math.toRadians(c1[0]); - var dByR = distance / this.radius; - var lat = Math.asin( - Math.sin(lat1) * Math.cos(dByR) + - Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing)); - var lon = lon1 + Math.atan2( - Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1), - Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat)); - return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; +goog.functions.equalTo = function(value, opt_useLooseComparison) { + return function(other) { + return opt_useLooseComparison ? (value == other) : (value === other); + }; }; -goog.provide('ol.sphere.NORMAL'); - -goog.require('ol.Sphere'); - /** - * The normal sphere. - * @const - * @type {ol.Sphere} + * Creates the composition of the functions passed in. + * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)). + * @param {function(...?):T} fn The final function. + * @param {...Function} var_args A list of functions. + * @return {function(...?):T} The composition of all inputs. + * @template T */ -ol.sphere.NORMAL = new ol.Sphere(6370997); +goog.functions.compose = function(fn, var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + if (length) { + result = functions[length - 1].apply(this, arguments); + } -goog.provide('ol.proj'); -goog.provide('ol.proj.METERS_PER_UNIT'); -goog.provide('ol.proj.Projection'); -goog.provide('ol.proj.ProjectionLike'); -goog.provide('ol.proj.Units'); + for (var i = length - 2; i >= 0; i--) { + result = functions[i].call(this, result); + } + return result; + }; +}; -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('ol'); -goog.require('ol.Extent'); -goog.require('ol.TransformFunction'); -goog.require('ol.extent'); -goog.require('ol.sphere.NORMAL'); + +/** + * Creates a function that calls the functions passed in in sequence, and + * returns the value of the last function. For example, + * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x). + * @param {...Function} var_args A list of functions. + * @return {!Function} A function that calls all inputs in sequence. + */ +goog.functions.sequence = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * Creates a function that returns true if each of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns false. + * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...?):boolean} A function that ANDs its component + * functions. + */ +goog.functions.and = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (!functions[i].apply(this, arguments)) { + return false; + } + } + return true; + }; +}; + + +/** + * Creates a function that returns true if any of its components evaluates + * to true. The components are evaluated in order, and the evaluation will be + * short-circuited as soon as a function returns true. + * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x). + * @param {...Function} var_args A list of functions. + * @return {function(...?):boolean} A function that ORs its component + * functions. + */ +goog.functions.or = function(var_args) { + var functions = arguments; + var length = functions.length; + return function() { + for (var i = 0; i < length; i++) { + if (functions[i].apply(this, arguments)) { + return true; + } + } + return false; + }; +}; + + +/** + * Creates a function that returns the Boolean opposite of a provided function. + * For example, (goog.functions.not(f))(x) is equivalent to !f(x). + * @param {!Function} f The original function. + * @return {function(...?):boolean} A function that delegates to f and returns + * opposite. + */ +goog.functions.not = function(f) { + return function() { + return !f.apply(this, arguments); + }; +}; + + +/** + * Generic factory function to construct an object given the constructor + * and the arguments. Intended to be bound to create object factories. + * + * Example: + * + * var factory = goog.partial(goog.functions.create, Class); + * + * @param {function(new:T, ...)} constructor The constructor for the Object. + * @param {...*} var_args The arguments to be passed to the constructor. + * @return {T} A new instance of the class given in {@code constructor}. + * @template T + */ +goog.functions.create = function(constructor, var_args) { + /** + * @constructor + * @final + */ + var temp = function() {}; + temp.prototype = constructor.prototype; + + // obj will have constructor's prototype in its chain and + // 'obj instanceof constructor' will be true. + var obj = new temp(); + + // obj is initialized by constructor. + // arguments is only array-like so lacks shift(), but can be used with + // the Array prototype function. + constructor.apply(obj, Array.prototype.slice.call(arguments, 1)); + return obj; +}; + + +/** + * @define {boolean} Whether the return value cache should be used. + * This should only be used to disable caches when testing. + */ +goog.define('goog.functions.CACHE_RETURN_VALUE', true); + + +/** + * Gives a wrapper function that caches the return value of a parameterless + * function when first called. + * + * When called for the first time, the given function is called and its + * return value is cached (thus this is only appropriate for idempotent + * functions). Subsequent calls will return the cached return value. This + * allows the evaluation of expensive functions to be delayed until first used. + * + * To cache the return values of functions with parameters, see goog.memoize. + * + * @param {!function():T} fn A function to lazily evaluate. + * @return {!function():T} A wrapped version the function. + * @template T + */ +goog.functions.cacheReturnValue = function(fn) { + var called = false; + var value; + + return function() { + if (!goog.functions.CACHE_RETURN_VALUE) { + return fn(); + } + + if (!called) { + value = fn(); + called = true; + } + + return value; + } +}; + +/** + * @license + * Latitude/longitude spherical geodesy formulae taken from + * http://www.movable-type.co.uk/scripts/latlong.html + * Licensed under CC-BY-3.0. + */ + +// FIXME add intersection of two paths given start points and bearings +// FIXME add rhumb lines + +goog.provide('ol.Sphere'); + +goog.require('goog.math'); + + + +/** + * @classdesc + * Class to create objects that can be used with {@link + * ol.geom.Polygon.circular}. + * + * For example to create a sphere whose radius is equal to the semi-major + * axis of the WGS84 ellipsoid: + * + * ```js + * var wgs84Sphere= new ol.Sphere(6378137); + * ``` + * + * @constructor + * @param {number} radius Radius. + * @api + */ +ol.Sphere = function(radius) { + + /** + * @type {number} + */ + this.radius = radius; + +}; + + +/** + * Returns the distance from c1 to c2 using the spherical law of cosines. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Spherical law of cosines distance. + */ +ol.Sphere.prototype.cosineDistance = function(c1, c2) { + var lat1 = goog.math.toRadians(c1[1]); + var lat2 = goog.math.toRadians(c2[1]); + var deltaLon = goog.math.toRadians(c2[0] - c1[0]); + return this.radius * Math.acos( + Math.sin(lat1) * Math.sin(lat2) + + Math.cos(lat1) * Math.cos(lat2) * Math.cos(deltaLon)); +}; + + +/** + * Returns the geodesic area for a list of coordinates. + * + * [Reference](http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409) + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for + * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 + * + * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear + * ring. If the ring is oriented clockwise, the area will be positive, + * otherwise it will be negative. + * @return {number} Area. + * @api + */ +ol.Sphere.prototype.geodesicArea = function(coordinates) { + var area = 0, len = coordinates.length; + var x1 = coordinates[len - 1][0]; + var y1 = coordinates[len - 1][1]; + for (var i = 0; i < len; i++) { + var x2 = coordinates[i][0], y2 = coordinates[i][1]; + area += goog.math.toRadians(x2 - x1) * + (2 + Math.sin(goog.math.toRadians(y1)) + + Math.sin(goog.math.toRadians(y2))); + x1 = x2; + y1 = y2; + } + return area * this.radius * this.radius / 2.0; +}; + + +/** + * Returns the distance of c3 from the great circle path defined by c1 and c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @param {ol.Coordinate} c3 Coordinate 3. + * @return {number} Cross-track distance. + */ +ol.Sphere.prototype.crossTrackDistance = function(c1, c2, c3) { + var d13 = this.cosineDistance(c1, c2); + var theta12 = goog.math.toRadians(this.initialBearing(c1, c2)); + var theta13 = goog.math.toRadians(this.initialBearing(c1, c3)); + return this.radius * + Math.asin(Math.sin(d13 / this.radius) * Math.sin(theta13 - theta12)); +}; + + +/** + * Returns the distance from c1 to c2 using Pythagoras's theorem on an + * equirectangular projection. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Equirectangular distance. + */ +ol.Sphere.prototype.equirectangularDistance = function(c1, c2) { + var lat1 = goog.math.toRadians(c1[1]); + var lat2 = goog.math.toRadians(c2[1]); + var deltaLon = goog.math.toRadians(c2[0] - c1[0]); + var x = deltaLon * Math.cos((lat1 + lat2) / 2); + var y = lat2 - lat1; + return this.radius * Math.sqrt(x * x + y * y); +}; + + +/** + * Returns the final bearing from c1 to c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Initial bearing. + */ +ol.Sphere.prototype.finalBearing = function(c1, c2) { + return (this.initialBearing(c2, c1) + 180) % 360; +}; + + +/** + * Returns the distance from c1 to c2 using the haversine formula. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Haversine distance. + * @api + */ +ol.Sphere.prototype.haversineDistance = function(c1, c2) { + var lat1 = goog.math.toRadians(c1[1]); + var lat2 = goog.math.toRadians(c2[1]); + var deltaLatBy2 = (lat2 - lat1) / 2; + var deltaLonBy2 = goog.math.toRadians(c2[0] - c1[0]) / 2; + var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + + Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * + Math.cos(lat1) * Math.cos(lat2); + return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +}; + + +/** + * Returns the point at `fraction` along the segment of the great circle passing + * through c1 and c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @param {number} fraction Fraction. + * @return {ol.Coordinate} Coordinate between c1 and c2. + */ +ol.Sphere.prototype.interpolate = function(c1, c2, fraction) { + var lat1 = goog.math.toRadians(c1[1]); + var lon1 = goog.math.toRadians(c1[0]); + var lat2 = goog.math.toRadians(c2[1]); + var lon2 = goog.math.toRadians(c2[0]); + var cosLat1 = Math.cos(lat1); + var sinLat1 = Math.sin(lat1); + var cosLat2 = Math.cos(lat2); + var sinLat2 = Math.sin(lat2); + var cosDeltaLon = Math.cos(lon2 - lon1); + var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon; + if (1 <= d) { + return c2.slice(); + } + d = fraction * Math.acos(d); + var cosD = Math.cos(d); + var sinD = Math.sin(d); + var y = Math.sin(lon2 - lon1) * cosLat2; + var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon; + var theta = Math.atan2(y, x); + var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta)); + var lon = lon1 + Math.atan2(Math.sin(theta) * sinD * cosLat1, + cosD - sinLat1 * Math.sin(lat)); + return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; +}; + + +/** + * Returns the initial bearing from c1 to c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Initial bearing. + */ +ol.Sphere.prototype.initialBearing = function(c1, c2) { + var lat1 = goog.math.toRadians(c1[1]); + var lat2 = goog.math.toRadians(c2[1]); + var deltaLon = goog.math.toRadians(c2[0] - c1[0]); + var y = Math.sin(deltaLon) * Math.cos(lat2); + var x = Math.cos(lat1) * Math.sin(lat2) - + Math.sin(lat1) * Math.cos(lat2) * Math.cos(deltaLon); + return goog.math.toDegrees(Math.atan2(y, x)); +}; + + +/** + * Returns the maximum latitude of the great circle defined by bearing and + * latitude. + * + * @param {number} bearing Bearing. + * @param {number} latitude Latitude. + * @return {number} Maximum latitude. + */ +ol.Sphere.prototype.maximumLatitude = function(bearing, latitude) { + return Math.cos(Math.abs(Math.sin(goog.math.toRadians(bearing)) * + Math.cos(goog.math.toRadians(latitude)))); +}; + + +/** + * Returns the midpoint between c1 and c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {ol.Coordinate} Midpoint. + */ +ol.Sphere.prototype.midpoint = function(c1, c2) { + var lat1 = goog.math.toRadians(c1[1]); + var lat2 = goog.math.toRadians(c2[1]); + var lon1 = goog.math.toRadians(c1[0]); + var deltaLon = goog.math.toRadians(c2[0] - c1[0]); + var Bx = Math.cos(lat2) * Math.cos(deltaLon); + var By = Math.cos(lat2) * Math.sin(deltaLon); + var cosLat1PlusBx = Math.cos(lat1) + Bx; + var lat = Math.atan2(Math.sin(lat1) + Math.sin(lat2), + Math.sqrt(cosLat1PlusBx * cosLat1PlusBx + By * By)); + var lon = lon1 + Math.atan2(By, cosLat1PlusBx); + return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; +}; + + +/** + * Returns the coordinate at the given distance and bearing from `c1`. + * + * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees). + * @param {number} distance The great-circle distance between the origin + * point and the target point. + * @param {number} bearing The bearing (in radians). + * @return {ol.Coordinate} The target point. + */ +ol.Sphere.prototype.offset = function(c1, distance, bearing) { + var lat1 = goog.math.toRadians(c1[1]); + var lon1 = goog.math.toRadians(c1[0]); + var dByR = distance / this.radius; + var lat = Math.asin( + Math.sin(lat1) * Math.cos(dByR) + + Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing)); + var lon = lon1 + Math.atan2( + Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1), + Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat)); + return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; +}; + +goog.provide('ol.sphere.NORMAL'); + +goog.require('ol.Sphere'); + + +/** + * The normal sphere. + * @const + * @type {ol.Sphere} + */ +ol.sphere.NORMAL = new ol.Sphere(6370997); + +goog.provide('ol.proj'); +goog.provide('ol.proj.METERS_PER_UNIT'); +goog.provide('ol.proj.Projection'); +goog.provide('ol.proj.ProjectionLike'); +goog.provide('ol.proj.Units'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol'); +goog.require('ol.Extent'); +goog.require('ol.TransformFunction'); +goog.require('ol.extent'); +goog.require('ol.sphere.NORMAL'); /** @@ -18595,7 +18445,7 @@ ol.proj.ProjectionLike; /** - * Projection units: `'degrees'`, `'ft'`, `'m'` or `'pixels'`. + * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, or `'us-ft'`. * @enum {string} * @api stable */ @@ -18603,7 +18453,8 @@ ol.proj.Units = { DEGREES: 'degrees', FEET: 'ft', METERS: 'm', - PIXELS: 'pixels' + PIXELS: 'pixels', + USFEET: 'us-ft' }; @@ -18618,6 +18469,7 @@ ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] = 2 * Math.PI * ol.sphere.NORMAL.radius / 360; ol.proj.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048; ol.proj.METERS_PER_UNIT[ol.proj.Units.METERS] = 1; +ol.proj.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937; @@ -18690,12 +18542,69 @@ ol.proj.Projection = function(options) { */ this.global_ = goog.isDef(options.global) ? options.global : false; + + /** + * @private + * @type {boolean} + */ + this.canWrapX_ = this.global_ && !goog.isNull(this.extent_); + + /** + * @private + * @type {function(number, ol.Coordinate):number} + */ + this.getPointResolutionFunc_ = goog.isDef(options.getPointResolution) ? + options.getPointResolution : this.getPointResolution_; + /** * @private * @type {ol.tilegrid.TileGrid} */ this.defaultTileGrid_ = null; + if (ol.ENABLE_PROJ4JS && typeof proj4 == 'function') { + var code = options.code; + var def = proj4.defs(code); + if (goog.isDef(def)) { + if (goog.isDef(def.axis) && !goog.isDef(options.axisOrientation)) { + this.axisOrientation_ = def.axis; + } + if (!goog.isDef(options.units)) { + var units = def.units; + if (!goog.isDef(units)) { + if (goog.isDef(def.to_meter)) { + units = def.to_meter.toString(); + ol.proj.METERS_PER_UNIT[units] = def.to_meter; + } + } + this.units_ = units; + } + var projections = ol.proj.projections_; + var currentCode, currentDef, currentProj, proj4Transform; + for (currentCode in projections) { + currentDef = proj4.defs(currentCode); + if (goog.isDef(currentDef)) { + currentProj = ol.proj.get(currentCode); + if (currentDef === def) { + ol.proj.addEquivalentProjections([currentProj, this]); + } else { + proj4Transform = proj4(currentCode, code); + ol.proj.addCoordinateTransforms(currentProj, this, + proj4Transform.forward, proj4Transform.inverse); + } + } + } + } + } + +}; + + +/** + * @return {boolean} The projection is suitable for wrapping the x-axis + */ +ol.proj.Projection.prototype.canWrapX = function() { + return this.canWrapX_; }; @@ -18767,7 +18676,7 @@ ol.proj.Projection.prototype.getAxisOrientation = function() { /** * Is this projection a global projection which spans the whole world? - * @return {boolean} Wether the projection is global. + * @return {boolean} Whether the projection is global. * @api stable */ ol.proj.Projection.prototype.isGlobal = function() { @@ -18775,6 +18684,17 @@ ol.proj.Projection.prototype.isGlobal = function() { }; +/** +* Set if the projection is a global projection which spans the whole world +* @param {boolean} global Whether the projection is global. +* @api stable +*/ +ol.proj.Projection.prototype.setGlobal = function(global) { + this.global_ = global; + this.canWrapX_ = global && !goog.isNull(this.extent_); +}; + + /** * @return {ol.tilegrid.TileGrid} The default tile grid. */ @@ -18798,6 +18718,7 @@ ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) { */ ol.proj.Projection.prototype.setExtent = function(extent) { this.extent_ = extent; + this.canWrapX_ = this.global_ && !goog.isNull(extent); }; @@ -18813,16 +18734,29 @@ ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) { /** - * Get the resolution of the point in degrees. For projections with degrees as - * the unit this will simply return the provided resolution. For other - * projections the point resolution is estimated by transforming the center - * pixel to EPSG:4326, measuring its width and height on the normal sphere, - * and taking the average of the width and height. - * @param {number} resolution Resolution. - * @param {ol.Coordinate} point Point. - * @return {number} Point resolution. - */ -ol.proj.Projection.prototype.getPointResolution = function(resolution, point) { +* Set the getPointResolution function for this projection. +* @param {function(number, ol.Coordinate):number} func Function +* @api +*/ +ol.proj.Projection.prototype.setGetPointResolution = function(func) { + this.getPointResolutionFunc_ = func; +}; + + +/** +* Default version. +* Get the resolution of the point in degrees or distance units. +* For projections with degrees as the unit this will simply return the +* provided resolution. For other projections the point resolution is +* estimated by transforming the 'point' pixel to EPSG:4326, +* measuring its width and height on the normal sphere, +* and taking the average of the width and height. +* @param {number} resolution Nominal resolution in projection units. +* @param {ol.Coordinate} point Point to find adjusted resolution at. +* @return {number} Point resolution at point in projection units. +* @private +*/ +ol.proj.Projection.prototype.getPointResolution_ = function(resolution, point) { var units = this.getUnits(); if (units == ol.proj.Units.DEGREES) { return resolution; @@ -18853,6 +18787,26 @@ ol.proj.Projection.prototype.getPointResolution = function(resolution, point) { }; +/** + * Get the resolution of the point in degrees or distance units. + * For projections with degrees as the unit this will simply return the + * provided resolution. The default for other projections is to estimate + * the point resolution by transforming the 'point' pixel to EPSG:4326, + * measuring its width and height on the normal sphere, + * and taking the average of the width and height. + * An alternative implementation may be given when constructing a + * projection. For many local projections, + * such a custom function will return the resolution unchanged. + * @param {number} resolution Resolution in projection units. + * @param {ol.Coordinate} point Point. + * @return {number} Point resolution in projection units. + * @api + */ +ol.proj.Projection.prototype.getPointResolution = function(resolution, point) { + return this.getPointResolutionFunc_(resolution, point); +}; + + /** * @private * @type {Object.<string, ol.proj.Projection>} @@ -18911,7 +18865,8 @@ ol.proj.addEquivalentTransforms = /** - * Add a Projection object to the list of supported projections. + * Add a Projection object to the list of supported projections that can be + * looked up by their code. * * @param {ol.proj.Projection} projection Projection instance. * @api stable @@ -18953,7 +18908,8 @@ ol.proj.createProjection = function(projection, defaultCode) { } else if (goog.isString(projection)) { return ol.proj.get(projection); } else { - goog.asserts.assertInstanceof(projection, ol.proj.Projection); + goog.asserts.assertInstanceof(projection, ol.proj.Projection, + 'projection should be an ol.proj.Projection'); return projection; } }; @@ -19054,8 +19010,10 @@ ol.proj.removeTransform = function(source, destination) { var sourceCode = source.getCode(); var destinationCode = destination.getCode(); var transforms = ol.proj.transforms_; - goog.asserts.assert(sourceCode in transforms); - goog.asserts.assert(destinationCode in transforms[sourceCode]); + goog.asserts.assert(sourceCode in transforms, + 'sourceCode should be in transforms'); + goog.asserts.assert(destinationCode in transforms[sourceCode], + 'destinationCode should be in transforms of sourceCode'); var transform = transforms[sourceCode][destinationCode]; delete transforms[sourceCode][destinationCode]; var keys = goog.object.getKeys(transforms[sourceCode]); @@ -19066,6 +19024,36 @@ ol.proj.removeTransform = function(source, destination) { }; +/** + * Transforms a coordinate from longitude/latitude to a different projection. + * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e. + * an array with longitude as 1st and latitude as 2nd element. + * @param {ol.proj.ProjectionLike=} opt_projection Target projection. The + * default is Web Mercator, i.e. 'EPSG:3857'. + * @return {ol.Coordinate} Coordinate projected to the target projection. + * @api stable + */ +ol.proj.fromLonLat = function(coordinate, opt_projection) { + return ol.proj.transform(coordinate, 'EPSG:4326', + goog.isDef(opt_projection) ? opt_projection : 'EPSG:3857'); +}; + + +/** + * Transforms a coordinate to longitude/latitude. + * @param {ol.Coordinate} coordinate Projected coordinate. + * @param {ol.proj.ProjectionLike=} opt_projection Projection of the coordinate. + * The default is Web Mercator, i.e. 'EPSG:3857'. + * @return {ol.Coordinate} Coordinate as longitude and latitude, i.e. an array + * with longitude as 1st and latitude as 2nd element. + * @api stable + */ +ol.proj.toLonLat = function(coordinate, opt_projection) { + return ol.proj.transform(coordinate, + goog.isDef(opt_projection) ? opt_projection : 'EPSG:3857', 'EPSG:4326'); +}; + + /** * Fetches a Projection object for the code specified. * @@ -19081,43 +19069,11 @@ ol.proj.get = function(projectionLike) { projection = projectionLike; } else if (goog.isString(projectionLike)) { var code = projectionLike; - var projections = ol.proj.projections_; - projection = projections[code]; + projection = ol.proj.projections_[code]; if (ol.ENABLE_PROJ4JS && !goog.isDef(projection) && - typeof proj4 == 'function') { - var def = proj4.defs(code); - if (goog.isDef(def)) { - var units = def.units; - if (!goog.isDef(units)) { - if (goog.isDef(def.to_meter)) { - units = def.to_meter.toString(); - ol.proj.METERS_PER_UNIT[units] = def.to_meter; - } - } - projection = new ol.proj.Projection({ - code: code, - units: units, - axisOrientation: def.axis - }); - ol.proj.addProjection(projection); - var currentCode, currentDef, currentProj, proj4Transform; - for (currentCode in projections) { - currentDef = proj4.defs(currentCode); - if (goog.isDef(currentDef)) { - currentProj = ol.proj.get(currentCode); - if (currentDef === def) { - ol.proj.addEquivalentProjections([currentProj, projection]); - } else { - proj4Transform = proj4(currentCode, code); - ol.proj.addCoordinateTransforms(currentProj, projection, - proj4Transform.forward, proj4Transform.inverse); - } - } - } - } else { - goog.asserts.assert(goog.isDef(projection)); - projection = null; - } + typeof proj4 == 'function' && goog.isDef(proj4.defs(code))) { + projection = new ol.proj.Projection({code: code}); + ol.proj.addProjection(projection); } } else { projection = null; @@ -19138,6 +19094,8 @@ ol.proj.get = function(projectionLike) { ol.proj.equivalent = function(projection1, projection2) { if (projection1 === projection2) { return true; + } else if (projection1.getCode() === projection2.getCode()) { + return true; } else if (projection1.getUnits() != projection2.getUnits()) { return false; } else { @@ -19186,7 +19144,7 @@ ol.proj.getTransformFromProjections = transform = transforms[sourceCode][destinationCode]; } if (!goog.isDef(transform)) { - goog.asserts.assert(goog.isDef(transform)); + goog.asserts.assert(goog.isDef(transform), 'transform should be defined'); transform = ol.proj.identityTransform; } return transform; @@ -19284,3265 +19242,3714 @@ ol.proj.transformWithProjections = return transformFn(point); }; -goog.provide('ol.View'); -goog.provide('ol.ViewHint'); +goog.provide('ol.geom.Geometry'); +goog.provide('ol.geom.GeometryLayout'); +goog.provide('ol.geom.GeometryType'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol'); -goog.require('ol.CenterConstraint'); -goog.require('ol.Constraints'); +goog.require('goog.functions'); goog.require('ol.Object'); -goog.require('ol.ResolutionConstraint'); -goog.require('ol.RotationConstraint'); -goog.require('ol.RotationConstraintType'); -goog.require('ol.Size'); -goog.require('ol.coordinate'); goog.require('ol.extent'); goog.require('ol.proj'); -goog.require('ol.proj.METERS_PER_UNIT'); -goog.require('ol.proj.Projection'); -goog.require('ol.proj.Units'); /** + * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`, + * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`, + * `'GeometryCollection'`, `'Circle'`. * @enum {string} + * @api stable */ -ol.ViewProperty = { - CENTER: 'center', - RESOLUTION: 'resolution', - ROTATION: 'rotation' +ol.geom.GeometryType = { + POINT: 'Point', + LINE_STRING: 'LineString', + LINEAR_RING: 'LinearRing', + POLYGON: 'Polygon', + MULTI_POINT: 'MultiPoint', + MULTI_LINE_STRING: 'MultiLineString', + MULTI_POLYGON: 'MultiPolygon', + GEOMETRY_COLLECTION: 'GeometryCollection', + CIRCLE: 'Circle' }; /** - * @enum {number} + * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z') + * or measure ('M') coordinate is available. Supported values are `'XY'`, + * `'XYZ'`, `'XYM'`, `'XYZM'`. + * @enum {string} + * @api stable */ -ol.ViewHint = { - ANIMATING: 0, - INTERACTING: 1 +ol.geom.GeometryLayout = { + XY: 'XY', + XYZ: 'XYZ', + XYM: 'XYM', + XYZM: 'XYZM' }; /** * @classdesc - * An ol.View object represents a simple 2D view of the map. - * - * This is the object to act upon to change the center, resolution, - * and rotation of the map. - * - * ### The view states - * - * An `ol.View` is determined by three states: `center`, `resolution`, - * and `rotation`. Each state has a corresponding getter and setter, e.g. - * `getCenter` and `setCenter` for the `center` state. - * - * An `ol.View` has a `projection`. The projection determines the - * coordinate system of the center, and its units determine the units of the - * resolution (projection units per pixel). The default projection is - * Spherical Mercator (EPSG:3857). - * - * ### The constraints - * - * `setCenter`, `setResolution` and `setRotation` can be used to change the - * states of the view. Any value can be passed to the setters. And the value - * that is passed to a setter will effectively be the value set in the view, - * and returned by the corresponding getter. - * - * But an `ol.View` object also has a *resolution constraint*, a - * *rotation constraint* and a *center constraint*. - * - * As said above, no constraints are applied when the setters are used to set - * new states for the view. Applying constraints is done explicitly through - * the use of the `constrain*` functions (`constrainResolution` and - * `constrainRotation` and `constrainCenter`). - * - * The main users of the constraints are the interactions and the - * controls. For example, double-clicking on the map changes the view to - * the "next" resolution. And releasing the fingers after pinch-zooming - * snaps to the closest resolution (with an animation). - * - * The *resolution constraint* snaps to specific resolutions. It is - * determined by the following options: `resolutions`, `maxResolution`, - * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three - * options are ignored. See documentation for each option for more - * information. - * - * The *rotation constraint* snaps to specific angles. It is determined - * by the following options: `enableRotation` and `constrainRotation`. - * By default the rotation value is snapped to zero when approaching the - * horizontal. + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for vector geometries. * - * The *center constraint* is determined by the `extent` option. By - * default the center is not constrained at all. + * To get notified of changes to the geometry, register a listener for the + * generic `change` event on your geometry instance. * * @constructor * @extends {ol.Object} - * @param {olx.ViewOptions=} opt_options View options. * @api stable */ -ol.View = function(opt_options) { - goog.base(this); - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {Array.<number>} - */ - this.hints_ = [0, 0]; +ol.geom.Geometry = function() { - /** - * @type {Object.<string, *>} - */ - var properties = {}; - properties[ol.ViewProperty.CENTER] = goog.isDef(options.center) ? - options.center : null; + goog.base(this); /** * @private - * @type {ol.proj.Projection} + * @type {ol.Extent} */ - this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857'); - - var resolutionConstraintInfo = ol.View.createResolutionConstraint_( - options); + this.extent_ = ol.extent.createEmpty(); /** * @private * @type {number} */ - this.maxResolution_ = resolutionConstraintInfo.maxResolution; + this.extentRevision_ = -1; /** - * @private - * @type {number} + * @protected + * @type {Object.<string, ol.geom.Geometry>} */ - this.minResolution_ = resolutionConstraintInfo.minResolution; + this.simplifiedGeometryCache = {}; /** - * @private + * @protected * @type {number} */ - this.minZoom_ = resolutionConstraintInfo.minZoom; - - var centerConstraint = ol.View.createCenterConstraint_(options); - var resolutionConstraint = resolutionConstraintInfo.constraint; - var rotationConstraint = ol.View.createRotationConstraint_(options); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; /** - * @private - * @type {ol.Constraints} + * @protected + * @type {number} */ - this.constraints_ = new ol.Constraints( - centerConstraint, resolutionConstraint, rotationConstraint); + this.simplifiedGeometryRevision = 0; - if (goog.isDef(options.resolution)) { - properties[ol.ViewProperty.RESOLUTION] = options.resolution; - } else if (goog.isDef(options.zoom)) { - properties[ol.ViewProperty.RESOLUTION] = this.constrainResolution( - this.maxResolution_, options.zoom - this.minZoom_); - } - properties[ol.ViewProperty.ROTATION] = - goog.isDef(options.rotation) ? options.rotation : 0; - this.setProperties(properties); }; -goog.inherits(ol.View, ol.Object); +goog.inherits(ol.geom.Geometry, ol.Object); /** - * @param {number} rotation Target rotation. - * @param {ol.Coordinate} anchor Rotation anchor. - * @return {ol.Coordinate|undefined} Center for rotation and anchor. + * Make a complete copy of the geometry. + * @function + * @return {!ol.geom.Geometry} Clone. */ -ol.View.prototype.calculateCenterRotate = function(rotation, anchor) { - var center; - var currentCenter = this.getCenter(); - if (goog.isDef(currentCenter)) { - center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]]; - ol.coordinate.rotate(center, rotation - this.getRotation()); - ol.coordinate.add(center, anchor); - } - return center; -}; +ol.geom.Geometry.prototype.clone = goog.abstractMethod; /** - * @param {number} resolution Target resolution. - * @param {ol.Coordinate} anchor Zoom anchor. - * @return {ol.Coordinate|undefined} Center for resolution and anchor. + * @param {number} x X. + * @param {number} y Y. + * @param {ol.Coordinate} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @return {number} Minimum squared distance. */ -ol.View.prototype.calculateCenterZoom = function(resolution, anchor) { - var center; - var currentCenter = this.getCenter(); - var currentResolution = this.getResolution(); - if (goog.isDef(currentCenter) && goog.isDef(currentResolution)) { - var x = anchor[0] - - resolution * (anchor[0] - currentCenter[0]) / currentResolution; - var y = anchor[1] - - resolution * (anchor[1] - currentCenter[1]) / currentResolution; - center = [x, y]; - } - return center; -}; +ol.geom.Geometry.prototype.closestPointXY = goog.abstractMethod; /** - * Get the constrained center of this view. - * @param {ol.Coordinate|undefined} center Center. - * @return {ol.Coordinate|undefined} Constrained center. - * @api + * Return the closest point of the geometry to the passed point as + * {@link ol.Coordinate coordinate}. + * @param {ol.Coordinate} point Point. + * @param {ol.Coordinate=} opt_closestPoint Closest point. + * @return {ol.Coordinate} Closest point. + * @api stable */ -ol.View.prototype.constrainCenter = function(center) { - return this.constraints_.center(center); +ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) { + var closestPoint = goog.isDef(opt_closestPoint) ? + opt_closestPoint : [NaN, NaN]; + this.closestPointXY(point[0], point[1], closestPoint, Infinity); + return closestPoint; }; /** - * Get the constrained resolution of this view. - * @param {number|undefined} resolution Resolution. - * @param {number=} opt_delta Delta. Default is `0`. - * @param {number=} opt_direction Direction. Default is `0`. - * @return {number|undefined} Constrained resolution. - * @api + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Contains coordinate. */ -ol.View.prototype.constrainResolution = function( - resolution, opt_delta, opt_direction) { - var delta = opt_delta || 0; - var direction = opt_direction || 0; - return this.constraints_.resolution(resolution, delta, direction); +ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) { + return this.containsXY(coordinate[0], coordinate[1]); }; /** - * Get the constrained rotation of this view. - * @param {number|undefined} rotation Rotation. - * @param {number=} opt_delta Delta. Default is `0`. - * @return {number|undefined} Constrained rotation. - * @api + * @param {ol.Extent} extent Extent. + * @protected + * @return {ol.Extent} extent Extent. */ -ol.View.prototype.constrainRotation = function(rotation, opt_delta) { - var delta = opt_delta || 0; - return this.constraints_.rotation(rotation, delta); -}; +ol.geom.Geometry.prototype.computeExtent = goog.abstractMethod; /** - * @return {ol.Coordinate|undefined} The center of the view. - * @observable - * @api stable + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). */ -ol.View.prototype.getCenter = function() { - return /** @type {ol.Coordinate|undefined} */ ( - this.get(ol.ViewProperty.CENTER)); -}; -goog.exportProperty( - ol.View.prototype, - 'getCenter', - ol.View.prototype.getCenter); +ol.geom.Geometry.prototype.containsXY = goog.functions.FALSE; /** - * @return {Array.<number>} Hint. + * Get the extent of the geometry. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} extent Extent. + * @api stable */ -ol.View.prototype.getHints = function() { - return goog.array.clone(this.hints_); +ol.geom.Geometry.prototype.getExtent = function(opt_extent) { + if (this.extentRevision_ != this.getRevision()) { + this.extent_ = this.computeExtent(this.extent_); + this.extentRevision_ = this.getRevision(); + } + return ol.extent.returnOrUpdate(this.extent_, opt_extent); }; /** - * Calculate the extent for the current view state and the passed `size`. - * `size` is the size in pixels of the box into which the calculated extent - * should fit. In most cases you want to get the extent of the entire map, - * that is `map.getSize()`. - * @param {ol.Size} size Box pixel size. - * @return {ol.Extent} Extent. - * @api + * Create a simplified version of this geometry using the Douglas Peucker + * algorithm. + * @see http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm + * @function + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.Geometry} Simplified geometry. */ -ol.View.prototype.calculateExtent = function(size) { - goog.asserts.assert(this.isDef()); - var center = this.getCenter(); - var resolution = this.getResolution(); - var minX = center[0] - resolution * size[0] / 2; - var maxX = center[0] + resolution * size[0] / 2; - var minY = center[1] - resolution * size[1] / 2; - var maxY = center[1] + resolution * size[1] / 2; - return [minX, minY, maxX, maxY]; -}; +ol.geom.Geometry.prototype.getSimplifiedGeometry = goog.abstractMethod; /** - * @return {ol.proj.Projection} The projection of the view. - * @api stable + * Get the type of this geometry. + * @function + * @return {ol.geom.GeometryType} Geometry type. */ -ol.View.prototype.getProjection = function() { - return this.projection_; -}; +ol.geom.Geometry.prototype.getType = goog.abstractMethod; /** - * @return {number|undefined} The resolution of the view. - * @observable - * @api stable + * Apply a transform function to each coordinate of the geometry. + * The geometry is modified in place. + * If you do not want the geometry modified in place, first clone() it and + * then use this function on the clone. + * @function + * @param {ol.TransformFunction} transformFn Transform. */ -ol.View.prototype.getResolution = function() { - return /** @type {number|undefined} */ ( - this.get(ol.ViewProperty.RESOLUTION)); -}; -goog.exportProperty( - ol.View.prototype, - 'getResolution', - ol.View.prototype.getResolution); +ol.geom.Geometry.prototype.applyTransform = goog.abstractMethod; /** - * Get the resolution for a provided extent (in map units) and size (in pixels). + * Test if the geometry and the passed extent intersect. * @param {ol.Extent} extent Extent. - * @param {ol.Size} size Box pixel size. - * @return {number} The resolution at which the provided extent will render at - * the given size. - * @api + * @return {boolean} `true` if the geometry and the extent intersect. + * @function */ -ol.View.prototype.getResolutionForExtent = function(extent, size) { - var xResolution = ol.extent.getWidth(extent) / size[0]; - var yResolution = ol.extent.getHeight(extent) / size[1]; - return Math.max(xResolution, yResolution); -}; +ol.geom.Geometry.prototype.intersectsExtent = goog.abstractMethod; /** - * Return a function that returns a value between 0 and 1 for a - * resolution. Exponential scaling is assumed. - * @param {number=} opt_power Power. - * @return {function(number): number} Resolution for value function. + * Translate the geometry. This modifies the geometry coordinates in place. If + * instead you want a new geometry, first `clone()` this geometry. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @function */ -ol.View.prototype.getResolutionForValueFunction = function(opt_power) { - var power = opt_power || 2; - var maxResolution = this.maxResolution_; - var minResolution = this.minResolution_; - var max = Math.log(maxResolution / minResolution) / Math.log(power); - return ( - /** - * @param {number} value Value. - * @return {number} Resolution. - */ - function(value) { - var resolution = maxResolution / Math.pow(power, value * max); - goog.asserts.assert(resolution >= minResolution && - resolution <= maxResolution); - return resolution; - }); -}; +ol.geom.Geometry.prototype.translate = goog.abstractMethod; /** - * @return {number|undefined} The rotation of the view. - * @observable + * Transform each coordinate of the geometry from one coordinate reference + * system to another. The geometry is modified in place. + * For example, a line will be transformed to a line and a circle to a circle. + * If you do not want the geometry modified in place, first clone() it and + * then use this function on the clone. + * + * @param {ol.proj.ProjectionLike} source The current projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @param {ol.proj.ProjectionLike} destination The desired projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @return {ol.geom.Geometry} This geometry. Note that original geometry is + * modified in place. * @api stable */ -ol.View.prototype.getRotation = function() { - return /** @type {number|undefined} */ (this.get(ol.ViewProperty.ROTATION)); +ol.geom.Geometry.prototype.transform = function(source, destination) { + this.applyTransform(ol.proj.getTransform(source, destination)); + return this; }; -goog.exportProperty( - ol.View.prototype, - 'getRotation', - ol.View.prototype.getRotation); + +goog.provide('ol.geom.flat.transform'); + +goog.require('goog.vec.Mat4'); /** - * Return a function that returns a resolution for a value between - * 0 and 1. Exponential scaling is assumed. - * @param {number=} opt_power Power. - * @return {function(number): number} Value for resolution function. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. */ -ol.View.prototype.getValueForResolutionFunction = function(opt_power) { - var power = opt_power || 2; - var maxResolution = this.maxResolution_; - var minResolution = this.minResolution_; - var max = Math.log(maxResolution / minResolution) / Math.log(power); - return ( - /** - * @param {number} resolution Resolution. - * @return {number} Value. - */ - function(resolution) { - var value = - (Math.log(maxResolution / resolution) / Math.log(power)) / max; - goog.asserts.assert(value >= 0 && value <= 1); - return value; - }); +ol.geom.flat.transform.transform2D = + function(flatCoordinates, offset, end, stride, transform, opt_dest) { + var m00 = goog.vec.Mat4.getElement(transform, 0, 0); + var m10 = goog.vec.Mat4.getElement(transform, 1, 0); + var m01 = goog.vec.Mat4.getElement(transform, 0, 1); + var m11 = goog.vec.Mat4.getElement(transform, 1, 1); + var m03 = goog.vec.Mat4.getElement(transform, 0, 3); + var m13 = goog.vec.Mat4.getElement(transform, 1, 3); + var dest = goog.isDef(opt_dest) ? opt_dest : []; + var i = 0; + var j; + for (j = offset; j < end; j += stride) { + var x = flatCoordinates[j]; + var y = flatCoordinates[j + 1]; + dest[i++] = m00 * x + m01 * y + m03; + dest[i++] = m10 * x + m11 * y + m13; + } + if (goog.isDef(opt_dest) && dest.length != i) { + dest.length = i; + } + return dest; }; /** - * @return {olx.ViewState} View state. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed coordinates. */ -ol.View.prototype.getState = function() { - goog.asserts.assert(this.isDef()); - var center = /** @type {ol.Coordinate} */ (this.getCenter()); - var projection = this.getProjection(); - var resolution = /** @type {number} */ (this.getResolution()); - var rotation = this.getRotation(); - return /** @type {olx.ViewState} */ ({ - center: center.slice(), - projection: goog.isDef(projection) ? projection : null, - resolution: resolution, - rotation: goog.isDef(rotation) ? rotation : 0 - }); +ol.geom.flat.transform.translate = + function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) { + var dest = goog.isDef(opt_dest) ? opt_dest : []; + var i = 0; + var j, k; + for (j = offset; j < end; j += stride) { + dest[i++] = flatCoordinates[j] + deltaX; + dest[i++] = flatCoordinates[j + 1] + deltaY; + for (k = j + 2; k < j + stride; ++k) { + dest[i++] = flatCoordinates[k]; + } + } + if (goog.isDef(opt_dest) && dest.length != i) { + dest.length = i; + } + return dest; }; +goog.provide('ol.geom.SimpleGeometry'); + +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.object'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.flat.transform'); + + /** - * Get the current zoom level. Return undefined if the current - * resolution is undefined or not a "constrained resolution". - * @return {number|undefined} Zoom. + * @classdesc + * Abstract base class; only used for creating subclasses; do not instantiate + * in apps, as cannot be rendered. + * + * @constructor + * @extends {ol.geom.Geometry} * @api stable */ -ol.View.prototype.getZoom = function() { - var offset; - var resolution = this.getResolution(); +ol.geom.SimpleGeometry = function() { - if (goog.isDef(resolution)) { - var res, z = 0; - do { - res = this.constrainResolution(this.maxResolution_, z); - if (res == resolution) { - offset = z; - break; - } - ++z; - } while (res > this.minResolution_); - } + goog.base(this); - return goog.isDef(offset) ? this.minZoom_ + offset : offset; -}; + /** + * @protected + * @type {ol.geom.GeometryLayout} + */ + this.layout = ol.geom.GeometryLayout.XY; + /** + * @protected + * @type {number} + */ + this.stride = 2; + + /** + * @protected + * @type {Array.<number>} + */ + this.flatCoordinates = null; -/** - * Fit the map view to the passed `extent` and `size`. `size` is the size in - * pixels of the box to fit the extent into. In most cases you will want to - * use the map size, that is `map.getSize()`. - * @param {ol.Extent} extent Extent. - * @param {ol.Size} size Box pixel size. - * @api - */ -ol.View.prototype.fitExtent = function(extent, size) { - if (!ol.extent.isEmpty(extent)) { - this.setCenter(ol.extent.getCenter(extent)); - var resolution = this.getResolutionForExtent(extent, size); - var constrainedResolution = this.constrainResolution(resolution, 0, 0); - if (constrainedResolution < resolution) { - constrainedResolution = - this.constrainResolution(constrainedResolution, -1, 0); - } - this.setResolution(constrainedResolution); - } }; +goog.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry); /** - * Fit the given geometry based on the given map size and border. - * Take care on the map angle. - * @param {ol.geom.SimpleGeometry} geometry Geometry. - * @param {ol.Size} size Box pixel size. - * @param {olx.view.FitGeometryOptions=} opt_options Options. - * @api + * @param {number} stride Stride. + * @private + * @return {ol.geom.GeometryLayout} layout Layout. */ -ol.View.prototype.fitGeometry = function(geometry, size, opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - - var padding = goog.isDef(options.padding) ? options.padding : [0, 0, 0, 0]; - var constrainResolution = goog.isDef(options.constrainResolution) ? - options.constrainResolution : true; - var nearest = goog.isDef(options.nearest) ? options.nearest : false; - var minResolution; - if (goog.isDef(options.minResolution)) { - minResolution = options.minResolution; - } else if (goog.isDef(options.maxZoom)) { - minResolution = this.constrainResolution( - this.maxResolution_, options.maxZoom - this.minZoom_, 0); +ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) { + if (stride == 2) { + return ol.geom.GeometryLayout.XY; + } else if (stride == 3) { + return ol.geom.GeometryLayout.XYZ; + } else if (stride == 4) { + return ol.geom.GeometryLayout.XYZM; } else { - minResolution = 0; + goog.asserts.fail('unsupported stride: ' + stride); } - var coords = geometry.getFlatCoordinates(); +}; - // calculate rotated extent - var rotation = this.getRotation(); - goog.asserts.assert(goog.isDef(rotation)); - var cosAngle = Math.cos(-rotation); - var sinAngle = Math.sin(-rotation); - var minRotX = +Infinity; - var minRotY = +Infinity; - var maxRotX = -Infinity; - var maxRotY = -Infinity; - var stride = geometry.getStride(); - for (var i = 0, ii = coords.length; i < ii; i += stride) { - var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle; - var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle; - minRotX = Math.min(minRotX, rotX); - minRotY = Math.min(minRotY, rotY); - maxRotX = Math.max(maxRotX, rotX); - maxRotY = Math.max(maxRotY, rotY); - } - // calculate resolution - var resolution = this.getResolutionForExtent( - [minRotX, minRotY, maxRotX, maxRotY], - [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]); - resolution = isNaN(resolution) ? minResolution : - Math.max(resolution, minResolution); - if (constrainResolution) { - var constrainedResolution = this.constrainResolution(resolution, 0, 0); - if (!nearest && constrainedResolution < resolution) { - constrainedResolution = this.constrainResolution( - constrainedResolution, -1, 0); - } - resolution = constrainedResolution; +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @return {number} Stride. + */ +ol.geom.SimpleGeometry.getStrideForLayout = function(layout) { + if (layout == ol.geom.GeometryLayout.XY) { + return 2; + } else if (layout == ol.geom.GeometryLayout.XYZ) { + return 3; + } else if (layout == ol.geom.GeometryLayout.XYM) { + return 3; + } else if (layout == ol.geom.GeometryLayout.XYZM) { + return 4; + } else { + goog.asserts.fail('unsupported layout: ' + layout); } - this.setResolution(resolution); - - // calculate center - sinAngle = -sinAngle; // go back to original rotation - var centerRotX = (minRotX + maxRotX) / 2; - var centerRotY = (minRotY + maxRotY) / 2; - centerRotX += (padding[1] - padding[3]) / 2 * resolution; - centerRotY += (padding[0] - padding[2]) / 2 * resolution; - var centerX = centerRotX * cosAngle - centerRotY * sinAngle; - var centerY = centerRotY * cosAngle + centerRotX * sinAngle; - - this.setCenter([centerX, centerY]); }; /** - * Center on coordinate and view position. - * Take care on the map angle. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {ol.Size} size Box pixel size. - * @param {ol.Pixel} position Position on the view to center on. - * @api + * @inheritDoc */ -ol.View.prototype.centerOn = function(coordinate, size, position) { - // calculate rotated position - var rotation = this.getRotation(); - var cosAngle = Math.cos(-rotation); - var sinAngle = Math.sin(-rotation); - var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle; - var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle; - var resolution = this.getResolution(); - rotX += (size[0] / 2 - position[0]) * resolution; - rotY += (position[1] - size[1] / 2) * resolution; +ol.geom.SimpleGeometry.prototype.containsXY = goog.functions.FALSE; - // go back to original angle - sinAngle = -sinAngle; // go back to original rotation - var centerX = rotX * cosAngle - rotY * sinAngle; - var centerY = rotY * cosAngle + rotX * sinAngle; - this.setCenter([centerX, centerY]); +/** + * @inheritDoc + */ +ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) { + return ol.extent.createOrUpdateFromFlatCoordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + extent); }; /** - * @return {boolean} Is defined. + * @return {Array} Coordinates. */ -ol.View.prototype.isDef = function() { - return goog.isDefAndNotNull(this.getCenter()) && - goog.isDef(this.getResolution()); -}; +ol.geom.SimpleGeometry.prototype.getCoordinates = goog.abstractMethod; /** - * Rotate the view around a given coordinate. - * @param {number} rotation New rotation value for the view. - * @param {ol.Coordinate=} opt_anchor The rotation center. + * Return the first coordinate of the geometry. + * @return {ol.Coordinate} First coordinate. * @api stable */ -ol.View.prototype.rotate = function(rotation, opt_anchor) { - if (goog.isDef(opt_anchor)) { - var center = this.calculateCenterRotate(rotation, opt_anchor); - this.setCenter(center); - } - this.setRotation(rotation); +ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() { + return this.flatCoordinates.slice(0, this.stride); }; /** - * Set the center of the current view. - * @param {ol.Coordinate|undefined} center The center of the view. - * @observable - * @api stable + * @return {Array.<number>} Flat coordinates. */ -ol.View.prototype.setCenter = function(center) { - this.set(ol.ViewProperty.CENTER, center); +ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() { + return this.flatCoordinates; }; -goog.exportProperty( - ol.View.prototype, - 'setCenter', - ol.View.prototype.setCenter); /** - * @param {ol.ViewHint} hint Hint. - * @param {number} delta Delta. - * @return {number} New value. + * Return the last coordinate of the geometry. + * @return {ol.Coordinate} Last point. + * @api stable */ -ol.View.prototype.setHint = function(hint, delta) { - goog.asserts.assert(0 <= hint && hint < this.hints_.length); - this.hints_[hint] += delta; - goog.asserts.assert(this.hints_[hint] >= 0); - return this.hints_[hint]; +ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() { + return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride); }; /** - * Set the resolution for this view. - * @param {number|undefined} resolution The resolution of the view. - * @observable + * Return the {@link ol.geom.GeometryLayout layout} of the geometry. + * @return {ol.geom.GeometryLayout} Layout. * @api stable */ -ol.View.prototype.setResolution = function(resolution) { - this.set(ol.ViewProperty.RESOLUTION, resolution); +ol.geom.SimpleGeometry.prototype.getLayout = function() { + return this.layout; }; -goog.exportProperty( - ol.View.prototype, - 'setResolution', - ol.View.prototype.setResolution); /** - * Set the rotation for this view. - * @param {number|undefined} rotation The rotation of the view. - * @observable - * @api stable + * @inheritDoc */ -ol.View.prototype.setRotation = function(rotation) { - this.set(ol.ViewProperty.ROTATION, rotation); +ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = + function(squaredTolerance) { + if (this.simplifiedGeometryRevision != this.getRevision()) { + goog.object.clear(this.simplifiedGeometryCache); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.simplifiedGeometryRevision = this.getRevision(); + } + // If squaredTolerance is negative or if we know that simplification will not + // have any effect then just return this. + if (squaredTolerance < 0 || + (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && + squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) { + return this; + } + var key = squaredTolerance.toString(); + if (this.simplifiedGeometryCache.hasOwnProperty(key)) { + return this.simplifiedGeometryCache[key]; + } else { + var simplifiedGeometry = + this.getSimplifiedGeometryInternal(squaredTolerance); + var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates(); + if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) { + this.simplifiedGeometryCache[key] = simplifiedGeometry; + return simplifiedGeometry; + } else { + // Simplification did not actually remove any coordinates. We now know + // that any calls to getSimplifiedGeometry with a squaredTolerance less + // than or equal to the current squaredTolerance will also not have any + // effect. This allows us to short circuit simplification (saving CPU + // cycles) and prevents the cache of simplified geometries from filling + // up with useless identical copies of this geometry (saving memory). + this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; + return this; + } + } }; -goog.exportProperty( - ol.View.prototype, - 'setRotation', - ol.View.prototype.setRotation); /** - * Zoom to a specific zoom level. - * @param {number} zoom Zoom level. - * @api stable + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.SimpleGeometry} Simplified geometry. + * @protected */ -ol.View.prototype.setZoom = function(zoom) { - var resolution = this.constrainResolution( - this.maxResolution_, zoom - this.minZoom_, 0); - this.setResolution(resolution); +ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = + function(squaredTolerance) { + return this; }; /** - * @param {olx.ViewOptions} options View options. - * @private - * @return {ol.CenterConstraintType} + * @return {number} Stride. */ -ol.View.createCenterConstraint_ = function(options) { - if (goog.isDef(options.extent)) { - return ol.CenterConstraint.createExtent(options.extent); - } else { - return ol.CenterConstraint.none; - } +ol.geom.SimpleGeometry.prototype.getStride = function() { + return this.stride; }; /** - * @private - * @param {olx.ViewOptions} options View options. - * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number, - * minResolution: number}} + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @protected */ -ol.View.createResolutionConstraint_ = function(options) { - var resolutionConstraint; - var maxResolution; - var minResolution; - - // TODO: move these to be ol constants - // see https://github.com/openlayers/ol3/issues/2076 - var defaultMaxZoom = 28; - var defaultZoomFactor = 2; +ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = + function(layout, flatCoordinates) { + this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout); + this.layout = layout; + this.flatCoordinates = flatCoordinates; +}; - var minZoom = goog.isDef(options.minZoom) ? - options.minZoom : ol.DEFAULT_MIN_ZOOM; - var maxZoom = goog.isDef(options.maxZoom) ? - options.maxZoom : defaultMaxZoom; +/** + * @param {Array} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + */ +ol.geom.SimpleGeometry.prototype.setCoordinates = goog.abstractMethod; - var zoomFactor = goog.isDef(options.zoomFactor) ? - options.zoomFactor : defaultZoomFactor; - if (goog.isDef(options.resolutions)) { - var resolutions = options.resolutions; - maxResolution = resolutions[0]; - minResolution = resolutions[resolutions.length - 1]; - resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions( - resolutions); +/** + * @param {ol.geom.GeometryLayout|undefined} layout Layout. + * @param {Array} coordinates Coordinates. + * @param {number} nesting Nesting. + * @protected + */ +ol.geom.SimpleGeometry.prototype.setLayout = + function(layout, coordinates, nesting) { + /** @type {number} */ + var stride; + if (goog.isDef(layout)) { + stride = ol.geom.SimpleGeometry.getStrideForLayout(layout); } else { - // calculate the default min and max resolution - var projection = ol.proj.createProjection(options.projection, 'EPSG:3857'); - var extent = projection.getExtent(); - var size = goog.isNull(extent) ? - // use an extent that can fit the whole world if need be - 360 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / - ol.proj.METERS_PER_UNIT[projection.getUnits()] : - Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent)); - - var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow( - defaultZoomFactor, ol.DEFAULT_MIN_ZOOM); - - var defaultMinResolution = defaultMaxResolution / Math.pow( - defaultZoomFactor, defaultMaxZoom - ol.DEFAULT_MIN_ZOOM); - - // user provided maxResolution takes precedence - maxResolution = options.maxResolution; - if (goog.isDef(maxResolution)) { - minZoom = 0; - } else { - maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom); - } - - // user provided minResolution takes precedence - minResolution = options.minResolution; - if (!goog.isDef(minResolution)) { - if (goog.isDef(options.maxZoom)) { - if (goog.isDef(options.maxResolution)) { - minResolution = maxResolution / Math.pow(zoomFactor, maxZoom); - } else { - minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom); - } + var i; + for (i = 0; i < nesting; ++i) { + if (coordinates.length === 0) { + this.layout = ol.geom.GeometryLayout.XY; + this.stride = 2; + return; } else { - minResolution = defaultMinResolution; + coordinates = /** @type {Array} */ (coordinates[0]); } } + stride = (/** @type {Array} */ (coordinates)).length; + layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride); + } + this.layout = layout; + this.stride = stride; +}; - // given discrete zoom levels, minResolution may be different than provided - maxZoom = minZoom + Math.floor( - Math.log(maxResolution / minResolution) / Math.log(zoomFactor)); - minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom); - resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( - zoomFactor, maxResolution, maxZoom - minZoom); +/** + * @inheritDoc + * @api stable + */ +ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) { + if (!goog.isNull(this.flatCoordinates)) { + transformFn(this.flatCoordinates, this.flatCoordinates, this.stride); + this.changed(); } - return {constraint: resolutionConstraint, maxResolution: maxResolution, - minResolution: minResolution, minZoom: minZoom}; }; /** - * @private - * @param {olx.ViewOptions} options View options. - * @return {ol.RotationConstraintType} Rotation constraint. + * @inheritDoc + * @api stable */ -ol.View.createRotationConstraint_ = function(options) { - var enableRotation = goog.isDef(options.enableRotation) ? - options.enableRotation : true; - if (enableRotation) { - var constrainRotation = options.constrainRotation; - if (!goog.isDef(constrainRotation) || constrainRotation === true) { - return ol.RotationConstraint.createSnapToZero(); - } else if (constrainRotation === false) { - return ol.RotationConstraint.none; - } else if (goog.isNumber(constrainRotation)) { - return ol.RotationConstraint.createSnapToN(constrainRotation); - } else { - goog.asserts.fail(); - return ol.RotationConstraint.none; - } - } else { - return ol.RotationConstraint.disable; +ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) { + var flatCoordinates = this.getFlatCoordinates(); + if (!goog.isNull(flatCoordinates)) { + var stride = this.getStride(); + ol.geom.flat.transform.translate( + flatCoordinates, 0, flatCoordinates.length, stride, + deltaX, deltaY, flatCoordinates); + this.changed(); } }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Easing functions for animations. - * - * @author arv@google.com (Erik Arvidsson) + * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Transformed flat coordinates. */ +ol.geom.transformSimpleGeometry2D = + function(simpleGeometry, transform, opt_dest) { + var flatCoordinates = simpleGeometry.getFlatCoordinates(); + if (goog.isNull(flatCoordinates)) { + return null; + } else { + var stride = simpleGeometry.getStride(); + return ol.geom.flat.transform.transform2D( + flatCoordinates, 0, flatCoordinates.length, stride, + transform, opt_dest); + } +}; -goog.provide('goog.fx.easing'); +goog.provide('ol.geom.flat.area'); /** - * Ease in - Start slow and speed up. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Area. */ -goog.fx.easing.easeIn = function(t) { - return goog.fx.easing.easeInInternal_(t, 3); +ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) { + var twiceArea = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + twiceArea += y1 * x2 - x1 * y2; + x1 = x2; + y1 = y2; + } + return twiceArea / 2; }; /** - * Ease in with specifiable exponent. - * @param {number} t Input between 0 and 1. - * @param {number} exp Ease exponent. - * @return {number} Output between 0 and 1. - * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @return {number} Area. */ -goog.fx.easing.easeInInternal_ = function(t, exp) { - return Math.pow(t, exp); +ol.geom.flat.area.linearRings = + function(flatCoordinates, offset, ends, stride) { + var area = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); + offset = end; + } + return area; }; /** - * Ease out - Start fastest and slows to a stop. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @return {number} Area. */ -goog.fx.easing.easeOut = function(t) { - return goog.fx.easing.easeOutInternal_(t, 3); +ol.geom.flat.area.linearRingss = + function(flatCoordinates, offset, endss, stride) { + var area = 0; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + area += + ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride); + offset = ends[ends.length - 1]; + } + return area; }; +goog.provide('ol.math'); + +goog.require('goog.asserts'); + /** - * Ease out with specifiable exponent. - * @param {number} t Input between 0 and 1. - * @param {number} exp Ease exponent. - * @return {number} Output between 0 and 1. - * @private + * @param {number} x X. + * @return {number} Hyperbolic cosine of x. */ -goog.fx.easing.easeOutInternal_ = function(t, exp) { - return 1 - goog.fx.easing.easeInInternal_(1 - t, exp); +ol.math.cosh = function(x) { + return (Math.exp(x) + Math.exp(-x)) / 2; }; /** - * Ease out long - Start fastest and slows to a stop with a long ease. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. + * @param {number} x X. + * @return {number} Hyperbolic cotangent of x. */ -goog.fx.easing.easeOutLong = function(t) { - return goog.fx.easing.easeOutInternal_(t, 4); +ol.math.coth = function(x) { + var expMinusTwoX = Math.exp(-2 * x); + return (1 + expMinusTwoX) / (1 - expMinusTwoX); }; /** - * Ease in and out - Start slow, speed up, then slow down. - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. + * @param {number} x X. + * @return {number} Hyperbolic cosecant of x. */ -goog.fx.easing.inAndOut = function(t) { - return 3 * t * t - 2 * t * t * t; +ol.math.csch = function(x) { + return 2 / (Math.exp(x) - Math.exp(-x)); }; -goog.provide('ol.easing'); -goog.require('goog.fx.easing'); +/** + * @param {number} x X. + * @return {number} The smallest power of two greater than or equal to x. + */ +ol.math.roundUpToPowerOfTwo = function(x) { + goog.asserts.assert(0 < x, 'x should be larger than 0'); + return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2)); +}; /** - * @function - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api - */ -ol.easing.easeIn = goog.fx.easing.easeIn; - - -/** - * @function - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api - */ -ol.easing.easeOut = goog.fx.easing.easeOut; - - -/** - * @function - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api + * @param {number} x X. + * @return {number} Hyperbolic secant of x. */ -ol.easing.inAndOut = goog.fx.easing.inAndOut; +ol.math.sech = function(x) { + return 2 / (Math.exp(x) + Math.exp(-x)); +}; /** - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api + * @param {number} x X. + * @return {number} Hyperbolic sine of x. */ -ol.easing.linear = function(t) { - return t; +ol.math.sinh = function(x) { + return (Math.exp(x) - Math.exp(-x)) / 2; }; /** - * @param {number} t Input between 0 and 1. - * @return {number} Output between 0 and 1. - * @api + * Returns the square of the closest distance between the point (x, y) and the + * line segment (x1, y1) to (x2, y2). + * @param {number} x X. + * @param {number} y Y. + * @param {number} x1 X1. + * @param {number} y1 Y1. + * @param {number} x2 X2. + * @param {number} y2 Y2. + * @return {number} Squared distance. */ -ol.easing.upAndDown = function(t) { - if (t < 0.5) { - return ol.easing.inAndOut(2 * t); - } else { - return 1 - ol.easing.inAndOut(2 * (t - 0.5)); +ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) { + var dx = x2 - x1; + var dy = y2 - y1; + if (dx !== 0 || dy !== 0) { + var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); + if (t > 1) { + x1 = x2; + y1 = y2; + } else if (t > 0) { + x1 += dx * t; + y1 += dy * t; + } } + return ol.math.squaredDistance(x, y, x1, y1); }; -goog.provide('ol.animation'); - -goog.require('ol.PreRenderFunction'); -goog.require('ol.ViewHint'); -goog.require('ol.coordinate'); -goog.require('ol.easing'); - /** - * @param {olx.animation.BounceOptions} options Bounce options. - * @return {ol.PreRenderFunction} Pre-render function. - * @api + * Returns the square of the distance between the points (x1, y1) and (x2, y2). + * @param {number} x1 X1. + * @param {number} y1 Y1. + * @param {number} x2 X2. + * @param {number} y2 Y2. + * @return {number} Squared distance. */ -ol.animation.bounce = function(options) { - var resolution = options.resolution; - var start = goog.isDef(options.start) ? options.start : goog.now(); - var duration = goog.isDef(options.duration) ? options.duration : 1000; - var easing = goog.isDef(options.easing) ? - options.easing : ol.easing.upAndDown; - return ( - /** - * @param {ol.Map} map Map. - * @param {?olx.FrameState} frameState Frame state. - */ - function(map, frameState) { - if (frameState.time < start) { - frameState.animate = true; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else if (frameState.time < start + duration) { - var delta = easing((frameState.time - start) / duration); - var deltaResolution = resolution - frameState.viewState.resolution; - frameState.animate = true; - frameState.viewState.resolution += delta * deltaResolution; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else { - return false; - } - }); +ol.math.squaredDistance = function(x1, y1, x2, y2) { + var dx = x2 - x1; + var dy = y2 - y1; + return dx * dx + dy * dy; }; /** - * @param {olx.animation.PanOptions} options Pan options. - * @return {ol.PreRenderFunction} Pre-render function. - * @api + * @param {number} x X. + * @return {number} Hyperbolic tangent of x. */ -ol.animation.pan = function(options) { - var source = options.source; - var start = goog.isDef(options.start) ? options.start : goog.now(); - var sourceX = source[0]; - var sourceY = source[1]; - var duration = goog.isDef(options.duration) ? options.duration : 1000; - var easing = goog.isDef(options.easing) ? - options.easing : ol.easing.inAndOut; - return ( - /** - * @param {ol.Map} map Map. - * @param {?olx.FrameState} frameState Frame state. - */ - function(map, frameState) { - if (frameState.time < start) { - frameState.animate = true; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else if (frameState.time < start + duration) { - var delta = 1 - easing((frameState.time - start) / duration); - var deltaX = sourceX - frameState.viewState.center[0]; - var deltaY = sourceY - frameState.viewState.center[1]; - frameState.animate = true; - frameState.viewState.center[0] += delta * deltaX; - frameState.viewState.center[1] += delta * deltaY; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else { - return false; - } - }); +ol.math.tanh = function(x) { + var expMinusTwoX = Math.exp(-2 * x); + return (1 - expMinusTwoX) / (1 + expMinusTwoX); }; +goog.provide('ol.geom.flat.closest'); -/** - * @param {olx.animation.RotateOptions} options Rotate options. - * @return {ol.PreRenderFunction} Pre-render function. - * @api - */ -ol.animation.rotate = function(options) { - var sourceRotation = goog.isDef(options.rotation) ? options.rotation : 0; - var start = goog.isDef(options.start) ? options.start : goog.now(); - var duration = goog.isDef(options.duration) ? options.duration : 1000; - var easing = goog.isDef(options.easing) ? - options.easing : ol.easing.inAndOut; - var anchor = goog.isDef(options.anchor) ? - options.anchor : null; - - return ( - /** - * @param {ol.Map} map Map. - * @param {?olx.FrameState} frameState Frame state. - */ - function(map, frameState) { - if (frameState.time < start) { - frameState.animate = true; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else if (frameState.time < start + duration) { - var delta = 1 - easing((frameState.time - start) / duration); - var deltaRotation = - (sourceRotation - frameState.viewState.rotation) * delta; - frameState.animate = true; - frameState.viewState.rotation += deltaRotation; - if (!goog.isNull(anchor)) { - var center = frameState.viewState.center; - ol.coordinate.sub(center, anchor); - ol.coordinate.rotate(center, deltaRotation); - ol.coordinate.add(center, anchor); - } - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else { - return false; - } - }); -}; +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('ol.math'); /** - * @param {olx.animation.ZoomOptions} options Zoom options. - * @return {ol.PreRenderFunction} Pre-render function. - * @api + * Returns the point on the 2D line segment flatCoordinates[offset1] to + * flatCoordinates[offset2] that is closest to the point (x, y). Extra + * dimensions are linearly interpolated. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset1 Offset 1. + * @param {number} offset2 Offset 2. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. */ -ol.animation.zoom = function(options) { - var sourceResolution = options.resolution; - var start = goog.isDef(options.start) ? options.start : goog.now(); - var duration = goog.isDef(options.duration) ? options.duration : 1000; - var easing = goog.isDef(options.easing) ? - options.easing : ol.easing.inAndOut; - return ( - /** - * @param {ol.Map} map Map. - * @param {?olx.FrameState} frameState Frame state. - */ - function(map, frameState) { - if (frameState.time < start) { - frameState.animate = true; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else if (frameState.time < start + duration) { - var delta = 1 - easing((frameState.time - start) / duration); - var deltaResolution = - sourceResolution - frameState.viewState.resolution; - frameState.animate = true; - frameState.viewState.resolution += delta * deltaResolution; - frameState.viewHints[ol.ViewHint.ANIMATING] += 1; - return true; - } else { - return false; - } - }); +ol.geom.flat.closest.point = + function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) { + var x1 = flatCoordinates[offset1]; + var y1 = flatCoordinates[offset1 + 1]; + var dx = flatCoordinates[offset2] - x1; + var dy = flatCoordinates[offset2 + 1] - y1; + var i, offset; + if (dx === 0 && dy === 0) { + offset = offset1; + } else { + var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); + if (t > 1) { + offset = offset2; + } else if (t > 0) { + for (i = 0; i < stride; ++i) { + closestPoint[i] = goog.math.lerp(flatCoordinates[offset1 + i], + flatCoordinates[offset2 + i], t); + } + closestPoint.length = stride; + return; + } else { + offset = offset1; + } + } + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[offset + i]; + } + closestPoint.length = stride; }; -goog.provide('ol.TileCoord'); -goog.provide('ol.tilecoord'); - -goog.require('goog.array'); -goog.require('goog.asserts'); - /** - * An array of three numbers representing the location of a tile in a tile - * grid. The order is `z`, `x`, and `y`. `z` is the zoom level. - * @typedef {Array.<number>} ol.TileCoord - * @api + * Return the squared of the largest distance between any pair of consecutive + * coordinates. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. */ -ol.TileCoord; +ol.geom.flat.closest.getMaxSquaredDelta = + function(flatCoordinates, offset, end, stride, maxSquaredDelta) { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + for (offset += stride; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + var squaredDelta = ol.math.squaredDistance(x1, y1, x2, y2); + if (squaredDelta > maxSquaredDelta) { + maxSquaredDelta = squaredDelta; + } + x1 = x2; + y1 = y2; + } + return maxSquaredDelta; +}; /** - * @enum {number} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. */ -ol.QuadKeyCharCode = { - ZERO: '0'.charCodeAt(0), - ONE: '1'.charCodeAt(0), - TWO: '2'.charCodeAt(0), - THREE: '3'.charCodeAt(0) +ol.geom.flat.closest.getsMaxSquaredDelta = + function(flatCoordinates, offset, ends, stride, maxSquaredDelta) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta( + flatCoordinates, offset, end, stride, maxSquaredDelta); + offset = end; + } + return maxSquaredDelta; }; /** - * @param {string} quadKey Quad key. - * @return {ol.TileCoord} Tile coordinate. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} maxSquaredDelta Max squared delta. + * @return {number} Max squared delta. */ -ol.tilecoord.createFromQuadKey = function(quadKey) { - var z = quadKey.length, x = 0, y = 0; - var mask = 1 << (z - 1); - var i; - for (i = 0; i < z; ++i) { - switch (quadKey.charCodeAt(i)) { - case ol.QuadKeyCharCode.ONE: - x += mask; - break; - case ol.QuadKeyCharCode.TWO: - y += mask; - break; - case ol.QuadKeyCharCode.THREE: - x += mask; - y += mask; - break; - } - mask >>= 1; +ol.geom.flat.closest.getssMaxSquaredDelta = + function(flatCoordinates, offset, endss, stride, maxSquaredDelta) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta( + flatCoordinates, offset, ends, stride, maxSquaredDelta); + offset = ends[ends.length - 1]; } - return [z, x, y]; + return maxSquaredDelta; }; /** - * @param {string} str String that follows pattern “z/x/y” where x, y and z are - * numbers. - * @return {ol.TileCoord} Tile coord. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. + * @param {number} x X. + * @param {number} y Y. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. */ -ol.tilecoord.createFromString = function(str) { - var v = str.split('/'); - goog.asserts.assert(v.length === 3); - v = goog.array.map(v, function(e, i, a) { - return parseInt(e, 10); - }); - return v; +ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end, + stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + if (offset == end) { + return minSquaredDistance; + } + var i, squaredDistance; + if (maxDelta === 0) { + // All points are identical, so just test the first point. + squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[offset], flatCoordinates[offset + 1]); + if (squaredDistance < minSquaredDistance) { + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[offset + i]; + } + closestPoint.length = stride; + return squaredDistance; + } else { + return minSquaredDistance; + } + } + goog.asserts.assert(maxDelta > 0, 'maxDelta should be larger than 0'); + var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN]; + var index = offset + stride; + while (index < end) { + ol.geom.flat.closest.point( + flatCoordinates, index - stride, index, stride, x, y, tmpPoint); + squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (i = 0; i < stride; ++i) { + closestPoint[i] = tmpPoint[i]; + } + closestPoint.length = stride; + index += stride; + } else { + // Skip ahead multiple points, because we know that all the skipped + // points cannot be any closer than the closest point we have found so + // far. We know this because we know how close the current point is, how + // close the closest point we have found so far is, and the maximum + // distance between consecutive points. For example, if we're currently + // at distance 10, the best we've found so far is 3, and that the maximum + // distance between consecutive points is 2, then we'll need to skip at + // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of + // finding a closer point. We use Math.max(..., 1) to ensure that we + // always advance at least one point, to avoid an infinite loop. + index += stride * Math.max( + ((Math.sqrt(squaredDistance) - + Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1); + } + } + if (isRing) { + // Check the closing segment. + ol.geom.flat.closest.point( + flatCoordinates, end - stride, offset, stride, x, y, tmpPoint); + squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (i = 0; i < stride; ++i) { + closestPoint[i] = tmpPoint[i]; + } + closestPoint.length = stride; + } + } + return minSquaredDistance; }; /** - * @param {number} z Z. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. * @param {number} x X. * @param {number} y Y. - * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. - * @return {ol.TileCoord} Tile coordinate. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. */ -ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) { - if (goog.isDef(opt_tileCoord)) { - opt_tileCoord[0] = z; - opt_tileCoord[1] = x; - opt_tileCoord[2] = y; - return opt_tileCoord; - } else { - return [z, x, y]; +ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends, + stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN]; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + minSquaredDistance = ol.geom.flat.closest.getClosestPoint( + flatCoordinates, offset, end, stride, + maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); + offset = end; } + return minSquaredDistance; }; /** - * @param {number} z Z. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} maxDelta Max delta. + * @param {boolean} isRing Is ring. * @param {number} x X. * @param {number} y Y. - * @return {string} Key. + * @param {Array.<number>} closestPoint Closest point. + * @param {number} minSquaredDistance Minimum squared distance. + * @param {Array.<number>=} opt_tmpPoint Temporary point object. + * @return {number} Minimum squared distance. */ -ol.tilecoord.getKeyZXY = function(z, x, y) { - return z + '/' + x + '/' + y; +ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset, + endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, + opt_tmpPoint) { + var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN]; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + minSquaredDistance = ol.geom.flat.closest.getsClosestPoint( + flatCoordinates, offset, ends, stride, + maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); + offset = ends[ends.length - 1]; + } + return minSquaredDistance; }; +goog.provide('ol.geom.flat.deflate'); + +goog.require('goog.asserts'); + /** - * @param {ol.TileCoord} tileCoord Tile coord. - * @return {number} Hash. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} stride Stride. + * @return {number} offset Offset. */ -ol.tilecoord.hash = function(tileCoord) { - return (tileCoord[1] << tileCoord[0]) + tileCoord[2]; +ol.geom.flat.deflate.coordinate = + function(flatCoordinates, offset, coordinate, stride) { + goog.asserts.assert(coordinate.length == stride, + 'length of the coordinate array should match stride'); + var i, ii; + for (i = 0, ii = coordinate.length; i < ii; ++i) { + flatCoordinates[offset++] = coordinate[i]; + } + return offset; }; /** - * @param {ol.TileCoord} tileCoord Tile coord. - * @return {string} Quad key. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {number} stride Stride. + * @return {number} offset Offset. */ -ol.tilecoord.quadKey = function(tileCoord) { - var z = tileCoord[0]; - var digits = new Array(z); - var mask = 1 << (z - 1); - var i, charCode; - for (i = 0; i < z; ++i) { - charCode = ol.QuadKeyCharCode.ZERO; - if (tileCoord[1] & mask) { - charCode += 1; - } - if (tileCoord[2] & mask) { - charCode += 2; +ol.geom.flat.deflate.coordinates = + function(flatCoordinates, offset, coordinates, stride) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + var coordinate = coordinates[i]; + goog.asserts.assert(coordinate.length == stride, + 'length of coordinate array should match stride'); + var j; + for (j = 0; j < stride; ++j) { + flatCoordinates[offset++] = coordinate[j]; } - digits[i] = String.fromCharCode(charCode); - mask >>= 1; } - return digits.join(''); + return offset; }; /** - * @param {ol.TileCoord} tileCoord Tile coord. - * @return {string} String. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess. + * @param {number} stride Stride. + * @param {Array.<number>=} opt_ends Ends. + * @return {Array.<number>} Ends. */ -ol.tilecoord.toString = function(tileCoord) { - return ol.tilecoord.getKeyZXY(tileCoord[0], tileCoord[1], tileCoord[2]); +ol.geom.flat.deflate.coordinatess = + function(flatCoordinates, offset, coordinatess, stride, opt_ends) { + var ends = goog.isDef(opt_ends) ? opt_ends : []; + var i = 0; + var j, jj; + for (j = 0, jj = coordinatess.length; j < jj; ++j) { + var end = ol.geom.flat.deflate.coordinates( + flatCoordinates, offset, coordinatess[j], stride); + ends[i++] = end; + offset = end; + } + ends.length = i; + return ends; }; -goog.provide('ol.TileRange'); - -goog.require('goog.asserts'); -goog.require('ol.Size'); -goog.require('ol.TileCoord'); - - /** - * A representation of a contiguous block of tiles. A tile range is specified - * by its min/max tile coordinates and is inclusive of coordinates. - * - * @constructor - * @param {number} minX Minimum X. - * @param {number} maxX Maximum X. - * @param {number} minY Minimum Y. - * @param {number} maxY Maximum Y. - * @struct + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss. + * @param {number} stride Stride. + * @param {Array.<Array.<number>>=} opt_endss Endss. + * @return {Array.<Array.<number>>} Endss. */ -ol.TileRange = function(minX, maxX, minY, maxY) { - - /** - * @type {number} - */ - this.minX = minX; - - /** - * @type {number} - */ - this.maxX = maxX; - - /** - * @type {number} - */ - this.minY = minY; - - /** - * @type {number} - */ - this.maxY = maxY; - +ol.geom.flat.deflate.coordinatesss = + function(flatCoordinates, offset, coordinatesss, stride, opt_endss) { + var endss = goog.isDef(opt_endss) ? opt_endss : []; + var i = 0; + var j, jj; + for (j = 0, jj = coordinatesss.length; j < jj; ++j) { + var ends = ol.geom.flat.deflate.coordinatess( + flatCoordinates, offset, coordinatesss[j], stride, endss[i]); + endss[i++] = ends; + offset = ends[ends.length - 1]; + } + endss.length = i; + return endss; }; +goog.provide('ol.geom.flat.inflate'); + /** - * @param {...ol.TileCoord} var_args Tile coordinates. - * @return {!ol.TileRange} Bounding tile box. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates. + * @return {Array.<ol.Coordinate>} Coordinates. */ -ol.TileRange.boundingTileRange = function(var_args) { - var tileCoord0 = /** @type {ol.TileCoord} */ (arguments[0]); - var tileCoord0Z = tileCoord0[0]; - var tileCoord0X = tileCoord0[1]; - var tileCoord0Y = tileCoord0[2]; - var tileRange = new ol.TileRange(tileCoord0X, tileCoord0X, - tileCoord0Y, tileCoord0Y); - var i, ii, tileCoord, tileCoordX, tileCoordY, tileCoordZ; - for (i = 1, ii = arguments.length; i < ii; ++i) { - tileCoord = /** @type {ol.TileCoord} */ (arguments[i]); - tileCoordZ = tileCoord[0]; - tileCoordX = tileCoord[1]; - tileCoordY = tileCoord[2]; - goog.asserts.assert(tileCoordZ == tileCoord0Z); - tileRange.minX = Math.min(tileRange.minX, tileCoordX); - tileRange.maxX = Math.max(tileRange.maxX, tileCoordX); - tileRange.minY = Math.min(tileRange.minY, tileCoordY); - tileRange.maxY = Math.max(tileRange.maxY, tileCoordY); +ol.geom.flat.inflate.coordinates = + function(flatCoordinates, offset, end, stride, opt_coordinates) { + var coordinates = goog.isDef(opt_coordinates) ? opt_coordinates : []; + var i = 0; + var j; + for (j = offset; j < end; j += stride) { + coordinates[i++] = flatCoordinates.slice(j, j + stride); } - return tileRange; + coordinates.length = i; + return coordinates; }; /** - * @param {number} minX Minimum X. - * @param {number} maxX Maximum X. - * @param {number} minY Minimum Y. - * @param {number} maxY Maximum Y. - * @param {ol.TileRange|undefined} tileRange TileRange. - * @return {ol.TileRange} Tile range. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess. + * @return {Array.<Array.<ol.Coordinate>>} Coordinatess. */ -ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) { - if (goog.isDef(tileRange)) { - tileRange.minX = minX; - tileRange.maxX = maxX; - tileRange.minY = minY; - tileRange.maxY = maxY; - return tileRange; - } else { - return new ol.TileRange(minX, maxX, minY, maxY); +ol.geom.flat.inflate.coordinatess = + function(flatCoordinates, offset, ends, stride, opt_coordinatess) { + var coordinatess = goog.isDef(opt_coordinatess) ? opt_coordinatess : []; + var i = 0; + var j, jj; + for (j = 0, jj = ends.length; j < jj; ++j) { + var end = ends[j]; + coordinatess[i++] = ol.geom.flat.inflate.coordinates( + flatCoordinates, offset, end, stride, coordinatess[i]); + offset = end; } + coordinatess.length = i; + return coordinatess; }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {boolean} Contains tile coordinate. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss + * Coordinatesss. + * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss. */ -ol.TileRange.prototype.contains = function(tileCoord) { - return this.containsXY(tileCoord[1], tileCoord[2]); +ol.geom.flat.inflate.coordinatesss = + function(flatCoordinates, offset, endss, stride, opt_coordinatesss) { + var coordinatesss = goog.isDef(opt_coordinatesss) ? opt_coordinatesss : []; + var i = 0; + var j, jj; + for (j = 0, jj = endss.length; j < jj; ++j) { + var ends = endss[j]; + coordinatesss[i++] = ol.geom.flat.inflate.coordinatess( + flatCoordinates, offset, ends, stride, coordinatesss[i]); + offset = ends[ends.length - 1]; + } + coordinatesss.length = i; + return coordinatesss; }; +// Based on simplify-js https://github.com/mourner/simplify-js +// Copyright (c) 2012, Vladimir Agafonkin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. -/** - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} Contains. - */ -ol.TileRange.prototype.containsTileRange = function(tileRange) { - return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX && - this.minY <= tileRange.minY && tileRange.maxY <= this.maxY; -}; - +goog.provide('ol.geom.flat.simplify'); -/** - * @param {number} x Tile coordinate x. - * @param {number} y Tile coordinate y. - * @return {boolean} Contains coordinate. - */ -ol.TileRange.prototype.containsXY = function(x, y) { - return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY; -}; +goog.require('ol.math'); /** - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} Equals. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {boolean} highQuality Highest quality. + * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat + * coordinates. + * @return {Array.<number>} Simplified line string. */ -ol.TileRange.prototype.equals = function(tileRange) { - return this.minX == tileRange.minX && this.minY == tileRange.minY && - this.maxX == tileRange.maxX && this.maxY == tileRange.maxY; +ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end, + stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) { + var simplifiedFlatCoordinates = goog.isDef(opt_simplifiedFlatCoordinates) ? + opt_simplifiedFlatCoordinates : []; + if (!highQuality) { + end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end, + stride, squaredTolerance, + simplifiedFlatCoordinates, 0); + flatCoordinates = simplifiedFlatCoordinates; + offset = 0; + stride = 2; + } + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + flatCoordinates, offset, end, stride, squaredTolerance, + simplifiedFlatCoordinates, 0); + return simplifiedFlatCoordinates; }; /** - * @param {ol.TileRange} tileRange Tile range. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. */ -ol.TileRange.prototype.extend = function(tileRange) { - if (tileRange.minX < this.minX) { - this.minX = tileRange.minX; - } - if (tileRange.maxX > this.maxX) { - this.maxX = tileRange.maxX; +ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end, + stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { + var n = (end - offset) / stride; + if (n < 3) { + for (; offset < end; offset += stride) { + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + 1]; + } + return simplifiedOffset; } - if (tileRange.minY < this.minY) { - this.minY = tileRange.minY; + /** @type {Array.<number>} */ + var markers = new Array(n); + markers[0] = 1; + markers[n - 1] = 1; + /** @type {Array.<number>} */ + var stack = [offset, end - stride]; + var index = 0; + var i; + while (stack.length > 0) { + var last = stack.pop(); + var first = stack.pop(); + var maxSquaredDistance = 0; + var x1 = flatCoordinates[first]; + var y1 = flatCoordinates[first + 1]; + var x2 = flatCoordinates[last]; + var y2 = flatCoordinates[last + 1]; + for (i = first + stride; i < last; i += stride) { + var x = flatCoordinates[i]; + var y = flatCoordinates[i + 1]; + var squaredDistance = ol.math.squaredSegmentDistance( + x, y, x1, y1, x2, y2); + if (squaredDistance > maxSquaredDistance) { + index = i; + maxSquaredDistance = squaredDistance; + } + } + if (maxSquaredDistance > squaredTolerance) { + markers[(index - offset) / stride] = 1; + if (first + stride < index) { + stack.push(first, index); + } + if (index + stride < last) { + stack.push(index, last); + } + } } - if (tileRange.maxY > this.maxY) { - this.maxY = tileRange.maxY; + for (i = 0; i < n; ++i) { + if (markers[i]) { + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + i * stride]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + i * stride + 1]; + } } + return simplifiedOffset; }; /** - * @return {number} Height. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<number>} simplifiedEnds Simplified ends. + * @return {number} Simplified offset. */ -ol.TileRange.prototype.getHeight = function() { - return this.maxY - this.minY + 1; +ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset, + ends, stride, squaredTolerance, simplifiedFlatCoordinates, + simplifiedOffset, simplifiedEnds) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + simplifiedOffset = ol.geom.flat.simplify.douglasPeucker( + flatCoordinates, offset, end, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset); + simplifiedEnds.push(simplifiedOffset); + offset = end; + } + return simplifiedOffset; }; /** - * @return {ol.Size} Size. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. + * @return {number} Simplified offset. */ -ol.TileRange.prototype.getSize = function() { - return [this.getWidth(), this.getHeight()]; +ol.geom.flat.simplify.douglasPeuckerss = function( + flatCoordinates, offset, endss, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + var simplifiedEnds = []; + simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers( + flatCoordinates, offset, ends, stride, squaredTolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); + simplifiedEndss.push(simplifiedEnds); + offset = ends[ends.length - 1]; + } + return simplifiedOffset; }; /** - * @return {number} Width. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} squaredTolerance Squared tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. */ -ol.TileRange.prototype.getWidth = function() { - return this.maxX - this.minX + 1; +ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end, + stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { + if (end <= offset + stride) { + // zero or one point, no simplification possible, so copy and return + for (; offset < end; offset += stride) { + simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset]; + simplifiedFlatCoordinates[simplifiedOffset++] = + flatCoordinates[offset + 1]; + } + return simplifiedOffset; + } + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + // copy first point + simplifiedFlatCoordinates[simplifiedOffset++] = x1; + simplifiedFlatCoordinates[simplifiedOffset++] = y1; + var x2 = x1; + var y2 = y1; + for (offset += stride; offset < end; offset += stride) { + x2 = flatCoordinates[offset]; + y2 = flatCoordinates[offset + 1]; + if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) { + // copy point at offset + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + x1 = x2; + y1 = y2; + } + } + if (x2 != x1 || y2 != y1) { + // copy last point + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + } + return simplifiedOffset; }; /** - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} Intersects. + * @param {number} value Value. + * @param {number} tolerance Tolerance. + * @return {number} Rounded value. */ -ol.TileRange.prototype.intersects = function(tileRange) { - return this.minX <= tileRange.maxX && - this.maxX >= tileRange.minX && - this.minY <= tileRange.maxY && - this.maxY >= tileRange.minY; +ol.geom.flat.simplify.snap = function(value, tolerance) { + return tolerance * Math.round(value / tolerance); }; -goog.provide('ol.Attribution'); - -goog.require('ol.TileRange'); - - /** - * @classdesc - * An attribution for a layer source. - * - * Example: - * - * source: new ol.source.OSM({ - * attributions: [ - * new ol.Attribution({ - * html: 'All maps © ' + - * '<a href="http://www.opencyclemap.org/">OpenCycleMap</a>' - * }), - * ol.source.OSM.ATTRIBUTION - * ], - * .. - * - * @constructor - * @param {olx.AttributionOptions} options Attribution options. - * @struct - * @api stable + * Simplifies a line string using an algorithm designed by Tim Schaub. + * Coordinates are snapped to the nearest value in a virtual grid and + * consecutive duplicate coordinates are discarded. This effectively preserves + * topology as the simplification of any subsection of a line string is + * independent of the rest of the line string. This means that, for examples, + * the common edge between two polygons will be simplified to the same line + * string independently in both polygons. This implementation uses a single + * pass over the coordinates and eliminates intermediate collinear points. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @return {number} Simplified offset. */ -ol.Attribution = function(options) { - - /** - * @private - * @type {string} - */ - this.html_ = options.html; - - /** - * @private - * @type {Object.<string, Array.<ol.TileRange>>} - */ - this.tileRanges_ = goog.isDef(options.tileRanges) ? - options.tileRanges : null; - +ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride, + tolerance, simplifiedFlatCoordinates, simplifiedOffset) { + // do nothing if the line is empty + if (offset == end) { + return simplifiedOffset; + } + // snap the first coordinate (P1) + var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + // add the first coordinate to the output + simplifiedFlatCoordinates[simplifiedOffset++] = x1; + simplifiedFlatCoordinates[simplifiedOffset++] = y1; + // find the next coordinate that does not snap to the same value as the first + // coordinate (P2) + var x2, y2; + do { + x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + if (offset == end) { + // all coordinates snap to the same value, the line collapses to a point + // push the last snapped value anyway to ensure that the output contains + // at least two points + // FIXME should we really return at least two points anyway? + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + return simplifiedOffset; + } + } while (x2 == x1 && y2 == y1); + while (offset < end) { + var x3, y3; + // snap the next coordinate (P3) + x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); + y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); + offset += stride; + // skip P3 if it is equal to P2 + if (x3 == x2 && y3 == y2) { + continue; + } + // calculate the delta between P1 and P2 + var dx1 = x2 - x1; + var dy1 = y2 - y1; + // calculate the delta between P3 and P1 + var dx2 = x3 - x1; + var dy2 = y3 - y1; + // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from + // P1 in the same direction then P2 is on the straight line between P1 and + // P3 + if ((dx1 * dy2 == dy1 * dx2) && + ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) && + ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) { + // discard P2 and set P2 = P3 + x2 = x3; + y2 = y3; + continue; + } + // either P1, P2, and P3 are not colinear, or they are colinear but P3 is + // between P3 and P1 or on the opposite half of the line to P2. add P2, + // and continue with P1 = P2 and P2 = P3 + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + x1 = x2; + y1 = y2; + x2 = x3; + y2 = y3; + } + // add the last point (P2) + simplifiedFlatCoordinates[simplifiedOffset++] = x2; + simplifiedFlatCoordinates[simplifiedOffset++] = y2; + return simplifiedOffset; }; /** - * @return {string} HTML. - * @api stable + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<number>} simplifiedEnds Simplified ends. + * @return {number} Simplified offset. */ -ol.Attribution.prototype.getHTML = function() { - return this.html_; +ol.geom.flat.simplify.quantizes = function( + flatCoordinates, offset, ends, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + simplifiedOffset = ol.geom.flat.simplify.quantize( + flatCoordinates, offset, end, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset); + simplifiedEnds.push(simplifiedOffset); + offset = end; + } + return simplifiedOffset; }; /** - * @param {Object.<string, ol.TileRange>} tileRanges Tile ranges. - * @return {boolean} Intersects any tile range. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} tolerance Tolerance. + * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat + * coordinates. + * @param {number} simplifiedOffset Simplified offset. + * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. + * @return {number} Simplified offset. */ -ol.Attribution.prototype.intersectsAnyTileRange = function(tileRanges) { - if (goog.isNull(this.tileRanges_)) { - return true; - } - var i, ii, tileRange, z; - for (z in tileRanges) { - if (!(z in this.tileRanges_)) { - continue; - } - tileRange = tileRanges[z]; - for (i = 0, ii = this.tileRanges_[z].length; i < ii; ++i) { - if (this.tileRanges_[z][i].intersects(tileRange)) { - return true; - } - } +ol.geom.flat.simplify.quantizess = function( + flatCoordinates, offset, endss, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + var simplifiedEnds = []; + simplifiedOffset = ol.geom.flat.simplify.quantizes( + flatCoordinates, offset, ends, stride, + tolerance, + simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); + simplifiedEndss.push(simplifiedEnds); + offset = ends[ends.length - 1]; } - return false; + return simplifiedOffset; }; -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Browser capability checks for the dom package. - * - */ - +goog.provide('ol.geom.LinearRing'); -goog.provide('goog.dom.BrowserFeature'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.simplify'); -goog.require('goog.userAgent'); /** - * Enum of browser capabilities. - * @enum {boolean} + * @classdesc + * Linear ring geometry. Only used as part of polygon; cannot be rendered + * on its own. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -goog.dom.BrowserFeature = { - /** - * Whether attributes 'name' and 'type' can be added to an element after it's - * created. False in Internet Explorer prior to version 9. - */ - CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE || - goog.userAgent.isDocumentModeOrHigher(9), +ol.geom.LinearRing = function(coordinates, opt_layout) { - /** - * Whether we can use element.children to access an element's Element - * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment - * nodes in the collection.) - */ - CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE || - goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) || - goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'), + goog.base(this); /** - * Opera, Safari 3, and Internet Explorer 9 all support innerText but they - * include text nodes in script and style tags. Not document-mode-dependent. + * @private + * @type {number} */ - CAN_USE_INNER_TEXT: ( - goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')), + this.maxDelta_ = -1; /** - * MSIE, Opera, and Safari>=4 support element.parentElement to access an - * element's parent if it is an Element. + * @private + * @type {number} */ - CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA || - goog.userAgent.WEBKIT, + this.maxDeltaRevision_ = -1; - /** - * Whether NoScope elements need a scoped element written before them in - * innerHTML. - * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1 - */ - INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE, + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); - /** - * Whether we use legacy IE range API. - */ - LEGACY_IE_RANGES: goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) }; +goog.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry); -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Defines the goog.dom.TagName enum. This enumerates - * all HTML tag names specified in either the the W3C HTML 4.01 index of - * elements or the HTML5 draft specification. - * - * References: - * http://www.w3.org/TR/html401/index/elements.html - * http://dev.w3.org/html5/spec/section-index.html - * + * Make a complete copy of the geometry. + * @return {!ol.geom.LinearRing} Clone. + * @api stable */ -goog.provide('goog.dom.TagName'); +ol.geom.LinearRing.prototype.clone = function() { + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return linearRing; +}; /** - * Enum of all html tag names specified by the W3C HTML4.01 and HTML5 - * specifications. - * @enum {string} + * @inheritDoc */ -goog.dom.TagName = { - A: 'A', - ABBR: 'ABBR', - ACRONYM: 'ACRONYM', - ADDRESS: 'ADDRESS', - APPLET: 'APPLET', - AREA: 'AREA', - ARTICLE: 'ARTICLE', - ASIDE: 'ASIDE', - AUDIO: 'AUDIO', - B: 'B', - BASE: 'BASE', - BASEFONT: 'BASEFONT', - BDI: 'BDI', - BDO: 'BDO', - BIG: 'BIG', - BLOCKQUOTE: 'BLOCKQUOTE', - BODY: 'BODY', - BR: 'BR', - BUTTON: 'BUTTON', - CANVAS: 'CANVAS', - CAPTION: 'CAPTION', - CENTER: 'CENTER', - CITE: 'CITE', - CODE: 'CODE', - COL: 'COL', - COLGROUP: 'COLGROUP', - COMMAND: 'COMMAND', - DATA: 'DATA', - DATALIST: 'DATALIST', - DD: 'DD', - DEL: 'DEL', - DETAILS: 'DETAILS', - DFN: 'DFN', - DIALOG: 'DIALOG', - DIR: 'DIR', - DIV: 'DIV', - DL: 'DL', - DT: 'DT', - EM: 'EM', - EMBED: 'EMBED', - FIELDSET: 'FIELDSET', - FIGCAPTION: 'FIGCAPTION', - FIGURE: 'FIGURE', - FONT: 'FONT', - FOOTER: 'FOOTER', - FORM: 'FORM', - FRAME: 'FRAME', - FRAMESET: 'FRAMESET', - H1: 'H1', - H2: 'H2', - H3: 'H3', - H4: 'H4', - H5: 'H5', - H6: 'H6', - HEAD: 'HEAD', - HEADER: 'HEADER', - HGROUP: 'HGROUP', - HR: 'HR', - HTML: 'HTML', - I: 'I', - IFRAME: 'IFRAME', - IMG: 'IMG', - INPUT: 'INPUT', - INS: 'INS', - ISINDEX: 'ISINDEX', - KBD: 'KBD', - KEYGEN: 'KEYGEN', - LABEL: 'LABEL', - LEGEND: 'LEGEND', - LI: 'LI', - LINK: 'LINK', - MAP: 'MAP', - MARK: 'MARK', - MATH: 'MATH', - MENU: 'MENU', - META: 'META', - METER: 'METER', - NAV: 'NAV', - NOFRAMES: 'NOFRAMES', - NOSCRIPT: 'NOSCRIPT', - OBJECT: 'OBJECT', - OL: 'OL', - OPTGROUP: 'OPTGROUP', - OPTION: 'OPTION', - OUTPUT: 'OUTPUT', - P: 'P', - PARAM: 'PARAM', - PRE: 'PRE', - PROGRESS: 'PROGRESS', - Q: 'Q', - RP: 'RP', - RT: 'RT', - RUBY: 'RUBY', - S: 'S', - SAMP: 'SAMP', - SCRIPT: 'SCRIPT', - SECTION: 'SECTION', - SELECT: 'SELECT', - SMALL: 'SMALL', - SOURCE: 'SOURCE', - SPAN: 'SPAN', - STRIKE: 'STRIKE', - STRONG: 'STRONG', - STYLE: 'STYLE', - SUB: 'SUB', - SUMMARY: 'SUMMARY', - SUP: 'SUP', - SVG: 'SVG', - TABLE: 'TABLE', - TBODY: 'TBODY', - TD: 'TD', - TEXTAREA: 'TEXTAREA', - TFOOT: 'TFOOT', - TH: 'TH', - THEAD: 'THEAD', - TIME: 'TIME', - TITLE: 'TITLE', - TR: 'TR', - TRACK: 'TRACK', - TT: 'TT', - U: 'U', - UL: 'UL', - VAR: 'VAR', - VIDEO: 'VIDEO', - WBR: 'WBR' +ol.geom.LinearRing.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getClosestPoint( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); }; -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for HTML element tag names. + * Return the area of the linear ring on projected plane. + * @return {number} Area (on projected plane). + * @api stable */ -goog.provide('goog.dom.tags'); - -goog.require('goog.object'); +ol.geom.LinearRing.prototype.getArea = function() { + return ol.geom.flat.area.linearRing( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; /** - * The void elements specified by - * http://www.w3.org/TR/html-markup/syntax.html#void-elements. - * @const - * @type {!Object} - * @private + * Return the coordinates of the linear ring. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable */ -goog.dom.tags.VOID_TAGS_ = goog.object.createSet(('area,base,br,col,command,' + - 'embed,hr,img,input,keygen,link,meta,param,source,track,wbr').split(',')); +ol.geom.LinearRing.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; /** - * Checks whether the tag is void (with no contents allowed and no legal end - * tag), for example 'br'. - * @param {string} tagName The tag name in lower case. - * @return {boolean} + * @inheritDoc */ -goog.dom.tags.isVoidTag = function(tagName) { - return goog.dom.tags.VOID_TAGS_[tagName] === true; +ol.geom.LinearRing.prototype.getSimplifiedGeometryInternal = + function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + squaredTolerance, simplifiedFlatCoordinates, 0); + var simplifiedLinearRing = new ol.geom.LinearRing(null); + simplifiedLinearRing.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); + return simplifiedLinearRing; }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -goog.provide('goog.string.TypedString'); - - /** - * Wrapper for strings that conform to a data type or language. - * - * Implementations of this interface are wrappers for strings, and typically - * associate a type contract with the wrapped string. Concrete implementations - * of this interface may choose to implement additional run-time type checking, - * see for example {@code goog.html.SafeHtml}. If available, client code that - * needs to ensure type membership of an object should use the type's function - * to assert type membership, such as {@code goog.html.SafeHtml.unwrap}. - * @interface + * @inheritDoc + * @api stable */ -goog.string.TypedString = function() {}; +ol.geom.LinearRing.prototype.getType = function() { + return ol.geom.GeometryType.LINEAR_RING; +}; /** - * Interface marker of the TypedString interface. - * - * This property can be used to determine at runtime whether or not an object - * implements this interface. All implementations of this interface set this - * property to {@code true}. - * @type {boolean} + * Set the coordinates of the linear ring. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -goog.string.TypedString.prototype.implementsGoogStringTypedString; +ol.geom.LinearRing.prototype.setCoordinates = + function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; /** - * Retrieves this wrapped string's value. - * @return {!string} The wrapped string's value. + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. */ -goog.string.TypedString.prototype.getTypedStringValue; - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +ol.geom.LinearRing.prototype.setFlatCoordinates = + function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; -goog.provide('goog.string.Const'); +goog.provide('ol.geom.Point'); -goog.require('goog.asserts'); -goog.require('goog.string.TypedString'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.math'); /** - * Wrapper for compile-time-constant strings. - * - * Const is a wrapper for strings that can only be created from program - * constants (i.e., string literals). This property relies on a custom Closure - * compiler check that {@code goog.string.Const.from} is only invoked on - * compile-time-constant expressions. - * - * Const is useful in APIs whose correct and secure use requires that certain - * arguments are not attacker controlled: Compile-time constants are inherently - * under the control of the application and not under control of external - * attackers, and hence are safe to use in such contexts. - * - * Instances of this type must be created via its factory method - * {@code goog.string.Const.from} and not by invoking its constructor. The - * constructor intentionally takes no parameters and the type is immutable; - * hence only a default instance corresponding to the empty string can be - * obtained via constructor invocation. + * @classdesc + * Point geometry. * - * @see goog.string.Const#from * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} + * @extends {ol.geom.SimpleGeometry} + * @param {ol.Coordinate} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -goog.string.Const = function() { - /** - * The wrapped value of this Const object. The field has a purposely ugly - * name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = ''; - - /** - * A type marker used to implement additional run-time type checking. - * @see goog.string.Const#unwrap - * @const - * @private - */ - this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ = - goog.string.Const.TYPE_MARKER_; +ol.geom.Point = function(coordinates, opt_layout) { + goog.base(this); + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); }; +goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry); /** - * @override - * @const + * Make a complete copy of the geometry. + * @return {!ol.geom.Point} Clone. + * @api stable */ -goog.string.Const.prototype.implementsGoogStringTypedString = true; +ol.geom.Point.prototype.clone = function() { + var point = new ol.geom.Point(null); + point.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return point; +}; /** - * Returns this Const's value a string. - * - * IMPORTANT: In code where it is security-relevant that an object's type is - * indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap} - * instead of this method. - * - * @see goog.string.Const#unwrap - * @override + * @inheritDoc */ -goog.string.Const.prototype.getTypedStringValue = function() { - return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_; +ol.geom.Point.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[0], flatCoordinates[1]); + if (squaredDistance < minSquaredDistance) { + var stride = this.stride; + var i; + for (i = 0; i < stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } + closestPoint.length = stride; + return squaredDistance; + } else { + return minSquaredDistance; + } }; /** - * Returns a debug-string representation of this value. - * - * To obtain the actual string value wrapped inside an object of this type, - * use {@code goog.string.Const.unwrap}. - * - * @see goog.string.Const#unwrap - * @override + * Return the coordinate of the point. + * @return {ol.Coordinate} Coordinates. + * @api stable */ -goog.string.Const.prototype.toString = function() { - return 'Const{' + - this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ + - '}'; +ol.geom.Point.prototype.getCoordinates = function() { + return goog.isNull(this.flatCoordinates) ? [] : this.flatCoordinates.slice(); }; /** - * Performs a runtime check that the provided object is indeed an instance - * of {@code goog.string.Const}, and returns its value. - * @param {!goog.string.Const} stringConst The object to extract from. - * @return {string} The Const object's contained string, unless the run-time - * type check fails. In that case, {@code unwrap} returns an innocuous - * string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. + * @inheritDoc */ -goog.string.Const.unwrap = function(stringConst) { - // Perform additional run-time type-checking to ensure that stringConst is - // indeed an instance of the expected type. This provides some additional - // protection against security bugs due to application code that disables type - // checks. - if (stringConst instanceof goog.string.Const && - stringConst.constructor === goog.string.Const && - stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ === - goog.string.Const.TYPE_MARKER_) { - return stringConst. - stringConstValueWithSecurityContract__googStringSecurityPrivate_; - } else { - goog.asserts.fail('expected object of type Const, got \'' + - stringConst + '\''); - return 'type_error:Const'; - } +ol.geom.Point.prototype.computeExtent = function(extent) { + return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent); }; /** - * Creates a Const object from a compile-time constant string. - * - * It is illegal to invoke this function on an expression whose - * compile-time-contant value cannot be determined by the Closure compiler. - * - * Correct invocations include, - * <pre> - * var s = goog.string.Const.from('hello'); - * var t = goog.string.Const.from('hello' + 'world'); - * </pre> - * - * In contrast, the following are illegal: - * <pre> - * var s = goog.string.Const.from(getHello()); - * var t = goog.string.Const.from('hello' + world); - * </pre> - * - * TODO(user): Compile-time checks that this function is only called - * with compile-time constant expressions. - * - * @param {string} s A constant string from which to create a Const. - * @return {!goog.string.Const} A Const object initialized to stringConst. + * @inheritDoc + * @api stable */ -goog.string.Const.from = function(s) { - return goog.string.Const.create__googStringSecurityPrivate_(s); +ol.geom.Point.prototype.getType = function() { + return ol.geom.GeometryType.POINT; }; /** - * Type marker for the Const type, used to implement additional run-time - * type checking. - * @const - * @private + * @inheritDoc + * @api stable */ -goog.string.Const.TYPE_MARKER_ = {}; +ol.geom.Point.prototype.intersectsExtent = function(extent) { + return ol.extent.containsXY(extent, + this.flatCoordinates[0], this.flatCoordinates[1]); +}; /** - * Utility method to create Const instances. - * @param {string} s The string to initialize the Const object with. - * @return {!goog.string.Const} The initialized Const object. - * @private + * Set the coordinate of the point. + * @param {ol.Coordinate} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -goog.string.Const.create__googStringSecurityPrivate_ = function(s) { - var stringConst = new goog.string.Const(); - stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = - s; - return stringConst; +ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 0); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinate( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } }; -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview The SafeStyle type and its builders. - * - * TODO(user): Link to document stating type contract. + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. */ +ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; -goog.provide('goog.html.SafeStyle'); +goog.provide('ol.geom.flat.contains'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - +goog.require('ol.extent'); /** - * A string-like object which represents a sequence of CSS declarations - * ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...}) - * and that carries the security type contract that its value, as a string, - * will not cause untrusted script execution (XSS) when evaluated as CSS in a - * browser. - * - * Instances of this type must be created via the factory methods - * ({@code goog.html.SafeStyle.create} or - * {@code goog.html.SafeStyle.fromConstant}) and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty string - * can be obtained via constructor invocation. - * - * A SafeStyle's string representation ({@link #getSafeStyleString()}) can - * safely: - * <ul> - * <li>Be interpolated as the entire content of a *quoted* HTML style - * attribute, or before already existing properties. The SafeStyle string - * *must be HTML-attribute-escaped* (where " and ' are escaped) before - * interpolation. - * <li>Be interpolated as the entire content of a {}-wrapped block within a - * stylesheet, or before already existing properties. The SafeStyle string - * should not be escaped before interpolation. SafeStyle's contract also - * guarantees that the string will not be able to introduce new properties - * or elide existing ones. - * <li>Be assigned to the style property of a DOM node. The SafeStyle string - * should not be escaped before being assigned to the property. - * </ul> - * - * A SafeStyle may never contain literal angle brackets. Otherwise, it could - * be unsafe to place a SafeStyle into a <style> tag (where it can't - * be HTML escaped). For example, if the SafeStyle containing - * "{@code font: 'foo <style/><script>evil</script>'}" were - * interpolated within a <style> tag, this would then break out of the - * style context into HTML. - * - * A SafeStyle may contain literal single or double quotes, and as such the - * entire style string must be escaped when used in a style attribute (if - * this were not the case, the string could contain a matching quote that - * would escape from the style attribute). - * - * Values of this type must be composable, i.e. for any two values - * {@code style1} and {@code style2} of this type, - * {@code goog.html.SafeStyle.unwrap(style1) + - * goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies - * the SafeStyle type constraint. This requirement implies that for any value - * {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must - * not end in a "property value" or "property name" context. For example, - * a value of {@code background:url("} or {@code font-} would not satisfy the - * SafeStyle contract. This is because concatenating such strings with a - * second value that itself does not contain unsafe CSS can result in an - * overall string that does. For example, if {@code javascript:evil())"} is - * appended to {@code background:url("}, the resulting string may result in - * the execution of a malicious script. - * - * TODO(user): Consider whether we should implement UTF-8 interchange - * validity checks and blacklisting of newlines (including Unicode ones) and - * other whitespace characters (\t, \f). Document here if so and also update - * SafeStyle.fromConstant(). - * - * The following example values comply with this type's contract: - * <ul> - * <li><pre>width: 1em;</pre> - * <li><pre>height:1em;</pre> - * <li><pre>width: 1em;height: 1em;</pre> - * <li><pre>background:url('http://url');</pre> - * </ul> - * In addition, the empty string is safe for use in a CSS attribute. - * - * The following example values do NOT comply with this type's contract: - * <ul> - * <li><pre>background: red</pre> (missing a trailing semi-colon) - * <li><pre>background:</pre> (missing a value and a trailing semi-colon) - * <li><pre>1em</pre> (missing an attribute name, which provides context for - * the value) - * </ul> - * - * @see goog.html.SafeStyle#create - * @see goog.html.SafeStyle#fromConstant - * @see http://www.w3.org/TR/css3-syntax/ - * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} Contains extent. */ -goog.html.SafeStyle = function() { - /** - * The contained value of this SafeStyle. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = ''; - - /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeStyle#unwrap - * @const - * @private - */ - this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +ol.geom.flat.contains.linearRingContainsExtent = + function(flatCoordinates, offset, end, stride, extent) { + var outside = ol.extent.forEachCorner(extent, + /** + * @param {ol.Coordinate} coordinate Coordinate. + */ + function(coordinate) { + return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates, + offset, end, stride, coordinate[0], coordinate[1]); + }); + return !outside; }; /** - * @override - * @const + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). */ -goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true; +ol.geom.flat.contains.linearRingContainsXY = + function(flatCoordinates, offset, end, stride, x, y) { + // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + var contains = false; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + var intersect = ((y1 > y) != (y2 > y)) && + (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1); + if (intersect) { + contains = !contains; + } + x1 = x2; + y1 = y2; + } + return contains; +}; /** - * Type marker for the SafeStyle type, used to implement additional - * run-time type checking. - * @const - * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). */ -goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +ol.geom.flat.contains.linearRingsContainsXY = + function(flatCoordinates, offset, ends, stride, x, y) { + goog.asserts.assert(ends.length > 0, 'ends should not be an empty array'); + if (ends.length === 0) { + return false; + } + if (!ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, ends[0], stride, x, y)) { + return false; + } + var i, ii; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, ends[i - 1], ends[i], stride, x, y)) { + return false; + } + } + return true; +}; /** - * Creates a SafeStyle object from a compile-time constant string. - * - * {@code style} should be in the format - * {@code name: value; [name: value; ...]} and must not have any < or > - * characters in it. This is so that SafeStyle's contract is preserved, - * allowing the SafeStyle to correctly be interpreted as a sequence of CSS - * declarations and without affecting the syntactic structure of any - * surrounding CSS and HTML. - * - * This method performs basic sanity checks on the format of {@code style} - * but does not constrain the format of {@code name} and {@code value}, except - * for disallowing tag characters. - * - * @param {!goog.string.Const} style A compile-time-constant string from which - * to create a SafeStyle. - * @return {!goog.html.SafeStyle} A SafeStyle object initialized to - * {@code style}. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {number} x X. + * @param {number} y Y. + * @return {boolean} Contains (x, y). */ -goog.html.SafeStyle.fromConstant = function(style) { - var styleString = goog.string.Const.unwrap(style); - if (styleString.length === 0) { - return goog.html.SafeStyle.EMPTY; +ol.geom.flat.contains.linearRingssContainsXY = + function(flatCoordinates, offset, endss, stride, x, y) { + goog.asserts.assert(endss.length > 0, 'endss should not be an empty array'); + if (endss.length === 0) { + return false; } - goog.html.SafeStyle.checkStyle_(styleString); - goog.asserts.assert(goog.string.endsWith(styleString, ';'), - 'Last character of style string is not \';\': ' + styleString); - goog.asserts.assert(goog.string.contains(styleString, ':'), - 'Style string must contain at least one \':\', to ' + - 'specify a "name: value" pair: ' + styleString); - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - styleString); + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + if (ol.geom.flat.contains.linearRingsContainsXY( + flatCoordinates, offset, ends, stride, x, y)) { + return true; + } + offset = ends[ends.length - 1]; + } + return false; }; +goog.provide('ol.geom.flat.interiorpoint'); -/** - * Checks if the style definition is valid. - * @param {string} style - * @private - */ -goog.html.SafeStyle.checkStyle_ = function(style) { - goog.asserts.assert(!/[<>]/.test(style), - 'Forbidden characters in style string: ' + style); -}; +goog.require('goog.asserts'); +goog.require('ol.geom.flat.contains'); /** - * Returns this SafeStyle's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of - * this method. If in doubt, assume that it's security relevant. In particular, - * note that goog.html functions which return a goog.html type do not guarantee - * the returned instance is of the right type. For example: - * - * <pre> - * var fakeSafeHtml = new String('fake'); - * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; - * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); - * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by - * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml - * // instanceof goog.html.SafeHtml. - * </pre> - * - * @see goog.html.SafeStyle#unwrap - * @override - */ -goog.html.SafeStyle.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeStyleWrappedValue_; + * Calculates a point that is likely to lie in the interior of the linear rings. + * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {Array.<number>} flatCenters Flat centers. + * @param {number} flatCentersOffset Flat center offset. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Destination. + */ +ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset, + ends, stride, flatCenters, flatCentersOffset, opt_dest) { + var i, ii, x, x1, x2, y1, y2; + var y = flatCenters[flatCentersOffset + 1]; + /** @type {Array.<number>} */ + var intersections = []; + // Calculate intersections with the horizontal line + var end = ends[0]; + x1 = flatCoordinates[end - stride]; + y1 = flatCoordinates[end - stride + 1]; + for (i = offset; i < end; i += stride) { + x2 = flatCoordinates[i]; + y2 = flatCoordinates[i + 1]; + if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) { + x = (y - y1) / (y2 - y1) * (x2 - x1) + x1; + intersections.push(x); + } + x1 = x2; + y1 = y2; + } + // Find the longest segment of the horizontal line that has its center point + // inside the linear ring. + var pointX = NaN; + var maxSegmentLength = -Infinity; + intersections.sort(); + x1 = intersections[0]; + for (i = 1, ii = intersections.length; i < ii; ++i) { + x2 = intersections[i]; + var segmentLength = Math.abs(x2 - x1); + if (segmentLength > maxSegmentLength) { + x = (x1 + x2) / 2; + if (ol.geom.flat.contains.linearRingsContainsXY( + flatCoordinates, offset, ends, stride, x, y)) { + pointX = x; + maxSegmentLength = segmentLength; + } + } + x1 = x2; + } + if (isNaN(pointX)) { + // There is no horizontal line that has its center point inside the linear + // ring. Use the center of the the linear ring's extent. + pointX = flatCenters[flatCentersOffset]; + } + if (goog.isDef(opt_dest)) { + opt_dest.push(pointX, y); + return opt_dest; + } else { + return [pointX, y]; + } }; -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeStyle, use - * {@code goog.html.SafeStyle.unwrap}. - * - * @see goog.html.SafeStyle#unwrap - * @override - */ - goog.html.SafeStyle.prototype.toString = function() { - return 'SafeStyle{' + - this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + '}'; - }; -} - - /** - * Performs a runtime check that the provided object is indeed a - * SafeStyle object, and returns its value. - * - * @param {!goog.html.SafeStyle} safeStyle The object to extract from. - * @return {string} The safeStyle object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {Array.<number>} flatCenters Flat centers. + * @return {Array.<number>} Interior points. */ -goog.html.SafeStyle.unwrap = function(safeStyle) { - // Perform additional Run-time type-checking to ensure that - // safeStyle is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeStyle instanceof goog.html.SafeStyle && - safeStyle.constructor === goog.html.SafeStyle && - safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_; - } else { - goog.asserts.fail( - 'expected object of type SafeStyle, got \'' + safeStyle + '\''); - return 'type_error:SafeStyle'; +ol.geom.flat.interiorpoint.linearRingss = + function(flatCoordinates, offset, endss, stride, flatCenters) { + goog.asserts.assert(2 * endss.length == flatCenters.length, + 'endss.length times 2 should be flatCenters.length'); + var interiorPoints = []; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates, + offset, ends, stride, flatCenters, 2 * i, interiorPoints); + offset = ends[ends.length - 1]; } + return interiorPoints; }; +goog.provide('ol.geom.flat.segments'); + /** - * Package-internal utility method to create SafeStyle instances. - * - * @param {string} style The string to initialize the SafeStyle object with. - * @return {!goog.html.SafeStyle} The initialized SafeStyle object. - * @package + * This function calls `callback` for each segment of the flat coordinates + * array. If the callback returns a truthy value the function returns that + * value immediately. Otherwise the function returns `false`. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function + * called for each segment. + * @param {S=} opt_this The object to be used as the value of 'this' + * within callback. + * @return {T|boolean} Value. + * @template T,S */ -goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse = - function(style) { - var safeStyle = new goog.html.SafeStyle(); - safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style; - return safeStyle; +ol.geom.flat.segments.forEach = + function(flatCoordinates, offset, end, stride, callback, opt_this) { + var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var point2 = []; + var ret; + for (; (offset + stride) < end; offset += stride) { + point2[0] = flatCoordinates[offset + stride]; + point2[1] = flatCoordinates[offset + stride + 1]; + ret = callback.call(opt_this, point1, point2); + if (ret) { + return ret; + } + point1[0] = point2[0]; + point1[1] = point2[1]; + } + return false; }; +goog.provide('ol.geom.flat.intersectsextent'); -/** - * A SafeStyle instance corresponding to the empty string. - * @const {!goog.html.SafeStyle} - */ -goog.html.SafeStyle.EMPTY = - goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(''); +goog.require('goog.asserts'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.segments'); /** - * The innocuous string generated by goog.html.SafeUrl.create when passed - * an unsafe value. - * @const {string} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. */ -goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez'; +ol.geom.flat.intersectsextent.lineString = + function(flatCoordinates, offset, end, stride, extent) { + var coordinatesExtent = ol.extent.extendFlatCoordinates( + ol.extent.createEmpty(), flatCoordinates, offset, end, stride); + if (!ol.extent.intersects(extent, coordinatesExtent)) { + return false; + } + if (ol.extent.containsExtent(extent, coordinatesExtent)) { + return true; + } + if (coordinatesExtent[0] >= extent[0] && + coordinatesExtent[2] <= extent[2]) { + return true; + } + if (coordinatesExtent[1] >= extent[1] && + coordinatesExtent[3] <= extent[3]) { + return true; + } + return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride, + /** + * @param {ol.Coordinate} point1 Start point. + * @param {ol.Coordinate} point2 End point. + * @return {boolean} `true` if the segment and the extent intersect, + * `false` otherwise. + */ + function(point1, point2) { + return ol.extent.intersectsSegment(extent, point1, point2); + }); +}; /** - * Mapping of property names to their values. - * @typedef {!Object<string, goog.string.Const|string>} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. */ -goog.html.SafeStyle.PropertyMap; +ol.geom.flat.intersectsextent.lineStrings = + function(flatCoordinates, offset, ends, stride, extent) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.intersectsextent.lineString( + flatCoordinates, offset, ends[i], stride, extent)) { + return true; + } + offset = ends[i]; + } + return false; +}; /** - * Creates a new SafeStyle object from the properties specified in the map. - * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to - * their values, for example {'margin': '1px'}. Names must consist of - * [-_a-zA-Z0-9]. Values might be strings consisting of [-.%_!# a-zA-Z0-9]. - * Other values must be wrapped in goog.string.Const. Null value causes - * skipping the property. - * @return {!goog.html.SafeStyle} - * @throws {Error} If invalid name is provided. - * @throws {goog.asserts.AssertionError} If invalid value is provided. With - * disabled assertions, invalid value is replaced by - * goog.html.SafeStyle.INNOCUOUS_STRING. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. */ -goog.html.SafeStyle.create = function(map) { - var style = ''; - for (var name in map) { - if (!/^[-_a-zA-Z0-9]+$/.test(name)) { - throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name); - } - var value = map[name]; - if (value == null) { - continue; - } - if (value instanceof goog.string.Const) { - value = goog.string.Const.unwrap(value); - // These characters can be used to change context and we don't want that - // even with const values. - goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].'); - } else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) { - goog.asserts.fail( - 'String value allows only [-.%_!# a-zA-Z0-9], got: ' + value); - value = goog.html.SafeStyle.INNOCUOUS_STRING; - } - style += name + ':' + value + ';'; +ol.geom.flat.intersectsextent.linearRing = + function(flatCoordinates, offset, end, stride, extent) { + if (ol.geom.flat.intersectsextent.lineString( + flatCoordinates, offset, end, stride, extent)) { + return true; } - if (!style) { - return goog.html.SafeStyle.EMPTY; + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[0], extent[1])) { + return true; } - goog.html.SafeStyle.checkStyle_(style); - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - style); + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[0], extent[3])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[2], extent[1])) { + return true; + } + if (ol.geom.flat.contains.linearRingContainsXY( + flatCoordinates, offset, end, stride, extent[2], extent[3])) { + return true; + } + return false; }; -// Keep in sync with the error string in create(). /** - * Regular expression for safe values. - * @const {!RegExp} - * @private + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. */ -goog.html.SafeStyle.VALUE_RE_ = /^[-.%_!# a-zA-Z0-9]+$/; +ol.geom.flat.intersectsextent.linearRings = + function(flatCoordinates, offset, ends, stride, extent) { + goog.asserts.assert(ends.length > 0, 'ends should not be an empty array'); + if (!ol.geom.flat.intersectsextent.linearRing( + flatCoordinates, offset, ends[0], stride, extent)) { + return false; + } + if (ends.length === 1) { + return true; + } + var i, ii; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ol.geom.flat.contains.linearRingContainsExtent( + flatCoordinates, ends[i - 1], ends[i], stride, extent)) { + return false; + } + } + return true; +}; /** - * Creates a new SafeStyle object by concatenating the values. - * @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args - * SafeStyles to concatenate. - * @return {!goog.html.SafeStyle} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @param {ol.Extent} extent Extent. + * @return {boolean} True if the geometry and the extent intersect. */ -goog.html.SafeStyle.concat = function(var_args) { - var style = ''; - - /** - * @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument - */ - var addArgument = function(argument) { - if (goog.isArray(argument)) { - goog.array.forEach(argument, addArgument); - } else { - style += goog.html.SafeStyle.unwrap(argument); +ol.geom.flat.intersectsextent.linearRingss = + function(flatCoordinates, offset, endss, stride, extent) { + goog.asserts.assert(endss.length > 0, 'endss should not be an empty array'); + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + if (ol.geom.flat.intersectsextent.linearRings( + flatCoordinates, offset, ends, stride, extent)) { + return true; } - }; - - goog.array.forEach(arguments, addArgument); - if (!style) { - return goog.html.SafeStyle.EMPTY; + offset = ends[ends.length - 1]; } - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - style); + return false; }; -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +goog.provide('ol.geom.flat.reverse'); + /** - * @fileoverview Utility functions for supporting Bidi issues. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. */ +ol.geom.flat.reverse.coordinates = + function(flatCoordinates, offset, end, stride) { + while (offset < end - stride) { + var i; + for (i = 0; i < stride; ++i) { + var tmp = flatCoordinates[offset + i]; + flatCoordinates[offset + i] = flatCoordinates[end - stride + i]; + flatCoordinates[end - stride + i] = tmp; + } + offset += stride; + end -= stride; + } +}; + +goog.provide('ol.geom.flat.orient'); + +goog.require('ol.geom.flat.reverse'); /** - * Namespace for bidi supporting functions. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} Is clockwise. */ -goog.provide('goog.i18n.bidi'); -goog.provide('goog.i18n.bidi.Dir'); -goog.provide('goog.i18n.bidi.DirectionalString'); -goog.provide('goog.i18n.bidi.Format'); +ol.geom.flat.orient.linearRingIsClockwise = + function(flatCoordinates, offset, end, stride) { + // http://tinyurl.com/clockwise-method + // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp + var edge = 0; + var x1 = flatCoordinates[end - stride]; + var y1 = flatCoordinates[end - stride + 1]; + for (; offset < end; offset += stride) { + var x2 = flatCoordinates[offset]; + var y2 = flatCoordinates[offset + 1]; + edge += (x2 - x1) * (y2 + y1); + x1 = x2; + y1 = y2; + } + return edge > 0; +}; /** - * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant - * to say that the current locale is a RTL locale. This should only be used - * if you want to override the default behavior for deciding whether the - * current locale is RTL or not. + * Determines if linear rings are oriented. By default, left-hand orientation + * is tested (first ring must be clockwise, remaining rings counter-clockwise). + * To test for right-hand orientation, use the `opt_right` argument. * - * {@see goog.i18n.bidi.IS_RTL} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Test for right-hand orientation + * (counter-clockwise exterior ring and clockwise interior rings). + * @return {boolean} Rings are correctly oriented. */ -goog.define('goog.i18n.bidi.FORCE_RTL', false); +ol.geom.flat.orient.linearRingsAreOriented = + function(flatCoordinates, offset, ends, stride, opt_right) { + var right = goog.isDef(opt_right) ? opt_right : false; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( + flatCoordinates, offset, end, stride); + if (i === 0) { + if ((right && isClockwise) || (!right && !isClockwise)) { + return false; + } + } else { + if ((right && !isClockwise) || (!right && isClockwise)) { + return false; + } + } + offset = end; + } + return true; +}; /** - * Constant that defines whether or not the current locale is a RTL locale. - * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default - * to check that {@link goog.LOCALE} is one of a few major RTL locales. - * - * <p>This is designed to be a maximally efficient compile-time constant. For - * example, for the default goog.LOCALE, compiling - * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It - * is this design consideration that limits the implementation to only - * supporting a few major RTL locales, as opposed to the broader repertoire of - * something like goog.i18n.bidi.isRtlLanguage. - * - * <p>Since this constant refers to the directionality of the locale, it is up - * to the caller to determine if this constant should also be used for the - * direction of the UI. + * Determines if linear rings are oriented. By default, left-hand orientation + * is tested (first ring must be clockwise, remaining rings counter-clockwise). + * To test for right-hand orientation, use the `opt_right` argument. * - * {@see goog.LOCALE} - * - * @type {boolean} - * - * TODO(user): write a test that checks that this is a compile-time constant. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Array of array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Test for right-hand orientation + * (counter-clockwise exterior ring and clockwise interior rings). + * @return {boolean} Rings are correctly oriented. */ -goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL || - ( - (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'he' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' || - goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') && - (goog.LOCALE.length == 2 || - goog.LOCALE.substring(2, 3) == '-' || - goog.LOCALE.substring(2, 3) == '_') - ) || ( - goog.LOCALE.length >= 3 && - goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' && - (goog.LOCALE.length == 3 || - goog.LOCALE.substring(3, 4) == '-' || - goog.LOCALE.substring(3, 4) == '_') - ); +ol.geom.flat.orient.linearRingssAreOriented = + function(flatCoordinates, offset, endss, stride, opt_right) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + if (!ol.geom.flat.orient.linearRingsAreOriented( + flatCoordinates, offset, endss[i], stride, opt_right)) { + return false; + } + } + return true; +}; /** - * Unicode formatting characters and directionality string constants. - * @enum {string} + * Orient coordinates in a flat array of linear rings. By default, rings + * are oriented following the left-hand rule (clockwise for exterior and + * counter-clockwise for interior rings). To orient according to the + * right-hand rule, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {boolean=} opt_right Follow the right-hand rule for orientation. + * @return {number} End. */ -goog.i18n.bidi.Format = { - /** Unicode "Left-To-Right Embedding" (LRE) character. */ - LRE: '\u202A', - /** Unicode "Right-To-Left Embedding" (RLE) character. */ - RLE: '\u202B', - /** Unicode "Pop Directional Formatting" (PDF) character. */ - PDF: '\u202C', - /** Unicode "Left-To-Right Mark" (LRM) character. */ - LRM: '\u200E', - /** Unicode "Right-To-Left Mark" (RLM) character. */ - RLM: '\u200F' +ol.geom.flat.orient.orientLinearRings = + function(flatCoordinates, offset, ends, stride, opt_right) { + var right = goog.isDef(opt_right) ? opt_right : false; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( + flatCoordinates, offset, end, stride); + var reverse = i === 0 ? + (right && isClockwise) || (!right && !isClockwise) : + (right && !isClockwise) || (!right && isClockwise); + if (reverse) { + ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride); + } + offset = end; + } + return offset; }; /** - * Directionality enum. - * @enum {number} + * Orient coordinates in a flat array of linear rings. By default, rings + * are oriented following the left-hand rule (clockwise for exterior and + * counter-clockwise for interior rings). To orient according to the + * right-hand rule, use the `opt_right` argument. + * + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Array of array of end indexes. + * @param {number} stride Stride. + * @param {boolean=} opt_right Follow the right-hand rule for orientation. + * @return {number} End. */ -goog.i18n.bidi.Dir = { +ol.geom.flat.orient.orientLinearRingss = + function(flatCoordinates, offset, endss, stride, opt_right) { + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + offset = ol.geom.flat.orient.orientLinearRings( + flatCoordinates, offset, endss[i], stride, opt_right); + } + return offset; +}; + +goog.provide('ol.geom.Polygon'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interiorpoint'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.simplify'); + + + +/** + * @classdesc + * Polygon geometry. + * + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.Polygon = function(coordinates, opt_layout) { + + goog.base(this); + /** - * Left-to-right. + * @type {Array.<number>} + * @private */ - LTR: 1, + this.ends_ = []; /** - * Right-to-left. + * @private + * @type {number} */ - RTL: -1, + this.flatInteriorPointRevision_ = -1; /** - * Neither left-to-right nor right-to-left. + * @private + * @type {ol.Coordinate} */ - NEUTRAL: 0 -}; + this.flatInteriorPoint_ = null; + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; -/** - * 'right' string constant. - * @type {string} - */ -goog.i18n.bidi.RIGHT = 'right'; + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + /** + * @private + * @type {number} + */ + this.orientedRevision_ = -1; -/** - * 'left' string constant. - * @type {string} - */ -goog.i18n.bidi.LEFT = 'left'; + /** + * @private + * @type {Array.<number>} + */ + this.orientedFlatCoordinates_ = null; + + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); + +}; +goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry); /** - * 'left' if locale is RTL, 'right' if not. - * @type {string} + * Append the passed linear ring to this polygon. + * @param {ol.geom.LinearRing} linearRing Linear ring. + * @api stable */ -goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : - goog.i18n.bidi.RIGHT; +ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) { + goog.asserts.assert(linearRing.getLayout() == this.layout, + 'layout of linearRing should match layout'); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = linearRing.getFlatCoordinates().slice(); + } else { + goog.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates()); + } + this.ends_.push(this.flatCoordinates.length); + this.changed(); +}; /** - * 'right' if locale is RTL, 'left' if not. - * @type {string} + * Make a complete copy of the geometry. + * @return {!ol.geom.Polygon} Clone. + * @api stable */ -goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : - goog.i18n.bidi.LEFT; +ol.geom.Polygon.prototype.clone = function() { + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), this.ends_.slice()); + return polygon; +}; /** - * Convert a directionality given in various formats to a goog.i18n.bidi.Dir - * constant. Useful for interaction with different standards of directionality - * representation. - * - * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given - * in one of the following formats: - * 1. A goog.i18n.bidi.Dir constant. - * 2. A number (positive = LTR, negative = RTL, 0 = neutral). - * 3. A boolean (true = RTL, false = LTR). - * 4. A null for unknown directionality. - * @param {boolean=} opt_noNeutral Whether a givenDir of zero or - * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in - * order to preserve legacy behavior. - * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the - * given directionality. If given null, returns null (i.e. unknown). + * @inheritDoc */ -goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) { - if (typeof givenDir == 'number') { - // This includes the non-null goog.i18n.bidi.Dir case. - return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : - givenDir < 0 ? goog.i18n.bidi.Dir.RTL : - opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL; - } else if (givenDir == null) { - return null; - } else { - // Must be typeof givenDir == 'boolean'. - return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; +ol.geom.Polygon.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( + this.flatCoordinates, 0, this.ends_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getsClosestPoint( + this.flatCoordinates, 0, this.ends_, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); }; /** - * A practical pattern to identify strong LTR characters. This pattern is not - * theoretically correct according to the Unicode standard. It is simplified for - * performance and small code size. - * @type {string} - * @private + * @inheritDoc */ -goog.i18n.bidi.ltrChars_ = - 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + - '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF'; +ol.geom.Polygon.prototype.containsXY = function(x, y) { + return ol.geom.flat.contains.linearRingsContainsXY( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y); +}; /** - * A practical pattern to identify strong RTL character. This pattern is not - * theoretically correct according to the Unicode standard. It is simplified - * for performance and small code size. - * @type {string} - * @private + * Return the area of the polygon on projected plane. + * @return {number} Area (on projected plane). + * @api stable */ -goog.i18n.bidi.rtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC'; +ol.geom.Polygon.prototype.getArea = function() { + return ol.geom.flat.area.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride); +}; /** - * Simplified regular expression for an HTML tag (opening or closing) or an HTML - * escape. We might want to skip over such expressions when estimating the text - * directionality. - * @type {RegExp} - * @private + * Get the coordinate array for this geometry. This array has the structure + * of a GeoJSON coordinate array for polygons. + * + * @param {boolean=} opt_right Orient coordinates according to the right-hand + * rule (counter-clockwise for exterior and clockwise for interior rings). + * If `false`, coordinates will be oriented according to the left-hand rule + * (clockwise for exterior and counter-clockwise for interior rings). + * By default, coordinate orientation will depend on how the geometry was + * constructed. + * @return {Array.<Array.<ol.Coordinate>>} Coordinates. + * @api stable */ -goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g; +ol.geom.Polygon.prototype.getCoordinates = function(opt_right) { + var flatCoordinates; + if (goog.isDef(opt_right)) { + flatCoordinates = this.getOrientedFlatCoordinates().slice(); + ol.geom.flat.orient.orientLinearRings( + flatCoordinates, 0, this.ends_, this.stride, opt_right); + } else { + flatCoordinates = this.flatCoordinates; + } + + return ol.geom.flat.inflate.coordinatess( + flatCoordinates, 0, this.ends_, this.stride); +}; /** - * Returns the input text with spaces instead of HTML tags or HTML escapes, if - * opt_isStripNeeded is true. Else returns the input as is. - * Useful for text directionality estimation. - * Note: the function should not be used in other contexts; it is not 100% - * correct, but rather a good-enough implementation for directionality - * estimation purposes. - * @param {string} str The given string. - * @param {boolean=} opt_isStripNeeded Whether to perform the stripping. - * Default: false (to retain consistency with calling functions). - * @return {string} The given string cleaned of HTML tags / escapes. - * @private + * @return {Array.<number>} Ends. */ -goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) { - return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : - str; +ol.geom.Polygon.prototype.getEnds = function() { + return this.ends_; }; /** - * Regular expression to check for RTL characters. - * @type {RegExp} - * @private + * @return {Array.<number>} Interior point. */ -goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']'); +ol.geom.Polygon.prototype.getFlatInteriorPoint = function() { + if (this.flatInteriorPointRevision_ != this.getRevision()) { + var flatCenter = ol.extent.getCenter(this.getExtent()); + this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, + flatCenter, 0); + this.flatInteriorPointRevision_ = this.getRevision(); + } + return this.flatInteriorPoint_; +}; /** - * Regular expression to check for LTR characters. - * @type {RegExp} - * @private + * Return an interior point of the polygon. + * @return {ol.geom.Point} Interior point. + * @api stable */ -goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']'); +ol.geom.Polygon.prototype.getInteriorPoint = function() { + return new ol.geom.Point(this.getFlatInteriorPoint()); +}; /** - * Test whether the given string has any RTL characters in it. - * @param {string} str The given string that need to be tested. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether the string contains RTL characters. + * Return the number of rings of the polygon, this includes the exterior + * ring and any interior rings. + * + * @return {number} Number of rings. + * @api */ -goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) { - return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_( - str, opt_isHtml)); +ol.geom.Polygon.prototype.getLinearRingCount = function() { + return this.ends_.length; }; /** - * Test whether the given string has any RTL characters in it. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the string contains RTL characters. - * @deprecated Use hasAnyRtl. + * Return the Nth linear ring of the polygon geometry. Return `null` if the + * given index is out of range. + * The exterior linear ring is available at index `0` and the interior rings + * at index `1` and beyond. + * + * @param {number} index Index. + * @return {ol.geom.LinearRing} Linear ring. + * @api stable */ -goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl; +ol.geom.Polygon.prototype.getLinearRing = function(index) { + goog.asserts.assert(0 <= index && index < this.ends_.length, + 'index should be in between 0 and and length of this.ends_'); + if (index < 0 || this.ends_.length <= index) { + return null; + } + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); + return linearRing; +}; /** - * Test whether the given string has any LTR characters in it. - * @param {string} str The given string that need to be tested. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether the string contains LTR characters. + * Return the linear rings of the polygon. + * @return {Array.<ol.geom.LinearRing>} Linear rings. + * @api stable */ -goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) { - return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_( - str, opt_isHtml)); +ol.geom.Polygon.prototype.getLinearRings = function() { + var layout = this.layout; + var flatCoordinates = this.flatCoordinates; + var ends = this.ends_; + var linearRings = []; + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var linearRing = new ol.geom.LinearRing(null); + linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); + linearRings.push(linearRing); + offset = end; + } + return linearRings; }; /** - * Regular expression pattern to check if the first character in the string - * is LTR. - * @type {RegExp} - * @private + * @return {Array.<number>} Oriented flat coordinates. */ -goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']'); +ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() { + if (this.orientedRevision_ != this.getRevision()) { + var flatCoordinates = this.flatCoordinates; + if (ol.geom.flat.orient.linearRingsAreOriented( + flatCoordinates, 0, this.ends_, this.stride)) { + this.orientedFlatCoordinates_ = flatCoordinates; + } else { + this.orientedFlatCoordinates_ = flatCoordinates.slice(); + this.orientedFlatCoordinates_.length = + ol.geom.flat.orient.orientLinearRings( + this.orientedFlatCoordinates_, 0, this.ends_, this.stride); + } + this.orientedRevision_ = this.getRevision(); + } + return this.orientedFlatCoordinates_; +}; /** - * Regular expression pattern to check if the first character in the string - * is RTL. - * @type {RegExp} - * @private + * @inheritDoc */ -goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']'); +ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = + function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEnds = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes( + this.flatCoordinates, 0, this.ends_, this.stride, + Math.sqrt(squaredTolerance), + simplifiedFlatCoordinates, 0, simplifiedEnds); + var simplifiedPolygon = new ol.geom.Polygon(null); + simplifiedPolygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); + return simplifiedPolygon; +}; /** - * Check if the first character in the string is RTL or not. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the first character in str is an RTL char. + * @inheritDoc + * @api stable */ -goog.i18n.bidi.isRtlChar = function(str) { - return goog.i18n.bidi.rtlRe_.test(str); +ol.geom.Polygon.prototype.getType = function() { + return ol.geom.GeometryType.POLYGON; }; /** - * Check if the first character in the string is LTR or not. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the first character in str is an LTR char. + * @inheritDoc + * @api stable */ -goog.i18n.bidi.isLtrChar = function(str) { - return goog.i18n.bidi.ltrRe_.test(str); +ol.geom.Polygon.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.linearRings( + this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent); }; /** - * Check if the first character in the string is neutral or not. - * @param {string} str The given string that need to be tested. - * @return {boolean} Whether the first character in str is a neutral char. + * Set the coordinates of the polygon. + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -goog.i18n.bidi.isNeutralChar = function(str) { - return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str); +ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); + } else { + this.setLayout(opt_layout, coordinates, 2); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + var ends = ol.geom.flat.deflate.coordinatess( + this.flatCoordinates, 0, coordinates, this.stride, this.ends_); + this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; + this.changed(); + } }; /** - * Regular expressions to check if a piece of text is of LTR directionality - * on first character with strong directionality. - * @type {RegExp} - * @private + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>} ends Ends. */ -goog.i18n.bidi.ltrDirCheckRe_ = new RegExp( - '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']'); +ol.geom.Polygon.prototype.setFlatCoordinates = + function(layout, flatCoordinates, ends) { + if (goog.isNull(flatCoordinates)) { + goog.asserts.assert(!goog.isNull(ends) && ends.length === 0, + 'ends cannot be null and should be an empty array'); + } else if (ends.length === 0) { + goog.asserts.assert(flatCoordinates.length === 0, + 'flatCoordinates should be an empty array'); + } else { + goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1], + 'the length of flatCoordinates should be the last entry of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.ends_ = ends; + this.changed(); +}; /** - * Regular expressions to check if a piece of text is of RTL directionality - * on first character with strong directionality. - * @type {RegExp} - * @private + * Create an approximation of a circle on the surface of a sphere. + * @param {ol.Sphere} sphere The sphere. + * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees). + * @param {number} radius The great-circle distance from the center to + * the polygon vertices. + * @param {number=} opt_n Optional number of vertices for the resulting + * polygon. Default is `32`. + * @return {ol.geom.Polygon} The "circular" polygon. + * @api stable */ -goog.i18n.bidi.rtlDirCheckRe_ = new RegExp( - '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']'); +ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) { + var n = goog.isDef(opt_n) ? opt_n : 32; + /** @type {Array.<number>} */ + var flatCoordinates = []; + var i; + for (i = 0; i < n; ++i) { + goog.array.extend( + flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n)); + } + flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]); + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); + return polygon; +}; /** - * Check whether the first strongly directional character (if any) is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL directionality is detected using the first - * strongly-directional character method. + * Create a polygon from an extent. The layout used is `XY`. + * @param {ol.Extent} extent The extent. + * @return {ol.geom.Polygon} The polygon. + * @api */ -goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) { - return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_( - str, opt_isHtml)); +ol.geom.Polygon.fromExtent = function(extent) { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var flatCoordinates = + [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY]; + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); + return polygon; }; /** - * Check whether the first strongly directional character (if any) is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL directionality is detected using the first - * strongly-directional character method. - * @deprecated Use startsWithRtl. + * Create a regular polygon from a circle. + * @param {ol.geom.Circle} circle Circle geometry. + * @param {number=} opt_sides Number of sides of the polygon. Default is 32. + * @param {number=} opt_angle Start angle for the first vertex of the polygon in + * radians. Default is 0. + * @return {ol.geom.Polygon} Polygon geometry. + * @api */ -goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl; +ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) { + var sides = goog.isDef(opt_sides) ? opt_sides : 32; + var stride = circle.getStride(); + var layout = circle.getLayout(); + var polygon = new ol.geom.Polygon(null, layout); + var flatCoordinates = goog.array.repeat(0, stride * (sides + 1)); + var ends = [flatCoordinates.length]; + polygon.setFlatCoordinates(layout, flatCoordinates, ends); + ol.geom.Polygon.makeRegular( + polygon, circle.getCenter(), circle.getRadius(), opt_angle); + return polygon; +}; /** - * Check whether the first strongly directional character (if any) is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR directionality is detected using the first - * strongly-directional character method. - */ -goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) { - return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_( - str, opt_isHtml)); + * Modify the coordinates of a polygon to make it a regular polygon. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {ol.Coordinate} center Center of the regular polygon. + * @param {number} radius Radius of the regular polygon. + * @param {number=} opt_angle Start angle for the first vertex of the polygon in + * radians. Default is 0. + */ +ol.geom.Polygon.makeRegular = function(polygon, center, radius, opt_angle) { + var flatCoordinates = polygon.getFlatCoordinates(); + var layout = polygon.getLayout(); + var stride = polygon.getStride(); + var ends = polygon.getEnds(); + goog.asserts.assert(ends.length === 1, 'only 1 ring is supported'); + var sides = flatCoordinates.length / stride - 1; + var startAngle = goog.isDef(opt_angle) ? opt_angle : 0; + var angle, coord, offset; + for (var i = 0; i <= sides; ++i) { + offset = i * stride; + angle = startAngle + (goog.math.modulo(i, sides) * 2 * Math.PI / sides); + flatCoordinates[offset] = center[0] + (radius * Math.cos(angle)); + flatCoordinates[offset + 1] = center[1] + (radius * Math.sin(angle)); + } + polygon.setFlatCoordinates(layout, flatCoordinates, ends); }; +goog.provide('ol.View'); +goog.provide('ol.ViewHint'); +goog.provide('ol.ViewProperty'); -/** - * Check whether the first strongly directional character (if any) is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR directionality is detected using the first - * strongly-directional character method. - * @deprecated Use startsWithLtr. - */ -goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol'); +goog.require('ol.CenterConstraint'); +goog.require('ol.Constraints'); +goog.require('ol.Object'); +goog.require('ol.ResolutionConstraint'); +goog.require('ol.RotationConstraint'); +goog.require('ol.RotationConstraintType'); +goog.require('ol.Size'); +goog.require('ol.coordinate'); +goog.require('ol.extent'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.proj'); +goog.require('ol.proj.METERS_PER_UNIT'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); /** - * Regular expression to check if a string looks like something that must - * always be LTR even in RTL text, e.g. a URL. When estimating the - * directionality of text containing these, we treat these as weakly LTR, - * like numbers. - * @type {RegExp} - * @private + * @enum {string} */ -goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/; +ol.ViewProperty = { + CENTER: 'center', + RESOLUTION: 'resolution', + ROTATION: 'rotation' +}; /** - * Check whether the input string either contains no strongly directional - * characters or looks like a url. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether neutral directionality is detected. + * @enum {number} */ -goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) { - str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml); - return goog.i18n.bidi.isRequiredLtrRe_.test(str) || - !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str); +ol.ViewHint = { + ANIMATING: 0, + INTERACTING: 1 }; + /** - * Regular expressions to check if the last strongly-directional character in a - * piece of text is LTR. - * @type {RegExp} - * @private + * @classdesc + * An ol.View object represents a simple 2D view of the map. + * + * This is the object to act upon to change the center, resolution, + * and rotation of the map. + * + * ### The view states + * + * An `ol.View` is determined by three states: `center`, `resolution`, + * and `rotation`. Each state has a corresponding getter and setter, e.g. + * `getCenter` and `setCenter` for the `center` state. + * + * An `ol.View` has a `projection`. The projection determines the + * coordinate system of the center, and its units determine the units of the + * resolution (projection units per pixel). The default projection is + * Spherical Mercator (EPSG:3857). + * + * ### The constraints + * + * `setCenter`, `setResolution` and `setRotation` can be used to change the + * states of the view. Any value can be passed to the setters. And the value + * that is passed to a setter will effectively be the value set in the view, + * and returned by the corresponding getter. + * + * But an `ol.View` object also has a *resolution constraint*, a + * *rotation constraint* and a *center constraint*. + * + * As said above, no constraints are applied when the setters are used to set + * new states for the view. Applying constraints is done explicitly through + * the use of the `constrain*` functions (`constrainResolution` and + * `constrainRotation` and `constrainCenter`). + * + * The main users of the constraints are the interactions and the + * controls. For example, double-clicking on the map changes the view to + * the "next" resolution. And releasing the fingers after pinch-zooming + * snaps to the closest resolution (with an animation). + * + * The *resolution constraint* snaps to specific resolutions. It is + * determined by the following options: `resolutions`, `maxResolution`, + * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three + * options are ignored. See documentation for each option for more + * information. + * + * The *rotation constraint* snaps to specific angles. It is determined + * by the following options: `enableRotation` and `constrainRotation`. + * By default the rotation value is snapped to zero when approaching the + * horizontal. + * + * The *center constraint* is determined by the `extent` option. By + * default the center is not constrained at all. + * + * @constructor + * @extends {ol.Object} + * @param {olx.ViewOptions=} opt_options View options. + * @api stable */ -goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp( - '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$'); +ol.View = function(opt_options) { + goog.base(this); + var options = goog.isDef(opt_options) ? opt_options : {}; + /** + * @private + * @type {Array.<number>} + */ + this.hints_ = [0, 0]; -/** - * Regular expressions to check if the last strongly-directional character in a - * piece of text is RTL. - * @type {RegExp} - * @private - */ -goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp( - '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$'); + /** + * @type {Object.<string, *>} + */ + var properties = {}; + properties[ol.ViewProperty.CENTER] = goog.isDef(options.center) ? + options.center : null; + /** + * @private + * @const + * @type {ol.proj.Projection} + */ + this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857'); -/** - * Check if the exit directionality a piece of text is LTR, i.e. if the last - * strongly-directional character in the string is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR exit directionality was detected. - */ -goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) { - return goog.i18n.bidi.ltrExitDirCheckRe_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); -}; + var resolutionConstraintInfo = ol.View.createResolutionConstraint_( + options); + /** + * @private + * @type {number} + */ + this.maxResolution_ = resolutionConstraintInfo.maxResolution; -/** - * Check if the exit directionality a piece of text is LTR, i.e. if the last - * strongly-directional character in the string is LTR. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether LTR exit directionality was detected. - * @deprecated Use endsWithLtr. - */ -goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr; + /** + * @private + * @type {number} + */ + this.minResolution_ = resolutionConstraintInfo.minResolution; + /** + * @private + * @type {number} + */ + this.minZoom_ = resolutionConstraintInfo.minZoom; -/** - * Check if the exit directionality a piece of text is RTL, i.e. if the last - * strongly-directional character in the string is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL exit directionality was detected. - */ -goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) { - return goog.i18n.bidi.rtlExitDirCheckRe_.test( - goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); + var centerConstraint = ol.View.createCenterConstraint_(options); + var resolutionConstraint = resolutionConstraintInfo.constraint; + var rotationConstraint = ol.View.createRotationConstraint_(options); + + /** + * @private + * @type {ol.Constraints} + */ + this.constraints_ = new ol.Constraints( + centerConstraint, resolutionConstraint, rotationConstraint); + + if (goog.isDef(options.resolution)) { + properties[ol.ViewProperty.RESOLUTION] = options.resolution; + } else if (goog.isDef(options.zoom)) { + properties[ol.ViewProperty.RESOLUTION] = this.constrainResolution( + this.maxResolution_, options.zoom - this.minZoom_); + } + properties[ol.ViewProperty.ROTATION] = + goog.isDef(options.rotation) ? options.rotation : 0; + this.setProperties(properties); }; +goog.inherits(ol.View, ol.Object); /** - * Check if the exit directionality a piece of text is RTL, i.e. if the last - * strongly-directional character in the string is RTL. - * @param {string} str String being checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether RTL exit directionality was detected. - * @deprecated Use endsWithRtl. + * @param {number} rotation Target rotation. + * @param {ol.Coordinate} anchor Rotation anchor. + * @return {ol.Coordinate|undefined} Center for rotation and anchor. */ -goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl; +ol.View.prototype.calculateCenterRotate = function(rotation, anchor) { + var center; + var currentCenter = this.getCenter(); + if (goog.isDef(currentCenter)) { + center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]]; + ol.coordinate.rotate(center, rotation - this.getRotation()); + ol.coordinate.add(center, anchor); + } + return center; +}; /** - * A regular expression for matching right-to-left language codes. - * See {@link #isRtlLanguage} for the design. - * @type {RegExp} - * @private + * @param {number} resolution Target resolution. + * @param {ol.Coordinate} anchor Zoom anchor. + * @return {ol.Coordinate|undefined} Center for resolution and anchor. */ -goog.i18n.bidi.rtlLocalesRe_ = new RegExp( - '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' + - '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' + - '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)', - 'i'); +ol.View.prototype.calculateCenterZoom = function(resolution, anchor) { + var center; + var currentCenter = this.getCenter(); + var currentResolution = this.getResolution(); + if (goog.isDef(currentCenter) && goog.isDef(currentResolution)) { + var x = anchor[0] - + resolution * (anchor[0] - currentCenter[0]) / currentResolution; + var y = anchor[1] - + resolution * (anchor[1] - currentCenter[1]) / currentResolution; + center = [x, y]; + } + return center; +}; /** - * Check if a BCP 47 / III language code indicates an RTL language, i.e. either: - * - a language code explicitly specifying one of the right-to-left scripts, - * e.g. "az-Arab", or<p> - * - a language code specifying one of the languages normally written in a - * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying - * Latin or Cyrillic script (which are the usual LTR alternatives).<p> - * The list of right-to-left scripts appears in the 100-199 range in - * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and - * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and - * Tifinagh, which also have significant modern usage. The rest (Syriac, - * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage - * and are not recognized to save on code size. - * The languages usually written in a right-to-left script are taken as those - * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in - * http://www.iana.org/assignments/language-subtag-registry, - * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug). - * Other subtags of the language code, e.g. regions like EG (Egypt), are - * ignored. - * @param {string} lang BCP 47 (a.k.a III) language code. - * @return {boolean} Whether the language code is an RTL language. + * Get the constrained center of this view. + * @param {ol.Coordinate|undefined} center Center. + * @return {ol.Coordinate|undefined} Constrained center. + * @api */ -goog.i18n.bidi.isRtlLanguage = function(lang) { - return goog.i18n.bidi.rtlLocalesRe_.test(lang); +ol.View.prototype.constrainCenter = function(center) { + return this.constraints_.center(center); }; /** - * Regular expression for bracket guard replacement in html. - * @type {RegExp} - * @private + * Get the constrained resolution of this view. + * @param {number|undefined} resolution Resolution. + * @param {number=} opt_delta Delta. Default is `0`. + * @param {number=} opt_direction Direction. Default is `0`. + * @return {number|undefined} Constrained resolution. + * @api */ -goog.i18n.bidi.bracketGuardHtmlRe_ = - /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)/g; +ol.View.prototype.constrainResolution = function( + resolution, opt_delta, opt_direction) { + var delta = opt_delta || 0; + var direction = opt_direction || 0; + return this.constraints_.resolution(resolution, delta, direction); +}; /** - * Regular expression for bracket guard replacement in text. - * @type {RegExp} - * @private + * Get the constrained rotation of this view. + * @param {number|undefined} rotation Rotation. + * @param {number=} opt_delta Delta. Default is `0`. + * @return {number|undefined} Constrained rotation. + * @api */ -goog.i18n.bidi.bracketGuardTextRe_ = - /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g; +ol.View.prototype.constrainRotation = function(rotation, opt_delta) { + var delta = opt_delta || 0; + return this.constraints_.rotation(rotation, delta); +}; /** - * Apply bracket guard using html span tag. This is to address the problem of - * messy bracket display frequently happens in RTL layout. - * @param {string} s The string that need to be processed. - * @param {boolean=} opt_isRtlContext specifies default direction (usually - * direction of the UI). - * @return {string} The processed string, with all bracket guarded. + * Get the view center. + * @return {ol.Coordinate|undefined} The center of the view. + * @observable + * @api stable */ -goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) { - var useRtl = opt_isRtlContext === undefined ? - goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext; - if (useRtl) { - return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_, - '<span dir=rtl>$&</span>'); - } - return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_, - '<span dir=ltr>$&</span>'); +ol.View.prototype.getCenter = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.ViewProperty.CENTER)); }; /** - * Apply bracket guard using LRM and RLM. This is to address the problem of - * messy bracket display frequently happens in RTL layout. - * This version works for both plain text and html. But it does not work as - * good as guardBracketInHtml in some cases. - * @param {string} s The string that need to be processed. - * @param {boolean=} opt_isRtlContext specifies default direction (usually - * direction of the UI). - * @return {string} The processed string, with all bracket guarded. + * @return {Array.<number>} Hint. */ -goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) { - var useRtl = opt_isRtlContext === undefined ? - goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext; - var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM; - return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark); +ol.View.prototype.getHints = function() { + return this.hints_.slice(); }; /** - * Enforce the html snippet in RTL directionality regardless overall context. - * If the html piece was enclosed by tag, dir will be applied to existing - * tag, otherwise a span tag will be added as wrapper. For this reason, if - * html snippet start with with tag, this tag must enclose the whole piece. If - * the tag already has a dir specified, this new one will override existing - * one in behavior (tested on FF and IE). - * @param {string} html The string that need to be processed. - * @return {string} The processed string, with directionality enforced to RTL. + * Calculate the extent for the current view state and the passed size. + * The size is the pixel dimensions of the box into which the calculated extent + * should fit. In most cases you want to get the extent of the entire map, + * that is `map.getSize()`. + * @param {ol.Size} size Box pixel size. + * @return {ol.Extent} Extent. + * @api stable */ -goog.i18n.bidi.enforceRtlInHtml = function(html) { - if (html.charAt(0) == '<') { - return html.replace(/<\w+/, '$& dir=rtl'); - } - // '\n' is important for FF so that it won't incorrectly merge span groups - return '\n<span dir=rtl>' + html + '</span>'; +ol.View.prototype.calculateExtent = function(size) { + var center = this.getCenter(); + goog.asserts.assert(goog.isDefAndNotNull(center), + 'The view center is not defined'); + var resolution = this.getResolution(); + goog.asserts.assert(goog.isDef(resolution), + 'The view resolution is not defined'); + var rotation = this.getRotation(); + goog.asserts.assert(goog.isDef(rotation), + 'The view rotation is not defined'); + + return ol.extent.getForViewAndSize(center, resolution, rotation, size); }; /** - * Enforce RTL on both end of the given text piece using unicode BiDi formatting - * characters RLE and PDF. - * @param {string} text The piece of text that need to be wrapped. - * @return {string} The wrapped string after process. + * Get the view projection. + * @return {ol.proj.Projection} The projection of the view. + * @api stable */ -goog.i18n.bidi.enforceRtlInText = function(text) { - return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF; +ol.View.prototype.getProjection = function() { + return this.projection_; }; /** - * Enforce the html snippet in RTL directionality regardless overall context. - * If the html piece was enclosed by tag, dir will be applied to existing - * tag, otherwise a span tag will be added as wrapper. For this reason, if - * html snippet start with with tag, this tag must enclose the whole piece. If - * the tag already has a dir specified, this new one will override existing - * one in behavior (tested on FF and IE). - * @param {string} html The string that need to be processed. - * @return {string} The processed string, with directionality enforced to RTL. + * Get the view resolution. + * @return {number|undefined} The resolution of the view. + * @observable + * @api stable */ -goog.i18n.bidi.enforceLtrInHtml = function(html) { - if (html.charAt(0) == '<') { - return html.replace(/<\w+/, '$& dir=ltr'); - } - // '\n' is important for FF so that it won't incorrectly merge span groups - return '\n<span dir=ltr>' + html + '</span>'; +ol.View.prototype.getResolution = function() { + return /** @type {number|undefined} */ ( + this.get(ol.ViewProperty.RESOLUTION)); }; /** - * Enforce LTR on both end of the given text piece using unicode BiDi formatting - * characters LRE and PDF. - * @param {string} text The piece of text that need to be wrapped. - * @return {string} The wrapped string after process. + * Get the resolution for a provided extent (in map units) and size (in pixels). + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Box pixel size. + * @return {number} The resolution at which the provided extent will render at + * the given size. */ -goog.i18n.bidi.enforceLtrInText = function(text) { - return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF; +ol.View.prototype.getResolutionForExtent = function(extent, size) { + var xResolution = ol.extent.getWidth(extent) / size[0]; + var yResolution = ol.extent.getHeight(extent) / size[1]; + return Math.max(xResolution, yResolution); }; /** - * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;" - * @type {RegExp} - * @private + * Return a function that returns a value between 0 and 1 for a + * resolution. Exponential scaling is assumed. + * @param {number=} opt_power Power. + * @return {function(number): number} Resolution for value function. */ -goog.i18n.bidi.dimensionsRe_ = - /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g; +ol.View.prototype.getResolutionForValueFunction = function(opt_power) { + var power = opt_power || 2; + var maxResolution = this.maxResolution_; + var minResolution = this.minResolution_; + var max = Math.log(maxResolution / minResolution) / Math.log(power); + return ( + /** + * @param {number} value Value. + * @return {number} Resolution. + */ + function(value) { + var resolution = maxResolution / Math.pow(power, value * max); + goog.asserts.assert(resolution >= minResolution && + resolution <= maxResolution, + 'calculated resolution outside allowed bounds (%s <= %s <= %s)', + minResolution, resolution, maxResolution); + return resolution; + }); +}; /** - * Regular expression for left. - * @type {RegExp} - * @private + * Get the view rotation. + * @return {number} The rotation of the view in radians. + * @observable + * @api stable */ -goog.i18n.bidi.leftRe_ = /left/gi; +ol.View.prototype.getRotation = function() { + return /** @type {number} */ (this.get(ol.ViewProperty.ROTATION)); +}; /** - * Regular expression for right. - * @type {RegExp} - * @private + * Return a function that returns a resolution for a value between + * 0 and 1. Exponential scaling is assumed. + * @param {number=} opt_power Power. + * @return {function(number): number} Value for resolution function. */ -goog.i18n.bidi.rightRe_ = /right/gi; +ol.View.prototype.getValueForResolutionFunction = function(opt_power) { + var power = opt_power || 2; + var maxResolution = this.maxResolution_; + var minResolution = this.minResolution_; + var max = Math.log(maxResolution / minResolution) / Math.log(power); + return ( + /** + * @param {number} resolution Resolution. + * @return {number} Value. + */ + function(resolution) { + var value = + (Math.log(maxResolution / resolution) / Math.log(power)) / max; + goog.asserts.assert(value >= 0 && value <= 1, + 'calculated value (%s) ouside allowed range (0-1)', value); + return value; + }); +}; /** - * Placeholder regular expression for swapping. - * @type {RegExp} - * @private + * @return {olx.ViewState} View state. */ -goog.i18n.bidi.tempRe_ = /%%%%/g; +ol.View.prototype.getState = function() { + goog.asserts.assert(this.isDef(), + 'the view was not defined (had no center and/or resolution)'); + var center = /** @type {ol.Coordinate} */ (this.getCenter()); + var projection = this.getProjection(); + var resolution = /** @type {number} */ (this.getResolution()); + var rotation = this.getRotation(); + return /** @type {olx.ViewState} */ ({ + // Snap center to closest pixel + center: [ + Math.round(center[0] / resolution) * resolution, + Math.round(center[1] / resolution) * resolution + ], + projection: goog.isDef(projection) ? projection : null, + resolution: resolution, + rotation: rotation + }); +}; /** - * Swap location parameters and 'left'/'right' in CSS specification. The - * processed string will be suited for RTL layout. Though this function can - * cover most cases, there are always exceptions. It is suggested to put - * those exceptions in separate group of CSS string. - * @param {string} cssStr CSS spefication string. - * @return {string} Processed CSS specification string. + * Get the current zoom level. Return undefined if the current + * resolution is undefined or not a "constrained resolution". + * @return {number|undefined} Zoom. + * @api stable */ -goog.i18n.bidi.mirrorCSS = function(cssStr) { - return cssStr. - // reverse dimensions - replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2'). - replace(goog.i18n.bidi.leftRe_, '%%%%'). // swap left and right - replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT). - replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT); +ol.View.prototype.getZoom = function() { + var offset; + var resolution = this.getResolution(); + + if (goog.isDef(resolution)) { + var res, z = 0; + do { + res = this.constrainResolution(this.maxResolution_, z); + if (res == resolution) { + offset = z; + break; + } + ++z; + } while (res > this.minResolution_); + } + + return goog.isDef(offset) ? this.minZoom_ + offset : offset; }; /** - * Regular expression for hebrew double quote substitution, finding quote - * directly after hebrew characters. - * @type {RegExp} - * @private + * Fit the given geometry or extent based on the given map size and border. + * The size is pixel dimensions of the box to fit the extent into. + * In most cases you will want to use the map size, that is `map.getSize()`. + * Takes care of the map angle. + * @param {ol.geom.SimpleGeometry|ol.Extent} geometry Geometry. + * @param {ol.Size} size Box pixel size. + * @param {olx.view.FitOptions=} opt_options Options. + * @api */ -goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g; +ol.View.prototype.fit = function(geometry, size, opt_options) { + if (!(geometry instanceof ol.geom.SimpleGeometry)) { + goog.asserts.assert(goog.isArray(geometry), + 'invalid extent or geometry'); + goog.asserts.assert(!ol.extent.isEmpty(geometry), + 'cannot fit empty extent'); + geometry = ol.geom.Polygon.fromExtent(geometry); + } + + var options = goog.isDef(opt_options) ? opt_options : {}; + + var padding = goog.isDef(options.padding) ? options.padding : [0, 0, 0, 0]; + var constrainResolution = goog.isDef(options.constrainResolution) ? + options.constrainResolution : true; + var nearest = goog.isDef(options.nearest) ? options.nearest : false; + var minResolution; + if (goog.isDef(options.minResolution)) { + minResolution = options.minResolution; + } else if (goog.isDef(options.maxZoom)) { + minResolution = this.constrainResolution( + this.maxResolution_, options.maxZoom - this.minZoom_, 0); + } else { + minResolution = 0; + } + var coords = geometry.getFlatCoordinates(); + + // calculate rotated extent + var rotation = this.getRotation(); + goog.asserts.assert(goog.isDef(rotation), 'rotation was not defined'); + var cosAngle = Math.cos(-rotation); + var sinAngle = Math.sin(-rotation); + var minRotX = +Infinity; + var minRotY = +Infinity; + var maxRotX = -Infinity; + var maxRotY = -Infinity; + var stride = geometry.getStride(); + for (var i = 0, ii = coords.length; i < ii; i += stride) { + var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle; + var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle; + minRotX = Math.min(minRotX, rotX); + minRotY = Math.min(minRotY, rotY); + maxRotX = Math.max(maxRotX, rotX); + maxRotY = Math.max(maxRotY, rotY); + } + + // calculate resolution + var resolution = this.getResolutionForExtent( + [minRotX, minRotY, maxRotX, maxRotY], + [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]); + resolution = isNaN(resolution) ? minResolution : + Math.max(resolution, minResolution); + if (constrainResolution) { + var constrainedResolution = this.constrainResolution(resolution, 0, 0); + if (!nearest && constrainedResolution < resolution) { + constrainedResolution = this.constrainResolution( + constrainedResolution, -1, 0); + } + resolution = constrainedResolution; + } + this.setResolution(resolution); + + // calculate center + sinAngle = -sinAngle; // go back to original rotation + var centerRotX = (minRotX + maxRotX) / 2; + var centerRotY = (minRotY + maxRotY) / 2; + centerRotX += (padding[1] - padding[3]) / 2 * resolution; + centerRotY += (padding[0] - padding[2]) / 2 * resolution; + var centerX = centerRotX * cosAngle - centerRotY * sinAngle; + var centerY = centerRotY * cosAngle + centerRotX * sinAngle; + + this.setCenter([centerX, centerY]); +}; /** - * Regular expression for hebrew single quote substitution, finding quote - * directly after hebrew characters. - * @type {RegExp} - * @private + * Center on coordinate and view position. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Size} size Box pixel size. + * @param {ol.Pixel} position Position on the view to center on. + * @api */ -goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g; +ol.View.prototype.centerOn = function(coordinate, size, position) { + // calculate rotated position + var rotation = this.getRotation(); + var cosAngle = Math.cos(-rotation); + var sinAngle = Math.sin(-rotation); + var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle; + var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle; + var resolution = this.getResolution(); + rotX += (size[0] / 2 - position[0]) * resolution; + rotY += (position[1] - size[1] / 2) * resolution; + + // go back to original angle + sinAngle = -sinAngle; // go back to original rotation + var centerX = rotX * cosAngle - rotY * sinAngle; + var centerY = rotY * cosAngle + rotX * sinAngle; + + this.setCenter([centerX, centerY]); +}; /** - * Replace the double and single quote directly after a Hebrew character with - * GERESH and GERSHAYIM. In such case, most likely that's user intention. - * @param {string} str String that need to be processed. - * @return {string} Processed string with double/single quote replaced. + * @return {boolean} Is defined. */ -goog.i18n.bidi.normalizeHebrewQuote = function(str) { - return str. - replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4'). - replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3'); +ol.View.prototype.isDef = function() { + return goog.isDefAndNotNull(this.getCenter()) && + goog.isDef(this.getResolution()); }; /** - * Regular expression to split a string into "words" for directionality - * estimation based on relative word counts. - * @type {RegExp} - * @private + * Rotate the view around a given coordinate. + * @param {number} rotation New rotation value for the view. + * @param {ol.Coordinate=} opt_anchor The rotation center. + * @api stable */ -goog.i18n.bidi.wordSeparatorRe_ = /\s+/; +ol.View.prototype.rotate = function(rotation, opt_anchor) { + if (goog.isDef(opt_anchor)) { + var center = this.calculateCenterRotate(rotation, opt_anchor); + this.setCenter(center); + } + this.setRotation(rotation); +}; /** - * Regular expression to check if a string contains any numerals. Used to - * differentiate between completely neutral strings and those containing - * numbers, which are weakly LTR. - * @type {RegExp} - * @private + * Set the center of the current view. + * @param {ol.Coordinate|undefined} center The center of the view. + * @observable + * @api stable */ -goog.i18n.bidi.hasNumeralsRe_ = /\d/; +ol.View.prototype.setCenter = function(center) { + this.set(ol.ViewProperty.CENTER, center); +}; /** - * This constant controls threshold of RTL directionality. - * @type {number} - * @private + * @param {ol.ViewHint} hint Hint. + * @param {number} delta Delta. + * @return {number} New value. */ -goog.i18n.bidi.rtlDetectionThreshold_ = 0.40; +ol.View.prototype.setHint = function(hint, delta) { + goog.asserts.assert(0 <= hint && hint < this.hints_.length, + 'illegal hint (%s), must be between 0 and %s', hint, this.hints_.length); + this.hints_[hint] += delta; + goog.asserts.assert(this.hints_[hint] >= 0, + 'Hint at %s must be positive, was %s', hint, this.hints_[hint]); + return this.hints_[hint]; +}; /** - * Estimates the directionality of a string based on relative word counts. - * If the number of RTL words is above a certain percentage of the total number - * of strongly directional words, returns RTL. - * Otherwise, if any words are strongly or weakly LTR, returns LTR. - * Otherwise, returns UNKNOWN, which is used to mean "neutral". - * Numbers are counted as weakly LTR. - * @param {string} str The string to be checked. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. + * Set the resolution for this view. + * @param {number|undefined} resolution The resolution of the view. + * @observable + * @api stable */ -goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) { - var rtlCount = 0; - var totalCount = 0; - var hasWeaklyLtr = false; - var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml). - split(goog.i18n.bidi.wordSeparatorRe_); - for (var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - if (goog.i18n.bidi.startsWithRtl(token)) { - rtlCount++; - totalCount++; - } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) { - hasWeaklyLtr = true; - } else if (goog.i18n.bidi.hasAnyLtr(token)) { - totalCount++; - } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) { - hasWeaklyLtr = true; - } - } - - return totalCount == 0 ? - (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) : - (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ? - goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR); +ol.View.prototype.setResolution = function(resolution) { + this.set(ol.ViewProperty.RESOLUTION, resolution); }; /** - * Check the directionality of a piece of text, return true if the piece of - * text should be laid out in RTL direction. - * @param {string} str The piece of text that need to be detected. - * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. - * Default: false. - * @return {boolean} Whether this piece of text should be laid out in RTL. + * Set the rotation for this view. + * @param {number} rotation The rotation of the view in radians. + * @observable + * @api stable */ -goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) { - return goog.i18n.bidi.estimateDirection(str, opt_isHtml) == - goog.i18n.bidi.Dir.RTL; +ol.View.prototype.setRotation = function(rotation) { + this.set(ol.ViewProperty.ROTATION, rotation); }; /** - * Sets text input element's directionality and text alignment based on a - * given directionality. Does nothing if the given directionality is unknown or - * neutral. - * @param {Element} element Input field element to set directionality to. - * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality, - * given in one of the following formats: - * 1. A goog.i18n.bidi.Dir constant. - * 2. A number (positive = LRT, negative = RTL, 0 = neutral). - * 3. A boolean (true = RTL, false = LTR). - * 4. A null for unknown directionality. + * Zoom to a specific zoom level. + * @param {number} zoom Zoom level. + * @api stable */ -goog.i18n.bidi.setElementDirAndAlign = function(element, dir) { - if (element) { - dir = goog.i18n.bidi.toDir(dir); - if (dir) { - element.style.textAlign = - dir == goog.i18n.bidi.Dir.RTL ? - goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT; - element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; - } - } +ol.View.prototype.setZoom = function(zoom) { + var resolution = this.constrainResolution( + this.maxResolution_, zoom - this.minZoom_, 0); + this.setResolution(resolution); }; - /** - * Strings that have an (optional) known direction. - * - * Implementations of this interface are string-like objects that carry an - * attached direction, if known. - * @interface + * @param {olx.ViewOptions} options View options. + * @private + * @return {ol.CenterConstraintType} */ -goog.i18n.bidi.DirectionalString = function() {}; +ol.View.createCenterConstraint_ = function(options) { + if (goog.isDef(options.extent)) { + return ol.CenterConstraint.createExtent(options.extent); + } else { + return ol.CenterConstraint.none; + } +}; /** - * Interface marker of the DirectionalString interface. - * - * This property can be used to determine at runtime whether or not an object - * implements this interface. All implementations of this interface set this - * property to {@code true}. - * @type {boolean} + * @private + * @param {olx.ViewOptions} options View options. + * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number, + * minResolution: number}} */ -goog.i18n.bidi.DirectionalString.prototype. - implementsGoogI18nBidiDirectionalString; +ol.View.createResolutionConstraint_ = function(options) { + var resolutionConstraint; + var maxResolution; + var minResolution; + + // TODO: move these to be ol constants + // see https://github.com/openlayers/ol3/issues/2076 + var defaultMaxZoom = 28; + var defaultZoomFactor = 2; + + var minZoom = goog.isDef(options.minZoom) ? + options.minZoom : ol.DEFAULT_MIN_ZOOM; + + var maxZoom = goog.isDef(options.maxZoom) ? + options.maxZoom : defaultMaxZoom; + + var zoomFactor = goog.isDef(options.zoomFactor) ? + options.zoomFactor : defaultZoomFactor; + + if (goog.isDef(options.resolutions)) { + var resolutions = options.resolutions; + maxResolution = resolutions[0]; + minResolution = resolutions[resolutions.length - 1]; + resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions( + resolutions); + } else { + // calculate the default min and max resolution + var projection = ol.proj.createProjection(options.projection, 'EPSG:3857'); + var extent = projection.getExtent(); + var size = goog.isNull(extent) ? + // use an extent that can fit the whole world if need be + 360 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + ol.proj.METERS_PER_UNIT[projection.getUnits()] : + Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent)); + + var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow( + defaultZoomFactor, ol.DEFAULT_MIN_ZOOM); + + var defaultMinResolution = defaultMaxResolution / Math.pow( + defaultZoomFactor, defaultMaxZoom - ol.DEFAULT_MIN_ZOOM); + + // user provided maxResolution takes precedence + maxResolution = options.maxResolution; + if (goog.isDef(maxResolution)) { + minZoom = 0; + } else { + maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom); + } + + // user provided minResolution takes precedence + minResolution = options.minResolution; + if (!goog.isDef(minResolution)) { + if (goog.isDef(options.maxZoom)) { + if (goog.isDef(options.maxResolution)) { + minResolution = maxResolution / Math.pow(zoomFactor, maxZoom); + } else { + minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom); + } + } else { + minResolution = defaultMinResolution; + } + } + + // given discrete zoom levels, minResolution may be different than provided + maxZoom = minZoom + Math.floor( + Math.log(maxResolution / minResolution) / Math.log(zoomFactor)); + minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom); + + resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( + zoomFactor, maxResolution, maxZoom - minZoom); + } + return {constraint: resolutionConstraint, maxResolution: maxResolution, + minResolution: minResolution, minZoom: minZoom}; +}; /** - * Retrieves this object's known direction (if any). - * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown. + * @private + * @param {olx.ViewOptions} options View options. + * @return {ol.RotationConstraintType} Rotation constraint. */ -goog.i18n.bidi.DirectionalString.prototype.getDirection; +ol.View.createRotationConstraint_ = function(options) { + var enableRotation = goog.isDef(options.enableRotation) ? + options.enableRotation : true; + if (enableRotation) { + var constrainRotation = options.constrainRotation; + if (!goog.isDef(constrainRotation) || constrainRotation === true) { + return ol.RotationConstraint.createSnapToZero(); + } else if (constrainRotation === false) { + return ol.RotationConstraint.none; + } else if (goog.isNumber(constrainRotation)) { + return ol.RotationConstraint.createSnapToN(constrainRotation); + } else { + goog.asserts.fail( + 'illegal option for constrainRotation (%s)', constrainRotation); + return ol.RotationConstraint.none; + } + } else { + return ol.RotationConstraint.disable; + } +}; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22557,1315 +22964,1235 @@ goog.i18n.bidi.DirectionalString.prototype.getDirection; // limitations under the License. /** - * @fileoverview The SafeUrl type and its builders. + * @fileoverview Easing functions for animations. * - * TODO(user): Link to document stating type contract. + * @author arv@google.com (Erik Arvidsson) */ -goog.provide('goog.html.SafeUrl'); +goog.provide('goog.fx.easing'); -goog.require('goog.asserts'); -goog.require('goog.i18n.bidi.Dir'); -goog.require('goog.i18n.bidi.DirectionalString'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); +/** + * Ease in - Start slow and speed up. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + */ +goog.fx.easing.easeIn = function(t) { + return goog.fx.easing.easeInInternal_(t, 3); +}; /** - * A string that is safe to use in URL context in DOM APIs and HTML documents. - * - * A SafeUrl is a string-like object that carries the security type contract - * that its value as a string will not cause untrusted script execution - * when evaluated as a hyperlink URL in a browser. - * - * Values of this type are guaranteed to be safe to use in URL/hyperlink - * contexts, such as, assignment to URL-valued DOM properties, or - * interpolation into a HTML template in URL context (e.g., inside a href - * attribute), in the sense that the use will not result in a - * Cross-Site-Scripting vulnerability. - * - * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's - * contract does not guarantee that instances are safe to interpolate into HTML - * without appropriate escaping. - * - * Note also that this type's contract does not imply any guarantees regarding - * the resource the URL refers to. In particular, SafeUrls are <b>not</b> - * safe to use in a context where the referred-to resource is interpreted as - * trusted code, e.g., as the src of a script tag. - * - * Instances of this type must be created via the factory methods - * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}), - * etc and not by invoking its constructor. The constructor intentionally - * takes no parameters and the type is immutable; hence only a default instance - * corresponding to the empty string can be obtained via constructor invocation. - * - * @see goog.html.SafeUrl#fromConstant - * @see goog.html.SafeUrl#from - * @see goog.html.SafeUrl#sanitize - * @constructor - * @final - * @struct - * @implements {goog.i18n.bidi.DirectionalString} - * @implements {goog.string.TypedString} + * Ease in with specifiable exponent. + * @param {number} t Input between 0 and 1. + * @param {number} exp Ease exponent. + * @return {number} Output between 0 and 1. + * @private */ -goog.html.SafeUrl = function() { - /** - * The contained value of this SafeUrl. The field has a purposely ugly - * name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; +goog.fx.easing.easeInInternal_ = function(t, exp) { + return Math.pow(t, exp); +}; - /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeUrl#unwrap - * @const - * @private - */ - this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + +/** + * Ease out - Start fastest and slows to a stop. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + */ +goog.fx.easing.easeOut = function(t) { + return goog.fx.easing.easeOutInternal_(t, 3); }; /** - * The innocuous string generated by goog.html.SafeUrl.sanitize when passed - * an unsafe URL. - * - * about:invalid is registered in - * http://www.w3.org/TR/css3-values/#about-invalid. - * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to - * contain a fragment, which is not to be considered when determining if an - * about URL is well-known. - * - * Using about:invalid seems preferable to using a fixed data URL, since - * browsers might choose to not report CSP violations on it, as legitimate - * CSS function calls to attr() can result in this URL being produced. It is - * also a standard URL which matches exactly the semantics we need: - * "The about:invalid URI references a non-existent document with a generic - * error condition. It can be used when a URI is necessary, but the default - * value shouldn't be resolveable as any type of document". - * - * @const {string} + * Ease out with specifiable exponent. + * @param {number} t Input between 0 and 1. + * @param {number} exp Ease exponent. + * @return {number} Output between 0 and 1. + * @private */ -goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez'; +goog.fx.easing.easeOutInternal_ = function(t, exp) { + return 1 - goog.fx.easing.easeInInternal_(1 - t, exp); +}; /** - * @override - * @const + * Ease out long - Start fastest and slows to a stop with a long ease. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. */ -goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true; +goog.fx.easing.easeOutLong = function(t) { + return goog.fx.easing.easeOutInternal_(t, 4); +}; /** - * Returns this SafeUrl's value a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this - * method. If in doubt, assume that it's security relevant. In particular, note - * that goog.html functions which return a goog.html type do not guarantee that - * the returned instance is of the right type. For example: - * - * <pre> - * var fakeSafeHtml = new String('fake'); - * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; - * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); - * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by - * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof - * // goog.html.SafeHtml. - * </pre> - * - * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the - * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST - * be appropriately escaped before embedding in a HTML document. Note that the - * required escaping is context-sensitive (e.g. a different escaping is - * required for embedding a URL in a style property within a style - * attribute, as opposed to embedding in a href attribute). - * - * @see goog.html.SafeUrl#unwrap - * @override + * Ease in and out - Start slow, speed up, then slow down. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. */ -goog.html.SafeUrl.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +goog.fx.easing.inAndOut = function(t) { + return 3 * t * t - 2 * t * t * t; }; +goog.provide('ol.easing'); + +goog.require('goog.fx.easing'); + /** - * @override - * @const + * Start slow and speed up. + * @function + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api */ -goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true; +ol.easing.easeIn = goog.fx.easing.easeIn; /** - * Returns this URLs directionality, which is always {@code LTR}. - * @override + * Start fast and slow down. + * @function + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api */ -goog.html.SafeUrl.prototype.getDirection = function() { - return goog.i18n.bidi.Dir.LTR; -}; +ol.easing.easeOut = goog.fx.easing.easeOut; -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeUrl, use - * {@code goog.html.SafeUrl.unwrap}. - * - * @see goog.html.SafeUrl#unwrap - * @override - */ - goog.html.SafeUrl.prototype.toString = function() { - return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + - '}'; - }; -} +/** + * Start slow, speed up, and then slow down again. + * @function + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.inAndOut = goog.fx.easing.inAndOut; /** - * Performs a runtime check that the provided object is indeed a SafeUrl - * object, and returns its value. - * - * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the - * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST - * be appropriately escaped before embedding in a HTML document. Note that the - * required escaping is context-sensitive (e.g. a different escaping is - * required for embedding a URL in a style property within a style - * attribute, as opposed to embedding in a href attribute). - * - * Note that the returned value does not necessarily correspond to the string - * with which the SafeUrl was constructed, since goog.html.SafeUrl.sanitize - * will percent-encode many characters. - * - * @param {!goog.html.SafeUrl} safeUrl The object to extract from. - * @return {string} The SafeUrl object's contained string, unless the run-time - * type check fails. In that case, {@code unwrap} returns an innocuous - * string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. + * Maintain a constant speed over time. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api */ -goog.html.SafeUrl.unwrap = function(safeUrl) { - // Perform additional Run-time type-checking to ensure that safeUrl is indeed - // an instance of the expected type. This provides some additional protection - // against security bugs due to application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeUrl instanceof goog.html.SafeUrl && - safeUrl.constructor === goog.html.SafeUrl && - safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_; - } else { - goog.asserts.fail('expected object of type SafeUrl, got \'' + - safeUrl + '\''); - return 'type_error:SafeUrl'; +ol.easing.linear = function(t) { + return t; +}; + +/** + * Start slow, speed up, and at the very end slow down again. This has the + * same general behavior as {@link ol.easing.inAndOut}, but the final slowdown + * is delayed. + * @param {number} t Input between 0 and 1. + * @return {number} Output between 0 and 1. + * @api + */ +ol.easing.upAndDown = function(t) { + if (t < 0.5) { + return ol.easing.inAndOut(2 * t); + } else { + return 1 - ol.easing.inAndOut(2 * (t - 0.5)); } }; +goog.provide('ol.animation'); + +goog.require('ol.PreRenderFunction'); +goog.require('ol.ViewHint'); +goog.require('ol.coordinate'); +goog.require('ol.easing'); + /** - * Creates a SafeUrl object from a compile-time constant string. - * - * Compile-time constant strings are inherently program-controlled and hence - * trusted. - * - * @param {!goog.string.Const} url A compile-time-constant string from which to - * create a SafeUrl. - * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}. + * Generate an animated transition that will "bounce" the resolution as it + * approaches the final value. + * @param {olx.animation.BounceOptions} options Bounce options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api */ -goog.html.SafeUrl.fromConstant = function(url) { - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( - goog.string.Const.unwrap(url)); +ol.animation.bounce = function(options) { + var resolution = options.resolution; + var start = goog.isDef(options.start) ? options.start : goog.now(); + var duration = goog.isDef(options.duration) ? options.duration : 1000; + var easing = goog.isDef(options.easing) ? + options.easing : ol.easing.upAndDown; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = easing((frameState.time - start) / duration); + var deltaResolution = resolution - frameState.viewState.resolution; + frameState.animate = true; + frameState.viewState.resolution += delta * deltaResolution; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); }; /** - * A pattern that recognizes a commonly useful subset of URLs that satisfy - * the SafeUrl contract. - * - * This regular expression matches a subset of URLs that will not cause script - * execution if used in URL context within a HTML document. Specifically, this - * regular expression matches if (comment from here on and regex copied from - * Soy's EscapingConventions): - * (1) Either a protocol in a whitelist (http, https, mailto). - * (2) or no protocol. A protocol must be followed by a colon. The below - * allows that by allowing colons only after one of the characters [/?#]. - * A colon after a hash (#) must be in the fragment. - * Otherwise, a colon after a (?) must be in a query. - * Otherwise, a colon after a single solidus (/) must be in a path. - * Otherwise, a colon after a double solidus (//) must be in the authority - * (before port). - * - * The pattern disallows &, used in HTML entity declarations before - * one of the characters in [/?#]. This disallows HTML entities used in the - * protocol name, which should never happen, e.g. "http" for "http". - * It also disallows HTML entities in the first path part of a relative path, - * e.g. "foo<bar/baz". Our existing escaping functions should not produce - * that. More importantly, it disallows masking of a colon, - * e.g. "javascript:...". - * - * @private - * @const {!RegExp} + * Generate an animated transition while updating the view center. + * @param {olx.animation.PanOptions} options Pan options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api */ -goog.html.SAFE_URL_PATTERN_ = /^(?:(?:https?|mailto):|[^&:/?#]*(?:[/?#]|$))/i; +ol.animation.pan = function(options) { + var source = options.source; + var start = goog.isDef(options.start) ? options.start : goog.now(); + var sourceX = source[0]; + var sourceY = source[1]; + var duration = goog.isDef(options.duration) ? options.duration : 1000; + var easing = goog.isDef(options.easing) ? + options.easing : ol.easing.inAndOut; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaX = sourceX - frameState.viewState.center[0]; + var deltaY = sourceY - frameState.viewState.center[1]; + frameState.animate = true; + frameState.viewState.center[0] += delta * deltaX; + frameState.viewState.center[1] += delta * deltaY; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); +}; /** - * Creates a SafeUrl object from {@code url}. If {@code url} is a - * goog.html.SafeUrl then it is simply returned. Otherwise the input string is - * validated to match a pattern of commonly used safe URLs. The string is - * converted to UTF-8 and non-whitelisted characters are percent-encoded. The - * string wrapped by the created SafeUrl will thus contain only ASCII printable - * characters. - * - * {@code url} may be a URL with the http, https, or mailto scheme, - * or a relative URL (i.e., a URL without a scheme; specifically, a - * scheme-relative, absolute-path-relative, or path-relative URL). - * - * {@code url} is converted to UTF-8 and non-whitelisted characters are - * percent-encoded. Whitelisted characters are '%' and, from RFC 3986, - * unreserved characters and reserved characters, with the exception of '\'', - * '(' and ')'. This ensures the the SafeUrl contains only ASCII-printable - * characters and reduces the chance of security bugs were it to be - * interpolated into a specific context without the necessary escaping. - * - * If {@code url} fails validation or does not UTF-16 decode correctly - * (JavaScript strings are UTF-16 encoded), this function returns a SafeUrl - * object containing an innocuous string, goog.html.SafeUrl.INNOCUOUS_STRING. - * - * @see http://url.spec.whatwg.org/#concept-relative-url - * @param {string|!goog.string.TypedString} url The URL to validate. - * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl. + * Generate an animated transition while updating the view rotation. + * @param {olx.animation.RotateOptions} options Rotate options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api */ -goog.html.SafeUrl.sanitize = function(url) { - if (url instanceof goog.html.SafeUrl) { - return url; - } - else if (url.implementsGoogStringTypedString) { - url = url.getTypedStringValue(); - } else { - url = String(url); - } - if (!goog.html.SAFE_URL_PATTERN_.test(url)) { - url = goog.html.SafeUrl.INNOCUOUS_STRING; - } else { - url = goog.html.SafeUrl.normalize_(url); - } - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +ol.animation.rotate = function(options) { + var sourceRotation = goog.isDef(options.rotation) ? options.rotation : 0; + var start = goog.isDef(options.start) ? options.start : goog.now(); + var duration = goog.isDef(options.duration) ? options.duration : 1000; + var easing = goog.isDef(options.easing) ? + options.easing : ol.easing.inAndOut; + var anchor = goog.isDef(options.anchor) ? + options.anchor : null; + + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaRotation = + (sourceRotation - frameState.viewState.rotation) * delta; + frameState.animate = true; + frameState.viewState.rotation += deltaRotation; + if (!goog.isNull(anchor)) { + var center = frameState.viewState.center; + ol.coordinate.sub(center, anchor); + ol.coordinate.rotate(center, deltaRotation); + ol.coordinate.add(center, anchor); + } + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } + }); }; /** - * Normalizes {@code url} the UTF-8 encoding of url, using a whitelist of - * characters. Whitelisted characters are not percent-encoded. - * @param {string} url The URL to normalize. - * @return {string} The normalized URL. - * @private + * Generate an animated transition while updating the view resolution. + * @param {olx.animation.ZoomOptions} options Zoom options. + * @return {ol.PreRenderFunction} Pre-render function. + * @api */ -goog.html.SafeUrl.normalize_ = function(url) { - try { - var normalized = encodeURI(url); - } catch (e) { // Happens if url contains invalid surrogate sequences. - return goog.html.SafeUrl.INNOCUOUS_STRING; - } - - return normalized.replace( - goog.html.SafeUrl.NORMALIZE_MATCHER_, - function(match) { - return goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_[match]; +ol.animation.zoom = function(options) { + var sourceResolution = options.resolution; + var start = goog.isDef(options.start) ? options.start : goog.now(); + var duration = goog.isDef(options.duration) ? options.duration : 1000; + var easing = goog.isDef(options.easing) ? + options.easing : ol.easing.inAndOut; + return ( + /** + * @param {ol.Map} map Map. + * @param {?olx.FrameState} frameState Frame state. + */ + function(map, frameState) { + if (frameState.time < start) { + frameState.animate = true; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else if (frameState.time < start + duration) { + var delta = 1 - easing((frameState.time - start) / duration); + var deltaResolution = + sourceResolution - frameState.viewState.resolution; + frameState.animate = true; + frameState.viewState.resolution += delta * deltaResolution; + frameState.viewHints[ol.ViewHint.ANIMATING] += 1; + return true; + } else { + return false; + } }); }; +goog.provide('ol.TileCoord'); +goog.provide('ol.tilecoord'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.extent'); + /** - * Matches characters and strings which need to be replaced in the string - * generated by encodeURI. Specifically: - * - * - '\'', '(' and ')' are not encoded. They are part of the reserved - * characters group in RFC 3986 but only appear in the obsolete mark - * production in Appendix D.2 of RFC 3986, so they can be encoded without - * changing semantics. - * - '[' and ']' are encoded by encodeURI, despite being reserved characters - * which can be used to represent IPv6 addresses. So they need to be decoded. - * - '%' is encoded by encodeURI. However, encoding '%' characters that are - * already part of a valid percent-encoded sequence changes the semantics of a - * URL, and hence we need to preserve them. Note that this may allow - * non-encoded '%' characters to remain in the URL (i.e., occurrences of '%' - * that are not part of a valid percent-encoded sequence, for example, - * 'ab%xy'). - * - * @const {!RegExp} - * @private + * An array of three numbers representing the location of a tile in a tile + * grid. The order is `z`, `x`, and `y`. `z` is the zoom level. + * @typedef {Array.<number>} ol.TileCoord + * @api */ -goog.html.SafeUrl.NORMALIZE_MATCHER_ = /[()']|%5B|%5D|%25/g; +ol.TileCoord; /** - * Map of replacements to be done in string generated by encodeURI. - * @const {!Object<string, string>} - * @private + * @enum {number} */ -goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_ = { - '\'': '%27', - '(': '%28', - ')': '%29', - '%5B': '[', - '%5D': ']', - '%25': '%' +ol.QuadKeyCharCode = { + ZERO: '0'.charCodeAt(0), + ONE: '1'.charCodeAt(0), + TWO: '2'.charCodeAt(0), + THREE: '3'.charCodeAt(0) }; /** - * Type marker for the SafeUrl type, used to implement additional run-time - * type checking. - * @const - * @private + * @param {string} str String that follows pattern “z/x/y” where x, y and z are + * numbers. + * @return {ol.TileCoord} Tile coord. */ -goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +ol.tilecoord.createFromString = function(str) { + var v = str.split('/'); + goog.asserts.assert(v.length === 3, + 'must provide a string in "z/x/y" format, got "%s"', str); + v = goog.array.map(v, function(e, i, a) { + return parseInt(e, 10); + }); + return v; +}; /** - * Package-internal utility method to create SafeUrl instances. - * - * @param {string} url The string to initialize the SafeUrl object with. - * @return {!goog.html.SafeUrl} The initialized SafeUrl object. - * @package + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. + * @return {ol.TileCoord} Tile coordinate. */ -goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function( - url) { - var safeUrl = new goog.html.SafeUrl(); - safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url; - return safeUrl; +ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) { + if (goog.isDef(opt_tileCoord)) { + opt_tileCoord[0] = z; + opt_tileCoord[1] = x; + opt_tileCoord[2] = y; + return opt_tileCoord; + } else { + return [z, x, y]; + } }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview The TrustedResourceUrl type and its builders. - * - * TODO(user): Link to document stating type contract. + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. */ +ol.tilecoord.getKeyZXY = function(z, x, y) { + return z + '/' + x + '/' + y; +}; -goog.provide('goog.html.TrustedResourceUrl'); - -goog.require('goog.asserts'); -goog.require('goog.i18n.bidi.Dir'); -goog.require('goog.i18n.bidi.DirectionalString'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); +/** + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {number} Hash. + */ +ol.tilecoord.hash = function(tileCoord) { + return (tileCoord[1] << tileCoord[0]) + tileCoord[2]; +}; /** - * A URL which is under application control and from which script, CSS, and - * other resources that represent executable code, can be fetched. - * - * Given that the URL can only be constructed from strings under application - * control and is used to load resources, bugs resulting in a malformed URL - * should not have a security impact and are likely to be easily detectable - * during testing. Given the wide number of non-RFC compliant URLs in use, - * stricter validation could prevent some applications from being able to use - * this type. - * - * Instances of this type must be created via the factory method, - * ({@code goog.html.TrustedResourceUrl.fromConstant}), and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty - * string can be obtained via constructor invocation. - * - * @see goog.html.TrustedResourceUrl#fromConstant - * @constructor - * @final - * @struct - * @implements {goog.i18n.bidi.DirectionalString} - * @implements {goog.string.TypedString} + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {string} Quad key. */ -goog.html.TrustedResourceUrl = function() { - /** - * The contained value of this TrustedResourceUrl. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} +ol.tilecoord.quadKey = function(tileCoord) { + var z = tileCoord[0]; + var digits = new Array(z); + var mask = 1 << (z - 1); + var i, charCode; + for (i = 0; i < z; ++i) { + charCode = ol.QuadKeyCharCode.ZERO; + if (tileCoord[1] & mask) { + charCode += 1; + } + if (tileCoord[2] & mask) { + charCode += 2; + } + digits[i] = String.fromCharCode(charCode); + mask >>= 1; + } + return digits.join(''); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coord. + * @return {string} String. + */ +ol.tilecoord.toString = function(tileCoord) { + return ol.tilecoord.getKeyZXY(tileCoord[0], tileCoord[1], tileCoord[2]); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCoord} Tile coordinate. + */ +ol.tilecoord.wrapX = function(tileCoord, tileGrid, projection) { + var z = tileCoord[0]; + var center = tileGrid.getTileCoordCenter(tileCoord); + var projectionExtent = ol.tilegrid.extentFromProjection(projection); + if (!ol.extent.containsCoordinate(projectionExtent, center)) { + var worldWidth = ol.extent.getWidth(projectionExtent); + var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth); + center[0] += worldWidth * worldsAway; + return tileGrid.getTileCoordForCoordAndZ(center, z); + } else { + return tileCoord; + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {boolean} Tile coordinate is within extent and zoom level range. + */ +ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) { + var z = tileCoord[0]; + var x = tileCoord[1]; + var y = tileCoord[2]; + + if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) { + return false; + } + var extent = tileGrid.getExtent(); + var tileRange; + if (goog.isNull(extent)) { + tileRange = tileGrid.getFullTileRange(z); + } else { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + } + if (goog.isNull(tileRange)) { + return true; + } else { + return tileRange.containsXY(x, y); + } +}; + +goog.provide('ol.TileRange'); + +goog.require('goog.asserts'); +goog.require('ol.Size'); +goog.require('ol.TileCoord'); + + + +/** + * A representation of a contiguous block of tiles. A tile range is specified + * by its min/max tile coordinates and is inclusive of coordinates. + * + * @constructor + * @param {number} minX Minimum X. + * @param {number} maxX Maximum X. + * @param {number} minY Minimum Y. + * @param {number} maxY Maximum Y. + * @struct + */ +ol.TileRange = function(minX, maxX, minY, maxY) { + + /** + * @type {number} */ - this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = ''; + this.minX = minX; /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.TrustedResourceUrl#unwrap - * @const - * @private + * @type {number} */ - this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + this.maxX = maxX; + + /** + * @type {number} + */ + this.minY = minY; + + /** + * @type {number} + */ + this.maxY = maxY; + }; /** - * @override - * @const + * @param {...ol.TileCoord} var_args Tile coordinates. + * @return {!ol.TileRange} Bounding tile box. */ -goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true; +ol.TileRange.boundingTileRange = function(var_args) { + var tileCoord0 = /** @type {ol.TileCoord} */ (arguments[0]); + var tileCoord0Z = tileCoord0[0]; + var tileCoord0X = tileCoord0[1]; + var tileCoord0Y = tileCoord0[2]; + var tileRange = new ol.TileRange(tileCoord0X, tileCoord0X, + tileCoord0Y, tileCoord0Y); + var i, ii, tileCoord, tileCoordX, tileCoordY, tileCoordZ; + for (i = 1, ii = arguments.length; i < ii; ++i) { + tileCoord = /** @type {ol.TileCoord} */ (arguments[i]); + tileCoordZ = tileCoord[0]; + tileCoordX = tileCoord[1]; + tileCoordY = tileCoord[2]; + goog.asserts.assert(tileCoordZ == tileCoord0Z, + 'passed tilecoords all have the same Z-value'); + tileRange.minX = Math.min(tileRange.minX, tileCoordX); + tileRange.maxX = Math.max(tileRange.maxX, tileCoordX); + tileRange.minY = Math.min(tileRange.minY, tileCoordY); + tileRange.maxY = Math.max(tileRange.maxY, tileCoordY); + } + return tileRange; +}; /** - * Returns this TrustedResourceUrl's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code TrustedResourceUrl}, use - * {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in - * doubt, assume that it's security relevant. In particular, note that - * goog.html functions which return a goog.html type do not guarantee that - * the returned instance is of the right type. For example: - * - * <pre> - * var fakeSafeHtml = new String('fake'); - * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; - * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); - * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by - * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof - * // goog.html.SafeHtml. - * </pre> - * - * @see goog.html.TrustedResourceUrl#unwrap - * @override + * @param {number} minX Minimum X. + * @param {number} maxX Maximum X. + * @param {number} minY Minimum Y. + * @param {number} maxY Maximum Y. + * @param {ol.TileRange|undefined} tileRange TileRange. + * @return {ol.TileRange} Tile range. */ -goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; +ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) { + if (goog.isDef(tileRange)) { + tileRange.minX = minX; + tileRange.maxX = maxX; + tileRange.minY = minY; + tileRange.maxY = maxY; + return tileRange; + } else { + return new ol.TileRange(minX, maxX, minY, maxY); + } }; /** - * @override - * @const + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {boolean} Contains tile coordinate. */ -goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString = - true; +ol.TileRange.prototype.contains = function(tileCoord) { + return this.containsXY(tileCoord[1], tileCoord[2]); +}; /** - * Returns this URLs directionality, which is always {@code LTR}. - * @override + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Contains. */ -goog.html.TrustedResourceUrl.prototype.getDirection = function() { - return goog.i18n.bidi.Dir.LTR; +ol.TileRange.prototype.containsTileRange = function(tileRange) { + return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX && + this.minY <= tileRange.minY && tileRange.maxY <= this.maxY; }; -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a TrustedResourceUrl, use - * {@code goog.html.TrustedResourceUrl.unwrap}. - * - * @see goog.html.TrustedResourceUrl#unwrap - * @override - */ - goog.html.TrustedResourceUrl.prototype.toString = function() { - return 'TrustedResourceUrl{' + - this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}'; - }; -} +/** + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @return {boolean} Contains coordinate. + */ +ol.TileRange.prototype.containsXY = function(x, y) { + return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY; +}; /** - * Performs a runtime check that the provided object is indeed a - * TrustedResourceUrl object, and returns its value. - * - * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to - * extract from. - * @return {string} The trustedResourceUrl object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Equals. */ -goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) { - // Perform additional Run-time type-checking to ensure that - // trustedResourceUrl is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl && - trustedResourceUrl.constructor === goog.html.TrustedResourceUrl && - trustedResourceUrl - .TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.TrustedResourceUrl - .TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return trustedResourceUrl - .privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; - } else { - goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' + - trustedResourceUrl + '\''); - return 'type_error:TrustedResourceUrl'; +ol.TileRange.prototype.equals = function(tileRange) { + return this.minX == tileRange.minX && this.minY == tileRange.minY && + this.maxX == tileRange.maxX && this.maxY == tileRange.maxY; +}; + +/** + * @param {ol.TileRange} tileRange Tile range. + */ +ol.TileRange.prototype.extend = function(tileRange) { + if (tileRange.minX < this.minX) { + this.minX = tileRange.minX; + } + if (tileRange.maxX > this.maxX) { + this.maxX = tileRange.maxX; + } + if (tileRange.minY < this.minY) { + this.minY = tileRange.minY; + } + if (tileRange.maxY > this.maxY) { + this.maxY = tileRange.maxY; } }; /** - * Creates a TrustedResourceUrl object from a compile-time constant string. - * - * Compile-time constant strings are inherently program-controlled and hence - * trusted. - * - * @param {!goog.string.Const} url A compile-time-constant string from which to - * create a TrustedResourceUrl. - * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object - * initialized to {@code url}. + * @return {number} Height. */ -goog.html.TrustedResourceUrl.fromConstant = function(url) { - return goog.html.TrustedResourceUrl - .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse( - goog.string.Const.unwrap(url)); +ol.TileRange.prototype.getHeight = function() { + return this.maxY - this.minY + 1; }; /** - * Type marker for the TrustedResourceUrl type, used to implement additional - * run-time type checking. - * @const - * @private + * @return {ol.Size} Size. */ -goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +ol.TileRange.prototype.getSize = function() { + return [this.getWidth(), this.getHeight()]; +}; /** - * Package-internal utility method to create TrustedResourceUrl instances. - * - * @param {string} url The string to initialize the TrustedResourceUrl object - * with. - * @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl - * object. - * @package + * @return {number} Width. */ -goog.html.TrustedResourceUrl. - createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) { - var trustedResourceUrl = new goog.html.TrustedResourceUrl(); - trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = - url; - return trustedResourceUrl; +ol.TileRange.prototype.getWidth = function() { + return this.maxX - this.minX + 1; }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - /** - * @fileoverview The SafeHtml type and its builders. - * - * TODO(user): Link to document stating type contract. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} Intersects. */ +ol.TileRange.prototype.intersects = function(tileRange) { + return this.minX <= tileRange.maxX && + this.maxX >= tileRange.minX && + this.minY <= tileRange.maxY && + this.maxY >= tileRange.minY; +}; -goog.provide('goog.html.SafeHtml'); +goog.provide('ol.Attribution'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.dom.tags'); -goog.require('goog.html.SafeStyle'); -goog.require('goog.html.SafeUrl'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.i18n.bidi.Dir'); -goog.require('goog.i18n.bidi.DirectionalString'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); +goog.require('goog.math'); +goog.require('ol.TileRange'); /** - * A string that is safe to use in HTML context in DOM APIs and HTML documents. - * - * A SafeHtml is a string-like object that carries the security type contract - * that its value as a string will not cause untrusted script execution when - * evaluated as HTML in a browser. + * @classdesc + * An attribution for a layer source. * - * Values of this type are guaranteed to be safe to use in HTML contexts, - * such as, assignment to the innerHTML DOM property, or interpolation into - * a HTML template in HTML PC_DATA context, in the sense that the use will not - * result in a Cross-Site-Scripting vulnerability. + * Example: * - * Instances of this type must be created via the factory methods - * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}), - * etc and not by invoking its constructor. The constructor intentionally - * takes no parameters and the type is immutable; hence only a default instance - * corresponding to the empty string can be obtained via constructor invocation. + * source: new ol.source.OSM({ + * attributions: [ + * new ol.Attribution({ + * html: 'All maps © ' + + * '<a href="http://www.opencyclemap.org/">OpenCycleMap</a>' + * }), + * ol.source.OSM.ATTRIBUTION + * ], + * .. * - * @see goog.html.SafeHtml#create - * @see goog.html.SafeHtml#htmlEscape * @constructor - * @final + * @param {olx.AttributionOptions} options Attribution options. * @struct - * @implements {goog.i18n.bidi.DirectionalString} - * @implements {goog.string.TypedString} + * @api stable */ -goog.html.SafeHtml = function() { - /** - * The contained value of this SafeHtml. The field has a purposely ugly - * name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; +ol.Attribution = function(options) { /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeHtml#unwrap - * @const * @private + * @type {string} */ - this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + this.html_ = options.html; /** - * This SafeHtml's directionality, or null if unknown. - * @private {?goog.i18n.bidi.Dir} + * @private + * @type {Object.<string, Array.<ol.TileRange>>} */ - this.dir_ = null; + this.tileRanges_ = goog.isDef(options.tileRanges) ? + options.tileRanges : null; + }; /** - * @override - * @const + * Get the attribution markup. + * @return {string} The attribution HTML. + * @api stable */ -goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true; - - -/** @override */ -goog.html.SafeHtml.prototype.getDirection = function() { - return this.dir_; +ol.Attribution.prototype.getHTML = function() { + return this.html_; }; /** - * @override - * @const + * @param {Object.<string, ol.TileRange>} tileRanges Tile ranges. + * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {!ol.proj.Projection} projection Projection. + * @return {boolean} Intersects any tile range. */ -goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true; +ol.Attribution.prototype.intersectsAnyTileRange = + function(tileRanges, tileGrid, projection) { + if (goog.isNull(this.tileRanges_)) { + return true; + } + var i, ii, tileRange, zKey; + for (zKey in tileRanges) { + if (!(zKey in this.tileRanges_)) { + continue; + } + tileRange = tileRanges[zKey]; + var testTileRange; + for (i = 0, ii = this.tileRanges_[zKey].length; i < ii; ++i) { + testTileRange = this.tileRanges_[zKey][i]; + if (testTileRange.intersects(tileRange)) { + return true; + } + var extentTileRange = tileGrid.getTileRangeForExtentAndZ( + projection.getExtent(), parseInt(zKey, 10)); + var width = extentTileRange.getWidth(); + if (tileRange.minX < extentTileRange.minX || + tileRange.maxX > extentTileRange.maxX) { + if (testTileRange.intersects(new ol.TileRange( + goog.math.modulo(tileRange.minX, width), + goog.math.modulo(tileRange.maxX, width), + tileRange.minY, tileRange.maxY))) { + return true; + } + if (tileRange.getWidth() > width && + testTileRange.intersects(extentTileRange)) { + return true; + } + } + } + } + return false; +}; + +goog.provide('ol.CanvasFunctionType'); /** - * Returns this SafeHtml's value a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of - * this method. If in doubt, assume that it's security relevant. In particular, - * note that goog.html functions which return a goog.html type do not guarantee - * that the returned instance is of the right type. For example: - * - * <pre> - * var fakeSafeHtml = new String('fake'); - * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; - * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); - * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by - * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml - * // instanceof goog.html.SafeHtml. - * </pre> + * A function returning the canvas element (`{HTMLCanvasElement}`) + * used by the source as an image. The arguments passed to the function are: + * {@link ol.Extent} the image extent, `{number}` the image resolution, + * `{number}` the device pixel ratio, {@link ol.Size} the image size, and + * {@link ol.proj.Projection} the image projection. The canvas returned by + * this function is cached by the source. The this keyword inside the function + * references the {@link ol.source.ImageCanvas}. * - * @see goog.html.SafeHtml#unwrap - * @override + * @typedef {function(this:ol.source.ImageCanvas, ol.Extent, number, + * number, ol.Size, ol.proj.Projection): HTMLCanvasElement} + * @api */ -goog.html.SafeHtml.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; -}; +ol.CanvasFunctionType; +/** + * An implementation of Google Maps' MVCArray. + * @see https://developers.google.com/maps/documentation/javascript/reference + */ -if (goog.DEBUG) { +goog.provide('ol.Collection'); +goog.provide('ol.CollectionEvent'); +goog.provide('ol.CollectionEventType'); + +goog.require('goog.array'); +goog.require('goog.events.Event'); +goog.require('ol.Object'); + + +/** + * @enum {string} + */ +ol.CollectionEventType = { /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeHtml, use - * {@code goog.html.SafeHtml.unwrap}. - * - * @see goog.html.SafeHtml#unwrap - * @override + * Triggered when an item is added to the collection. + * @event ol.CollectionEvent#add + * @api stable */ - goog.html.SafeHtml.prototype.toString = function() { - return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + - '}'; - }; -} + ADD: 'add', + /** + * Triggered when an item is removed from the collection. + * @event ol.CollectionEvent#remove + * @api stable + */ + REMOVE: 'remove' +}; + /** - * Performs a runtime check that the provided object is indeed a SafeHtml - * object, and returns its value. - * @param {!goog.html.SafeHtml} safeHtml The object to extract from. - * @return {string} The SafeHtml object's contained string, unless the run-time - * type check fails. In that case, {@code unwrap} returns an innocuous - * string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. + * @classdesc + * Events emitted by {@link ol.Collection} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.CollectionEvent} + * @param {ol.CollectionEventType} type Type. + * @param {*=} opt_element Element. + * @param {Object=} opt_target Target. */ -goog.html.SafeHtml.unwrap = function(safeHtml) { - // Perform additional run-time type-checking to ensure that safeHtml is indeed - // an instance of the expected type. This provides some additional protection - // against security bugs due to application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeHtml instanceof goog.html.SafeHtml && - safeHtml.constructor === goog.html.SafeHtml && - safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_; - } else { - goog.asserts.fail('expected object of type SafeHtml, got \'' + - safeHtml + '\''); - return 'type_error:SafeHtml'; - } +ol.CollectionEvent = function(type, opt_element, opt_target) { + + goog.base(this, type, opt_target); + + /** + * The element that is added to or removed from the collection. + * @type {*} + * @api stable + */ + this.element = opt_element; + }; +goog.inherits(ol.CollectionEvent, goog.events.Event); /** - * Shorthand for union of types that can sensibly be converted to strings - * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString). - * @private - * @typedef {string|number|boolean|!goog.string.TypedString| - * !goog.i18n.bidi.DirectionalString} + * @enum {string} */ -goog.html.SafeHtml.TextOrHtml_; +ol.CollectionProperty = { + LENGTH: 'length' +}; + /** - * Returns HTML-escaped text as a SafeHtml object. - * - * If text is of a type that implements - * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new - * {@code SafeHtml} object is set to {@code text}'s directionality, if known. - * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e., - * {@code null}). + * @classdesc + * An expanded version of standard JS Array, adding convenience methods for + * manipulation. Add and remove changes to the Collection trigger a Collection + * event. Note that this does not cover changes to the objects _within_ the + * Collection; they trigger events on the appropriate object, not on the + * Collection as a whole. * - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If - * the parameter is of type SafeHtml it is returned directly (no escaping - * is done). - * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + * @constructor + * @extends {ol.Object} + * @fires ol.CollectionEvent + * @param {!Array.<T>=} opt_array Array. + * @template T + * @api stable */ -goog.html.SafeHtml.htmlEscape = function(textOrHtml) { - if (textOrHtml instanceof goog.html.SafeHtml) { - return textOrHtml; - } - var dir = null; - if (textOrHtml.implementsGoogI18nBidiDirectionalString) { - dir = textOrHtml.getDirection(); - } - var textAsString; - if (textOrHtml.implementsGoogStringTypedString) { - textAsString = textOrHtml.getTypedStringValue(); - } else { - textAsString = String(textOrHtml); - } - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - goog.string.htmlEscape(textAsString), dir); -}; +ol.Collection = function(opt_array) { + goog.base(this); + + /** + * @private + * @type {!Array.<T>} + */ + this.array_ = goog.isDef(opt_array) ? opt_array : []; + + this.updateLength_(); -/** - * Returns HTML-escaped text as a SafeHtml object, with newlines changed to - * <br>. - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If - * the parameter is of type SafeHtml it is returned directly (no escaping - * is done). - * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. - */ -goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) { - if (textOrHtml instanceof goog.html.SafeHtml) { - return textOrHtml; - } - var html = goog.html.SafeHtml.htmlEscape(textOrHtml); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)), - html.getDirection()); }; +goog.inherits(ol.Collection, ol.Object); /** - * Returns HTML-escaped text as a SafeHtml object, with newlines changed to - * <br> and escaping whitespace to preserve spatial formatting. Character - * entity #160 is used to make it safer for XML. - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If - * the parameter is of type SafeHtml it is returned directly (no escaping - * is done). - * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + * Remove all elements from the collection. + * @api stable */ -goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function( - textOrHtml) { - if (textOrHtml instanceof goog.html.SafeHtml) { - return textOrHtml; +ol.Collection.prototype.clear = function() { + while (this.getLength() > 0) { + this.pop(); } - var html = goog.html.SafeHtml.htmlEscape(textOrHtml); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)), - html.getDirection()); }; /** - * Coerces an arbitrary object into a SafeHtml object. - * - * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same - * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and - * HTML-escaped. If {@code textOrHtml} is of a type that implements - * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is - * preserved. - * - * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to - * coerce. - * @return {!goog.html.SafeHtml} The resulting SafeHtml object. - * @deprecated Use goog.html.SafeHtml.htmlEscape. + * Add elements to the collection. This pushes each item in the provided array + * to the end of the collection. + * @param {!Array.<T>} arr Array. + * @return {ol.Collection.<T>} This collection. + * @api stable */ -goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape; +ol.Collection.prototype.extend = function(arr) { + var i, ii; + for (i = 0, ii = arr.length; i < ii; ++i) { + this.push(arr[i]); + } + return this; +}; /** - * @const - * @private + * Iterate over each element, calling the provided callback. + * @param {function(this: S, T, number, Array.<T>): *} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the array). The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S + * @api stable */ -goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/; +ol.Collection.prototype.forEach = function(f, opt_this) { + goog.array.forEach(this.array_, f, opt_this); +}; /** - * Set of attributes containing URL as defined at - * http://www.w3.org/TR/html5/index.html#attributes-1. - * @private @const {!Object<string,boolean>} + * Get a reference to the underlying Array object. Warning: if the array + * is mutated, no events will be dispatched by the collection, and the + * collection's "length" property won't be in sync with the actual length + * of the array. + * @return {!Array.<T>} Array. + * @api stable */ -goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet('action', 'cite', - 'data', 'formaction', 'href', 'manifest', 'poster', 'src'); +ol.Collection.prototype.getArray = function() { + return this.array_; +}; /** - * Tags which are unsupported via create(). They might be supported via a - * tag-specific create method. These are tags which might require a - * TrustedResourceUrl in one of their attributes or a restricted type for - * their content. - * @private @const {!Object<string,boolean>} + * Get the element at the provided index. + * @param {number} index Index. + * @return {T} Element. + * @api stable */ -goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet( - 'embed', 'iframe', 'link', 'object', 'script', 'style', 'template'); +ol.Collection.prototype.item = function(index) { + return this.array_[index]; +}; /** - * @typedef {string|number|goog.string.TypedString| - * goog.html.SafeStyle.PropertyMap} - * @private + * Get the length of this collection. + * @return {number} The length of the array. + * @observable + * @api stable */ -goog.html.SafeHtml.AttributeValue_; +ol.Collection.prototype.getLength = function() { + return /** @type {number} */ (this.get(ol.CollectionProperty.LENGTH)); +}; /** - * Creates a SafeHtml content consisting of a tag with optional attributes and - * optional content. - * - * For convenience tag names and attribute names are accepted as regular - * strings, instead of goog.string.Const. Nevertheless, you should not pass - * user-controlled values to these parameters. Note that these parameters are - * syntactically validated at runtime, and invalid values will result in - * an exception. - * - * Example usage: - * - * goog.html.SafeHtml.create('br'); - * goog.html.SafeHtml.create('div', {'class': 'a'}); - * goog.html.SafeHtml.create('p', {}, 'a'); - * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br')); - * - * goog.html.SafeHtml.create('span', { - * 'style': {'margin': '0'} - * }); - * - * To guarantee SafeHtml's type contract is upheld there are restrictions on - * attribute values and tag names. - * - * - For attributes which contain script code (on*), a goog.string.Const is - * required. - * - For attributes which contain style (style), a goog.html.SafeStyle or a - * goog.html.SafeStyle.PropertyMap is required. - * - For attributes which are interpreted as URLs (e.g. src, href) a - * goog.html.SafeUrl or goog.string.Const is required. - * - For tags which can load code, more specific goog.html.SafeHtml.create*() - * functions must be used. Tags which can load code and are not supported by - * this function are embed, iframe, link, object, script, style, and template. - * - * @param {string} tagName The name of the tag. Only tag names consisting of - * [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed. - * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} - * opt_attributes Mapping from attribute names to their values. Only - * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or - * undefined causes the attribute to be omitted. - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to - * HTML-escape and put inside the tag. This must be empty for void tags - * like <br>. Array elements are concatenated. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid tag name, attribute name, or attribute value is - * provided. - * @throws {goog.asserts.AssertionError} If content for void tag is provided. + * Insert an element at the provided index. + * @param {number} index Index. + * @param {T} elem Element. + * @api stable */ -goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) { - if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) { - throw Error('Invalid tag name <' + tagName + '>.'); - } - if (tagName.toLowerCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) { - throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.'); - } - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - tagName, opt_attributes, opt_content); +ol.Collection.prototype.insertAt = function(index, elem) { + goog.array.insertAt(this.array_, elem, index); + this.updateLength_(); + this.dispatchEvent( + new ol.CollectionEvent(ol.CollectionEventType.ADD, elem, this)); }; /** - * Creates a SafeHtml representing an iframe tag. - * - * By default the sandbox attribute is set to an empty value, which is the most - * secure option, as it confers the iframe the least privileges. If this - * is too restrictive then granting individual privileges is the preferable - * option. Unsetting the attribute entirely is the least secure option and - * should never be done unless it's stricly necessary. - * - * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src - * attribute. If null or undefined src will not be set. - * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute. - * If null or undefined srcdoc will not be set. - * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} - * opt_attributes Mapping from attribute names to their values. Only - * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or - * undefined causes the attribute to be omitted. - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to - * HTML-escape and put inside the tag. Array elements are concatenated. - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. - * @throws {Error} If invalid tag name, attribute name, or attribute value is - * provided. If opt_attributes contains the src or srcdoc attributes. + * Remove the last element of the collection and return it. + * Return `undefined` if the collection is empty. + * @return {T|undefined} Element. + * @api stable */ -goog.html.SafeHtml.createIframe = function( - opt_src, opt_srcdoc, opt_attributes, opt_content) { - var fixedAttributes = {}; - fixedAttributes['src'] = opt_src || null; - fixedAttributes['srcdoc'] = opt_srcdoc || null; - var defaultAttributes = {'sandbox': ''}; - var attributes = goog.html.SafeHtml.combineAttributes( - fixedAttributes, defaultAttributes, opt_attributes); - return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( - 'iframe', attributes, opt_content); +ol.Collection.prototype.pop = function() { + return this.removeAt(this.getLength() - 1); }; /** - * @param {string} tagName The tag name. - * @param {string} name The attribute name. - * @param {!goog.html.SafeHtml.AttributeValue_} value The attribute value. - * @return {string} A "name=value" string. - * @throws {Error} If attribute value is unsafe for the given tag and attribute. - * @private + * Insert the provided element at the end of the collection. + * @param {T} elem Element. + * @return {number} Length. + * @api stable */ -goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) { - // If it's goog.string.Const, allow any valid attribute name. - if (value instanceof goog.string.Const) { - value = goog.string.Const.unwrap(value); - } else if (name.toLowerCase() == 'style') { - value = goog.html.SafeHtml.getStyleValue_(value); - } else if (/^on/i.test(name)) { - // TODO(jakubvrana): Disallow more attributes with a special meaning. - throw Error('Attribute "' + name + - '" requires goog.string.Const value, "' + value + '" given.'); - // URL attributes handled differently accroding to tag. - } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) { - if (value instanceof goog.html.TrustedResourceUrl) { - value = goog.html.TrustedResourceUrl.unwrap(value); - } else if (value instanceof goog.html.SafeUrl) { - value = goog.html.SafeUrl.unwrap(value); - } else { - // TODO(user): Allow strings and sanitize them automatically, - // so that it's consistent with accepting a map directly for "style". - throw Error('Attribute "' + name + '" on tag "' + tagName + - '" requires goog.html.SafeUrl or goog.string.Const value, "' + - value + '" given.'); - } - } - - // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require - // HTML-escaping. - if (value.implementsGoogStringTypedString) { - // Ok to call getTypedStringValue() since there's no reliance on the type - // contract for security here. - value = value.getTypedStringValue(); - } - - goog.asserts.assert(goog.isString(value) || goog.isNumber(value), - 'String or number value expected, got ' + - (typeof value) + ' with value: ' + value); - return name + '="' + goog.string.htmlEscape(String(value)) + '"'; +ol.Collection.prototype.push = function(elem) { + var n = this.array_.length; + this.insertAt(n, elem); + return n; }; /** - * Gets value allowed in "style" attribute. - * @param {goog.html.SafeHtml.AttributeValue_} value It could be SafeStyle or a - * map which will be passed to goog.html.SafeStyle.create. - * @return {string} Unwrapped value. - * @throws {Error} If string value is given. - * @private + * Remove the first occurrence of an element from the collection. + * @param {T} elem Element. + * @return {T|undefined} The removed element or undefined if none found. + * @api stable */ -goog.html.SafeHtml.getStyleValue_ = function(value) { - if (!goog.isObject(value)) { - throw Error('The "style" attribute requires goog.html.SafeStyle or map ' + - 'of style properties, ' + (typeof value) + ' given: ' + value); - } - if (!(value instanceof goog.html.SafeStyle)) { - // Process the property bag into a style object. - value = goog.html.SafeStyle.create(value); +ol.Collection.prototype.remove = function(elem) { + var arr = this.array_; + var i, ii; + for (i = 0, ii = arr.length; i < ii; ++i) { + if (arr[i] === elem) { + return this.removeAt(i); + } } - return goog.html.SafeStyle.unwrap(value); + return undefined; }; /** - * Creates a SafeHtml content with known directionality consisting of a tag with - * optional attributes and optional content. - * @param {!goog.i18n.bidi.Dir} dir Directionality. - * @param {string} tagName - * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content - * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * Remove the element at the provided index and return it. + * Return `undefined` if the collection does not contain this index. + * @param {number} index Index. + * @return {T|undefined} Value. + * @api stable */ -goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes, - opt_content) { - var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content); - html.dir_ = dir; - return html; +ol.Collection.prototype.removeAt = function(index) { + var prev = this.array_[index]; + goog.array.removeAt(this.array_, index); + this.updateLength_(); + this.dispatchEvent( + new ol.CollectionEvent(ol.CollectionEventType.REMOVE, prev, this)); + return prev; }; /** - * Creates a new SafeHtml object by concatenating values. - * @param {...(!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate. - * @return {!goog.html.SafeHtml} + * Set the element at the provided index. + * @param {number} index Index. + * @param {T} elem Element. + * @api stable */ -goog.html.SafeHtml.concat = function(var_args) { - var dir = goog.i18n.bidi.Dir.NEUTRAL; - var content = ''; - - /** - * @param {!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument - */ - var addArgument = function(argument) { - if (goog.isArray(argument)) { - goog.array.forEach(argument, addArgument); - } else { - var html = goog.html.SafeHtml.htmlEscape(argument); - content += goog.html.SafeHtml.unwrap(html); - var htmlDir = html.getDirection(); - if (dir == goog.i18n.bidi.Dir.NEUTRAL) { - dir = htmlDir; - } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) { - dir = null; - } +ol.Collection.prototype.setAt = function(index, elem) { + var n = this.getLength(); + if (index < n) { + var prev = this.array_[index]; + this.array_[index] = elem; + this.dispatchEvent( + new ol.CollectionEvent(ol.CollectionEventType.REMOVE, prev, this)); + this.dispatchEvent( + new ol.CollectionEvent(ol.CollectionEventType.ADD, elem, this)); + } else { + var j; + for (j = n; j < index; ++j) { + this.insertAt(j, undefined); } - }; - - goog.array.forEach(arguments, addArgument); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - content, dir); + this.insertAt(index, elem); + } }; /** - * Creates a new SafeHtml object with known directionality by concatenating the - * values. - * @param {!goog.i18n.bidi.Dir} dir Directionality. - * @param {...!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>} var_args Elements of array - * arguments would be processed recursively. - * @return {!goog.html.SafeHtml} + * @private */ -goog.html.SafeHtml.concatWithDir = function(dir, var_args) { - var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1)); - html.dir_ = dir; - return html; +ol.Collection.prototype.updateLength_ = function() { + this.set(ol.CollectionProperty.LENGTH, this.array_.length); }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Type marker for the SafeHtml type, used to implement additional run-time - * type checking. - * @const - * @private + * @fileoverview Names of standard colors with their associated hex values. */ -goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; - -/** - * Package-internal utility method to create SafeHtml instances. - * - * @param {string} html The string to initialize the SafeHtml object with. - * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be - * constructed, or null if unknown. - * @return {!goog.html.SafeHtml} The initialized SafeHtml object. - * @package - */ -goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function( - html, dir) { - var safeHtml = new goog.html.SafeHtml(); - safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html; - safeHtml.dir_ = dir; - return safeHtml; -}; +goog.provide('goog.color.names'); /** - * Like create() but does not restrict which tags can be constructed. - * - * @param {string} tagName Tag name. Set or validated by caller. - * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes - * @param {(!goog.html.SafeHtml.TextOrHtml_| - * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content - * @return {!goog.html.SafeHtml} - * @throws {Error} If invalid or unsafe attribute name or value is provided. - * @throws {goog.asserts.AssertionError} If content for void tag is provided. - * @package - */ -goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = - function(tagName, opt_attributes, opt_content) { - var dir = null; - var result = '<' + tagName; - - if (opt_attributes) { - for (var name in opt_attributes) { - if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) { - throw Error('Invalid attribute name "' + name + '".'); - } - var value = opt_attributes[name]; - if (!goog.isDefAndNotNull(value)) { - continue; - } - result += ' ' + - goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value); - } - } - - var content = opt_content; - if (!goog.isDef(content)) { - content = []; - } else if (!goog.isArray(content)) { - content = [content]; - } - - if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) { - goog.asserts.assert(!content.length, - 'Void tag <' + tagName + '> does not allow content.'); - result += '>'; - } else { - var html = goog.html.SafeHtml.concat(content); - result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>'; - dir = html.getDirection(); - } - - var dirAttribute = opt_attributes && opt_attributes['dir']; - if (dirAttribute) { - if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) { - // If the tag has the "dir" attribute specified then its direction is - // neutral because it can be safely used in any context. - dir = goog.i18n.bidi.Dir.NEUTRAL; - } else { - dir = null; - } - } - - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - result, dir); -}; - - -/** - * @param {!Object<string, string>} fixedAttributes - * @param {!Object<string, string>} defaultAttributes - * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} - * opt_attributes Optional attributes passed to create*(). - * @return {!Object<string, goog.html.SafeHtml.AttributeValue_>} - * @throws {Error} If opt_attributes contains an attribute with the same name - * as an attribute in fixedAttributes. - * @package + * A map that contains a lot of colors that are recognised by various browsers. + * This list is way larger than the minimal one dictated by W3C. + * The keys of this map are the lowercase "readable" names of the colors, while + * the values are the "hex" values. */ -goog.html.SafeHtml.combineAttributes = function( - fixedAttributes, defaultAttributes, opt_attributes) { - var combinedAttributes = {}; - var name; - - for (name in fixedAttributes) { - goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); - combinedAttributes[name] = fixedAttributes[name]; - } - for (name in defaultAttributes) { - goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); - combinedAttributes[name] = defaultAttributes[name]; - } - - for (name in opt_attributes) { - var nameLower = name.toLowerCase(); - if (nameLower in fixedAttributes) { - throw Error('Cannot override "' + nameLower + '" attribute, got "' + - name + '" with value "' + opt_attributes[name] + '"'); - } - if (nameLower in defaultAttributes) { - delete combinedAttributes[nameLower]; - } - combinedAttributes[name] = opt_attributes[name]; - } - - return combinedAttributes; +goog.color.names = { + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgreen': '#006400', + 'darkgrey': '#a9a9a9', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'grey': '#808080', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightgrey': '#d3d3d3', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370db', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#db7093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32' }; - -/** - * A SafeHtml instance corresponding to the empty string. - * @const {!goog.html.SafeHtml} - */ -goog.html.SafeHtml.EMPTY = - goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - '', goog.i18n.bidi.Dir.NEUTRAL); - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23880,4563 +24207,5325 @@ goog.html.SafeHtml.EMPTY = // limitations under the License. /** - * @fileoverview Type-safe wrappers for unsafe DOM APIs. - * - * This file provides type-safe wrappers for DOM APIs that can result in - * cross-site scripting (XSS) vulnerabilities, if the API is supplied with - * untrusted (attacker-controlled) input. Instead of plain strings, the type - * safe wrappers consume values of types from the goog.html package whose - * contract promises that values are safe to use in the corresponding context. - * - * Hence, a program that exclusively uses the wrappers in this file (i.e., whose - * only reference to security-sensitive raw DOM APIs are in this file) is - * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo - * correctness of code that produces values of the respective goog.html types, - * and absent code that violates type safety). - * - * For example, assigning to an element's .innerHTML property a string that is - * derived (even partially) from untrusted input typically results in an XSS - * vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value - * of type goog.html.SafeHtml, whose contract states that using its values in a - * HTML context will not result in XSS. Hence a program that is free of direct - * assignments to any element's innerHTML property (with the exception of the - * assignment to .innerHTML in this file) is guaranteed to be free of XSS due to - * assignment of untrusted strings to the innerHTML property. + * @fileoverview Utilities related to color and color conversion. */ -goog.provide('goog.dom.safe'); +goog.provide('goog.color'); +goog.provide('goog.color.Hsl'); +goog.provide('goog.color.Hsv'); +goog.provide('goog.color.Rgb'); -goog.require('goog.html.SafeHtml'); -goog.require('goog.html.SafeUrl'); +goog.require('goog.color.names'); +goog.require('goog.math'); /** - * Assigns known-safe HTML to an element's innerHTML property. - * @param {!Element} elem The element whose innerHTML is to be assigned to. - * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + * RGB color representation. An array containing three elements [r, g, b], + * each an integer in [0, 255], representing the red, green, and blue components + * of the color respectively. + * @typedef {Array<number>} */ -goog.dom.safe.setInnerHtml = function(elem, html) { - elem.innerHTML = goog.html.SafeHtml.unwrap(html); -}; +goog.color.Rgb; /** - * Assigns known-safe HTML to an element's outerHTML property. - * @param {!Element} elem The element whose outerHTML is to be assigned to. - * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + * HSV color representation. An array containing three elements [h, s, v]: + * h (hue) must be an integer in [0, 360], cyclic. + * s (saturation) must be a number in [0, 1]. + * v (value/brightness) must be an integer in [0, 255]. + * @typedef {Array<number>} */ -goog.dom.safe.setOuterHtml = function(elem, html) { - elem.outerHTML = goog.html.SafeHtml.unwrap(html); -}; +goog.color.Hsv; /** - * Writes known-safe HTML to a document. - * @param {!Document} doc The document to be written to. - * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + * HSL color representation. An array containing three elements [h, s, l]: + * h (hue) must be an integer in [0, 360], cyclic. + * s (saturation) must be a number in [0, 1]. + * l (lightness) must be a number in [0, 1]. + * @typedef {Array<number>} */ -goog.dom.safe.documentWrite = function(doc, html) { - doc.write(goog.html.SafeHtml.unwrap(html)); -}; +goog.color.Hsl; /** - * Safely assigns a URL to an anchor element's href property. - * - * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to - * anchor's href property. If url is of type string however, it is first - * sanitized using goog.html.SafeUrl.sanitize. - * - * Example usage: - * goog.dom.safe.setAnchorHref(anchorEl, url); - * which is a safe alternative to - * anchorEl.href = url; - * The latter can result in XSS vulnerabilities if url is a - * user-/attacker-controlled value. - * - * @param {!HTMLAnchorElement} anchor The anchor element whose href property - * is to be assigned to. - * @param {string|!goog.html.SafeUrl} url The URL to assign. - * @see goog.html.SafeUrl#sanitize + * Parses a color out of a string. + * @param {string} str Color in some format. + * @return {{hex: string, type: string}} 'hex' is a string containing a hex + * representation of the color, 'type' is a string containing the type + * of color format passed in ('hex', 'rgb', 'named'). */ -goog.dom.safe.setAnchorHref = function(anchor, url) { - /** @type {!goog.html.SafeUrl} */ - var safeUrl; - if (url instanceof goog.html.SafeUrl) { - safeUrl = url; +goog.color.parse = function(str) { + var result = {}; + str = String(str); + + var maybeHex = goog.color.prependHashIfNecessaryHelper(str); + if (goog.color.isValidHexColor_(maybeHex)) { + result.hex = goog.color.normalizeHex(maybeHex); + result.type = 'hex'; + return result; } else { - safeUrl = goog.html.SafeUrl.sanitize(url); + var rgb = goog.color.isValidRgbColor_(str); + if (rgb.length) { + result.hex = goog.color.rgbArrayToHex(rgb); + result.type = 'rgb'; + return result; + } else if (goog.color.names) { + var hex = goog.color.names[str.toLowerCase()]; + if (hex) { + result.hex = hex; + result.type = 'named'; + return result; + } + } } - anchor.href = goog.html.SafeUrl.unwrap(safeUrl); + throw Error(str + ' is not a valid color string'); }; /** - * Safely assigns a URL to a Location object's href property. - * - * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to - * loc's href property. If url is of type string however, it is first sanitized - * using goog.html.SafeUrl.sanitize. - * - * Example usage: - * goog.dom.safe.setLocationHref(document.location, redirectUrl); - * which is a safe alternative to - * document.location.href = redirectUrl; - * The latter can result in XSS vulnerabilities if redirectUrl is a - * user-/attacker-controlled value. - * - * @param {!Location} loc The Location object whose href property is to be - * assigned to. - * @param {string|!goog.html.SafeUrl} url The URL to assign. - * @see goog.html.SafeUrl#sanitize + * Determines if the given string can be parsed as a color. + * {@see goog.color.parse}. + * @param {string} str Potential color string. + * @return {boolean} True if str is in a format that can be parsed to a color. */ -goog.dom.safe.setLocationHref = function(loc, url) { - /** @type {!goog.html.SafeUrl} */ - var safeUrl; - if (url instanceof goog.html.SafeUrl) { - safeUrl = url; - } else { - safeUrl = goog.html.SafeUrl.sanitize(url); - } - loc.href = goog.html.SafeUrl.unwrap(safeUrl); +goog.color.isValidColor = function(str) { + var maybeHex = goog.color.prependHashIfNecessaryHelper(str); + return !!(goog.color.isValidHexColor_(maybeHex) || + goog.color.isValidRgbColor_(str).length || + goog.color.names && goog.color.names[str.toLowerCase()]); }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A utility class for representing two-dimensional positions. + * Parses red, green, blue components out of a valid rgb color string. + * Throws Error if the color string is invalid. + * @param {string} str RGB representation of a color. + * {@see goog.color.isValidRgbColor_}. + * @return {!goog.color.Rgb} rgb representation of the color. */ - - -goog.provide('goog.math.Coordinate'); - -goog.require('goog.math'); - +goog.color.parseRgb = function(str) { + var rgb = goog.color.isValidRgbColor_(str); + if (!rgb.length) { + throw Error(str + ' is not a valid RGB color'); + } + return rgb; +}; /** - * Class for representing coordinates and positions. - * @param {number=} opt_x Left, defaults to 0. - * @param {number=} opt_y Top, defaults to 0. - * @constructor + * Converts a hex representation of a color to RGB. + * @param {string} hexColor Color to convert. + * @return {string} string of the form 'rgb(R,G,B)' which can be used in + * styles. */ -goog.math.Coordinate = function(opt_x, opt_y) { - /** - * X-value - * @type {number} - */ - this.x = goog.isDef(opt_x) ? opt_x : 0; - - /** - * Y-value - * @type {number} - */ - this.y = goog.isDef(opt_y) ? opt_y : 0; +goog.color.hexToRgbStyle = function(hexColor) { + return goog.color.rgbStyle_(goog.color.hexToRgb(hexColor)); }; /** - * Returns a new copy of the coordinate. - * @return {!goog.math.Coordinate} A clone of this coordinate. + * Regular expression for extracting the digits in a hex color triplet. + * @type {RegExp} + * @private */ -goog.math.Coordinate.prototype.clone = function() { - return new goog.math.Coordinate(this.x, this.y); -}; - - -if (goog.DEBUG) { - /** - * Returns a nice string representing the coordinate. - * @return {string} In the form (50, 73). - * @override - */ - goog.math.Coordinate.prototype.toString = function() { - return '(' + this.x + ', ' + this.y + ')'; - }; -} +goog.color.hexTripletRe_ = /#(.)(.)(.)/; /** - * Compares coordinates for equality. - * @param {goog.math.Coordinate} a A Coordinate. - * @param {goog.math.Coordinate} b A Coordinate. - * @return {boolean} True iff the coordinates are equal, or if both are null. + * Normalize an hex representation of a color + * @param {string} hexColor an hex color string. + * @return {string} hex color in the format '#rrggbb' with all lowercase + * literals. */ -goog.math.Coordinate.equals = function(a, b) { - if (a == b) { - return true; +goog.color.normalizeHex = function(hexColor) { + if (!goog.color.isValidHexColor_(hexColor)) { + throw Error("'" + hexColor + "' is not a valid hex color"); } - if (!a || !b) { - return false; + if (hexColor.length == 4) { // of the form #RGB + hexColor = hexColor.replace(goog.color.hexTripletRe_, '#$1$1$2$2$3$3'); } - return a.x == b.x && a.y == b.y; + return hexColor.toLowerCase(); }; /** - * Returns the distance between two coordinates. - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {number} The distance between {@code a} and {@code b}. + * Converts a hex representation of a color to RGB. + * @param {string} hexColor Color to convert. + * @return {!goog.color.Rgb} rgb representation of the color. */ -goog.math.Coordinate.distance = function(a, b) { - var dx = a.x - b.x; - var dy = a.y - b.y; - return Math.sqrt(dx * dx + dy * dy); -}; - +goog.color.hexToRgb = function(hexColor) { + hexColor = goog.color.normalizeHex(hexColor); + var r = parseInt(hexColor.substr(1, 2), 16); + var g = parseInt(hexColor.substr(3, 2), 16); + var b = parseInt(hexColor.substr(5, 2), 16); -/** - * Returns the magnitude of a coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @return {number} The distance between the origin and {@code a}. - */ -goog.math.Coordinate.magnitude = function(a) { - return Math.sqrt(a.x * a.x + a.y * a.y); + return [r, g, b]; }; /** - * Returns the angle from the origin to a coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @return {number} The angle, in degrees, clockwise from the positive X - * axis to {@code a}. + * Converts a color from RGB to hex representation. + * @param {number} r Amount of red, int between 0 and 255. + * @param {number} g Amount of green, int between 0 and 255. + * @param {number} b Amount of blue, int between 0 and 255. + * @return {string} hex representation of the color. */ -goog.math.Coordinate.azimuth = function(a) { - return goog.math.angle(0, 0, a.x, a.y); +goog.color.rgbToHex = function(r, g, b) { + r = Number(r); + g = Number(g); + b = Number(b); + if (isNaN(r) || r < 0 || r > 255 || + isNaN(g) || g < 0 || g > 255 || + isNaN(b) || b < 0 || b > 255) { + throw Error('"(' + r + ',' + g + ',' + b + '") is not a valid RGB color'); + } + var hexR = goog.color.prependZeroIfNecessaryHelper(r.toString(16)); + var hexG = goog.color.prependZeroIfNecessaryHelper(g.toString(16)); + var hexB = goog.color.prependZeroIfNecessaryHelper(b.toString(16)); + return '#' + hexR + hexG + hexB; }; /** - * Returns the squared distance between two coordinates. Squared distances can - * be used for comparisons when the actual value is not required. - * - * Performance note: eliminating the square root is an optimization often used - * in lower-level languages, but the speed difference is not nearly as - * pronounced in JavaScript (only a few percent.) - * - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {number} The squared distance between {@code a} and {@code b}. + * Converts a color from RGB to hex representation. + * @param {goog.color.Rgb} rgb rgb representation of the color. + * @return {string} hex representation of the color. */ -goog.math.Coordinate.squaredDistance = function(a, b) { - var dx = a.x - b.x; - var dy = a.y - b.y; - return dx * dx + dy * dy; +goog.color.rgbArrayToHex = function(rgb) { + return goog.color.rgbToHex(rgb[0], rgb[1], rgb[2]); }; /** - * Returns the difference between two coordinates as a new - * goog.math.Coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {!goog.math.Coordinate} A Coordinate representing the difference - * between {@code a} and {@code b}. + * Converts a color from RGB color space to HSL color space. + * Modified from {@link http://en.wikipedia.org/wiki/HLS_color_space}. + * @param {number} r Value of red, in [0, 255]. + * @param {number} g Value of green, in [0, 255]. + * @param {number} b Value of blue, in [0, 255]. + * @return {!goog.color.Hsl} hsl representation of the color. */ -goog.math.Coordinate.difference = function(a, b) { - return new goog.math.Coordinate(a.x - b.x, a.y - b.y); -}; +goog.color.rgbToHsl = function(r, g, b) { + // First must normalize r, g, b to be between 0 and 1. + var normR = r / 255; + var normG = g / 255; + var normB = b / 255; + var max = Math.max(normR, normG, normB); + var min = Math.min(normR, normG, normB); + var h = 0; + var s = 0; + // Luminosity is the average of the max and min rgb color intensities. + var l = 0.5 * (max + min); -/** - * Returns the sum of two coordinates as a new goog.math.Coordinate. - * @param {!goog.math.Coordinate} a A Coordinate. - * @param {!goog.math.Coordinate} b A Coordinate. - * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two - * coordinates. - */ -goog.math.Coordinate.sum = function(a, b) { - return new goog.math.Coordinate(a.x + b.x, a.y + b.y); -}; + // The hue and saturation are dependent on which color intensity is the max. + // If max and min are equal, the color is gray and h and s should be 0. + if (max != min) { + if (max == normR) { + h = 60 * (normG - normB) / (max - min); + } else if (max == normG) { + h = 60 * (normB - normR) / (max - min) + 120; + } else if (max == normB) { + h = 60 * (normR - normG) / (max - min) + 240; + } + if (0 < l && l <= 0.5) { + s = (max - min) / (2 * l); + } else { + s = (max - min) / (2 - 2 * l); + } + } -/** - * Rounds the x and y fields to the next larger integer values. - * @return {!goog.math.Coordinate} This coordinate with ceil'd fields. - */ -goog.math.Coordinate.prototype.ceil = function() { - this.x = Math.ceil(this.x); - this.y = Math.ceil(this.y); - return this; + // Make sure the hue falls between 0 and 360. + return [Math.round(h + 360) % 360, s, l]; }; /** - * Rounds the x and y fields to the next smaller integer values. - * @return {!goog.math.Coordinate} This coordinate with floored fields. + * Converts a color from RGB color space to HSL color space. + * @param {goog.color.Rgb} rgb rgb representation of the color. + * @return {!goog.color.Hsl} hsl representation of the color. */ -goog.math.Coordinate.prototype.floor = function() { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); - return this; +goog.color.rgbArrayToHsl = function(rgb) { + return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]); }; /** - * Rounds the x and y fields to the nearest integer values. - * @return {!goog.math.Coordinate} This coordinate with rounded fields. + * Helper for hslToRgb. + * @param {number} v1 Helper variable 1. + * @param {number} v2 Helper variable 2. + * @param {number} vH Helper variable 3. + * @return {number} Appropriate RGB value, given the above. + * @private */ -goog.math.Coordinate.prototype.round = function() { - this.x = Math.round(this.x); - this.y = Math.round(this.y); - return this; +goog.color.hueToRgb_ = function(v1, v2, vH) { + if (vH < 0) { + vH += 1; + } else if (vH > 1) { + vH -= 1; + } + if ((6 * vH) < 1) { + return (v1 + (v2 - v1) * 6 * vH); + } else if (2 * vH < 1) { + return v2; + } else if (3 * vH < 2) { + return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6); + } + return v1; }; /** - * Translates this box by the given offsets. If a {@code goog.math.Coordinate} - * is given, then the x and y values are translated by the coordinate's x and y. - * Otherwise, x and y are translated by {@code tx} and {@code opt_ty} - * respectively. - * @param {number|goog.math.Coordinate} tx The value to translate x by or the - * the coordinate to translate this coordinate by. - * @param {number=} opt_ty The value to translate y by. - * @return {!goog.math.Coordinate} This coordinate after translating. + * Converts a color from HSL color space to RGB color space. + * Modified from {@link http://www.easyrgb.com/math.html} + * @param {number} h Hue, in [0, 360]. + * @param {number} s Saturation, in [0, 1]. + * @param {number} l Luminosity, in [0, 1]. + * @return {!goog.color.Rgb} rgb representation of the color. */ -goog.math.Coordinate.prototype.translate = function(tx, opt_ty) { - if (tx instanceof goog.math.Coordinate) { - this.x += tx.x; - this.y += tx.y; +goog.color.hslToRgb = function(h, s, l) { + var r = 0; + var g = 0; + var b = 0; + var normH = h / 360; // normalize h to fall in [0, 1] + + if (s == 0) { + r = g = b = l * 255; } else { - this.x += tx; - if (goog.isNumber(opt_ty)) { - this.y += opt_ty; + var temp1 = 0; + var temp2 = 0; + if (l < 0.5) { + temp2 = l * (1 + s); + } else { + temp2 = l + s - (s * l); } + temp1 = 2 * l - temp2; + r = 255 * goog.color.hueToRgb_(temp1, temp2, normH + (1 / 3)); + g = 255 * goog.color.hueToRgb_(temp1, temp2, normH); + b = 255 * goog.color.hueToRgb_(temp1, temp2, normH - (1 / 3)); } - return this; + + return [Math.round(r), Math.round(g), Math.round(b)]; }; /** - * Scales this coordinate by the given scale factors. The x and y values are - * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} - * is not given, then {@code sx} is used for both x and y. - * @param {number} sx The scale factor to use for the x dimension. - * @param {number=} opt_sy The scale factor to use for the y dimension. - * @return {!goog.math.Coordinate} This coordinate after scaling. + * Converts a color from HSL color space to RGB color space. + * @param {goog.color.Hsl} hsl hsl representation of the color. + * @return {!goog.color.Rgb} rgb representation of the color. */ -goog.math.Coordinate.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.x *= sx; - this.y *= sy; - return this; +goog.color.hslArrayToRgb = function(hsl) { + return goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]); }; /** - * Rotates this coordinate clockwise about the origin (or, optionally, the given - * center) by the given angle, in radians. - * @param {number} radians The angle by which to rotate this coordinate - * clockwise about the given center, in radians. - * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults - * to (0, 0) if not given. + * Helper for isValidHexColor_. + * @type {RegExp} + * @private */ -goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) { - var center = opt_center || new goog.math.Coordinate(0, 0); - - var x = this.x; - var y = this.y; - var cos = Math.cos(radians); - var sin = Math.sin(radians); - - this.x = (x - center.x) * cos - (y - center.y) * sin + center.x; - this.y = (x - center.x) * sin + (y - center.y) * cos + center.y; -}; +goog.color.validHexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i; /** - * Rotates this coordinate clockwise about the origin (or, optionally, the given - * center) by the given angle, in degrees. - * @param {number} degrees The angle by which to rotate this coordinate - * clockwise about the given center, in degrees. - * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults - * to (0, 0) if not given. + * Checks if a string is a valid hex color. We expect strings of the format + * #RRGGBB (ex: #1b3d5f) or #RGB (ex: #3CA == #33CCAA). + * @param {string} str String to check. + * @return {boolean} Whether the string is a valid hex color. + * @private */ -goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) { - this.rotateRadians(goog.math.toRadians(degrees), opt_center); +goog.color.isValidHexColor_ = function(str) { + return goog.color.validHexColorRe_.test(str); }; -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A utility class for representing two-dimensional sizes. - * @author brenneman@google.com (Shawn Brenneman) + * Helper for isNormalizedHexColor_. + * @type {RegExp} + * @private */ +goog.color.normalizedHexColorRe_ = /^#[0-9a-f]{6}$/; -goog.provide('goog.math.Size'); - +/** + * Checks if a string is a normalized hex color. + * We expect strings of the format #RRGGBB (ex: #1b3d5f) + * using only lowercase letters. + * @param {string} str String to check. + * @return {boolean} Whether the string is a normalized hex color. + * @private + */ +goog.color.isNormalizedHexColor_ = function(str) { + return goog.color.normalizedHexColorRe_.test(str); +}; /** - * Class for representing sizes consisting of a width and height. Undefined - * width and height support is deprecated and results in compiler warning. - * @param {number} width Width. - * @param {number} height Height. - * @constructor + * Regular expression for matching and capturing RGB style strings. Helper for + * isValidRgbColor_. + * @type {RegExp} + * @private */ -goog.math.Size = function(width, height) { - /** - * Width - * @type {number} - */ - this.width = width; - - /** - * Height - * @type {number} - */ - this.height = height; -}; +goog.color.rgbColorRe_ = + /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i; /** - * Compares sizes for equality. - * @param {goog.math.Size} a A Size. - * @param {goog.math.Size} b A Size. - * @return {boolean} True iff the sizes have equal widths and equal - * heights, or if both are null. + * Checks if a string is a valid rgb color. We expect strings of the format + * '(r, g, b)', or 'rgb(r, g, b)', where each color component is an int in + * [0, 255]. + * @param {string} str String to check. + * @return {!goog.color.Rgb} the rgb representation of the color if it is + * a valid color, or the empty array otherwise. + * @private */ -goog.math.Size.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; +goog.color.isValidRgbColor_ = function(str) { + // Each component is separate (rather than using a repeater) so we can + // capture the match. Also, we explicitly set each component to be either 0, + // or start with a non-zero, to prevent octal numbers from slipping through. + var regExpResultArray = str.match(goog.color.rgbColorRe_); + if (regExpResultArray) { + var r = Number(regExpResultArray[1]); + var g = Number(regExpResultArray[2]); + var b = Number(regExpResultArray[3]); + if (r >= 0 && r <= 255 && + g >= 0 && g <= 255 && + b >= 0 && b <= 255) { + return [r, g, b]; + } } - return a.width == b.width && a.height == b.height; + return []; }; /** - * @return {!goog.math.Size} A new copy of the Size. + * Takes a hex value and prepends a zero if it's a single digit. + * Small helper method for use by goog.color and friends. + * @param {string} hex Hex value to prepend if single digit. + * @return {string} hex value prepended with zero if it was single digit, + * otherwise the same value that was passed in. */ -goog.math.Size.prototype.clone = function() { - return new goog.math.Size(this.width, this.height); +goog.color.prependZeroIfNecessaryHelper = function(hex) { + return hex.length == 1 ? '0' + hex : hex; }; -if (goog.DEBUG) { - /** - * Returns a nice string representing size. - * @return {string} In the form (50 x 73). - * @override - */ - goog.math.Size.prototype.toString = function() { - return '(' + this.width + ' x ' + this.height + ')'; - }; -} - - /** - * @return {number} The longer of the two dimensions in the size. + * Takes a string a prepends a '#' sign if one doesn't exist. + * Small helper method for use by goog.color and friends. + * @param {string} str String to check. + * @return {string} The value passed in, prepended with a '#' if it didn't + * already have one. */ -goog.math.Size.prototype.getLongest = function() { - return Math.max(this.width, this.height); +goog.color.prependHashIfNecessaryHelper = function(str) { + return str.charAt(0) == '#' ? str : '#' + str; }; /** - * @return {number} The shorter of the two dimensions in the size. + * Takes an array of [r, g, b] and converts it into a string appropriate for + * CSS styles. + * @param {goog.color.Rgb} rgb rgb representation of the color. + * @return {string} string of the form 'rgb(r,g,b)'. + * @private */ -goog.math.Size.prototype.getShortest = function() { - return Math.min(this.width, this.height); +goog.color.rgbStyle_ = function(rgb) { + return 'rgb(' + rgb.join(',') + ')'; }; /** - * @return {number} The area of the size (width * height). + * Converts an HSV triplet to an RGB array. V is brightness because b is + * reserved for blue in RGB. + * @param {number} h Hue value in [0, 360]. + * @param {number} s Saturation value in [0, 1]. + * @param {number} brightness brightness in [0, 255]. + * @return {!goog.color.Rgb} rgb representation of the color. */ -goog.math.Size.prototype.area = function() { - return this.width * this.height; +goog.color.hsvToRgb = function(h, s, brightness) { + var red = 0; + var green = 0; + var blue = 0; + if (s == 0) { + red = brightness; + green = brightness; + blue = brightness; + } else { + var sextant = Math.floor(h / 60); + var remainder = (h / 60) - sextant; + var val1 = brightness * (1 - s); + var val2 = brightness * (1 - (s * remainder)); + var val3 = brightness * (1 - (s * (1 - remainder))); + switch (sextant) { + case 1: + red = val2; + green = brightness; + blue = val1; + break; + case 2: + red = val1; + green = brightness; + blue = val3; + break; + case 3: + red = val1; + green = val2; + blue = brightness; + break; + case 4: + red = val3; + green = val1; + blue = brightness; + break; + case 5: + red = brightness; + green = val1; + blue = val2; + break; + case 6: + case 0: + red = brightness; + green = val3; + blue = val1; + break; + } + } + + return [Math.floor(red), Math.floor(green), Math.floor(blue)]; }; /** - * @return {number} The perimeter of the size (width + height) * 2. + * Converts from RGB values to an array of HSV values. + * @param {number} red Red value in [0, 255]. + * @param {number} green Green value in [0, 255]. + * @param {number} blue Blue value in [0, 255]. + * @return {!goog.color.Hsv} hsv representation of the color. */ -goog.math.Size.prototype.perimeter = function() { - return (this.width + this.height) * 2; +goog.color.rgbToHsv = function(red, green, blue) { + + var max = Math.max(Math.max(red, green), blue); + var min = Math.min(Math.min(red, green), blue); + var hue; + var saturation; + var value = max; + if (min == max) { + hue = 0; + saturation = 0; + } else { + var delta = (max - min); + saturation = delta / max; + + if (red == max) { + hue = (green - blue) / delta; + } else if (green == max) { + hue = 2 + ((blue - red) / delta); + } else { + hue = 4 + ((red - green) / delta); + } + hue *= 60; + if (hue < 0) { + hue += 360; + } + if (hue > 360) { + hue -= 360; + } + } + + return [hue, saturation, value]; }; /** - * @return {number} The ratio of the size's width to its height. + * Converts from an array of RGB values to an array of HSV values. + * @param {goog.color.Rgb} rgb rgb representation of the color. + * @return {!goog.color.Hsv} hsv representation of the color. */ -goog.math.Size.prototype.aspectRatio = function() { - return this.width / this.height; +goog.color.rgbArrayToHsv = function(rgb) { + return goog.color.rgbToHsv(rgb[0], rgb[1], rgb[2]); }; /** - * @return {boolean} True if the size has zero area, false if both dimensions - * are non-zero numbers. + * Converts an HSV triplet to an RGB array. + * @param {goog.color.Hsv} hsv hsv representation of the color. + * @return {!goog.color.Rgb} rgb representation of the color. */ -goog.math.Size.prototype.isEmpty = function() { - return !this.area(); +goog.color.hsvArrayToRgb = function(hsv) { + return goog.color.hsvToRgb(hsv[0], hsv[1], hsv[2]); }; /** - * Clamps the width and height parameters upward to integer values. - * @return {!goog.math.Size} This size with ceil'd components. - */ -goog.math.Size.prototype.ceil = function() { - this.width = Math.ceil(this.width); - this.height = Math.ceil(this.height); - return this; + * Converts a hex representation of a color to HSL. + * @param {string} hex Color to convert. + * @return {!goog.color.Hsv} hsv representation of the color. + */ +goog.color.hexToHsl = function(hex) { + var rgb = goog.color.hexToRgb(hex); + return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]); }; /** - * @param {!goog.math.Size} target The target size. - * @return {boolean} True if this Size is the same size or smaller than the - * target size in both dimensions. + * Converts from h,s,l values to a hex string + * @param {number} h Hue, in [0, 360]. + * @param {number} s Saturation, in [0, 1]. + * @param {number} l Luminosity, in [0, 1]. + * @return {string} hex representation of the color. */ -goog.math.Size.prototype.fitsInside = function(target) { - return this.width <= target.width && this.height <= target.height; +goog.color.hslToHex = function(h, s, l) { + return goog.color.rgbArrayToHex(goog.color.hslToRgb(h, s, l)); }; /** - * Clamps the width and height parameters downward to integer values. - * @return {!goog.math.Size} This size with floored components. + * Converts from an hsl array to a hex string + * @param {goog.color.Hsl} hsl hsl representation of the color. + * @return {string} hex representation of the color. */ -goog.math.Size.prototype.floor = function() { - this.width = Math.floor(this.width); - this.height = Math.floor(this.height); - return this; +goog.color.hslArrayToHex = function(hsl) { + return goog.color.rgbArrayToHex(goog.color.hslToRgb(hsl[0], hsl[1], hsl[2])); }; /** - * Rounds the width and height parameters to integer values. - * @return {!goog.math.Size} This size with rounded components. + * Converts a hex representation of a color to HSV + * @param {string} hex Color to convert. + * @return {!goog.color.Hsv} hsv representation of the color. */ -goog.math.Size.prototype.round = function() { - this.width = Math.round(this.width); - this.height = Math.round(this.height); - return this; +goog.color.hexToHsv = function(hex) { + return goog.color.rgbArrayToHsv(goog.color.hexToRgb(hex)); }; /** - * Scales this size by the given scale factors. The width and height are scaled - * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not - * given, then {@code sx} is used for both the width and height. - * @param {number} sx The scale factor to use for the width. - * @param {number=} opt_sy The scale factor to use for the height. - * @return {!goog.math.Size} This Size object after scaling. + * Converts from h,s,v values to a hex string + * @param {number} h Hue, in [0, 360]. + * @param {number} s Saturation, in [0, 1]. + * @param {number} v Value, in [0, 255]. + * @return {string} hex representation of the color. */ -goog.math.Size.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.width *= sx; - this.height *= sy; - return this; +goog.color.hsvToHex = function(h, s, v) { + return goog.color.rgbArrayToHex(goog.color.hsvToRgb(h, s, v)); }; /** - * Uniformly scales the size to fit inside the dimensions of a given size. The - * original aspect ratio will be preserved. - * - * This function assumes that both Sizes contain strictly positive dimensions. - * @param {!goog.math.Size} target The target size. - * @return {!goog.math.Size} This Size object, after optional scaling. + * Converts from an HSV array to a hex string + * @param {goog.color.Hsv} hsv hsv representation of the color. + * @return {string} hex representation of the color. */ -goog.math.Size.prototype.scaleToFit = function(target) { - var s = this.aspectRatio() > target.aspectRatio() ? - target.width / this.width : - target.height / this.height; - - return this.scale(s); +goog.color.hsvArrayToHex = function(hsv) { + return goog.color.hsvToHex(hsv[0], hsv[1], hsv[2]); }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for manipulating the browser's Document Object Model - * Inspiration taken *heavily* from mochikit (http://mochikit.com/). - * - * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer - * to a different document object. This is useful if you are working with - * frames or multiple windows. - * - * @author arv@google.com (Erik Arvidsson) + * Calculates the Euclidean distance between two color vectors on an HSL sphere. + * A demo of the sphere can be found at: + * http://en.wikipedia.org/wiki/HSL_color_space + * In short, a vector for color (H, S, L) in this system can be expressed as + * (S*L'*cos(2*PI*H), S*L'*sin(2*PI*H), L), where L' = abs(L - 0.5), and we + * simply calculate the 1-2 distance using these coordinates + * @param {goog.color.Hsl} hsl1 First color in hsl representation. + * @param {goog.color.Hsl} hsl2 Second color in hsl representation. + * @return {number} Distance between the two colors, in the range [0, 1]. */ +goog.color.hslDistance = function(hsl1, hsl2) { + var sl1, sl2; + if (hsl1[2] <= 0.5) { + sl1 = hsl1[1] * hsl1[2]; + } else { + sl1 = hsl1[1] * (1.0 - hsl1[2]); + } + if (hsl2[2] <= 0.5) { + sl2 = hsl2[1] * hsl2[2]; + } else { + sl2 = hsl2[1] * (1.0 - hsl2[2]); + } -// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem -// is that getTextContent should mimic the DOM3 textContent. We should add a -// getInnerText (or getText) which tries to return the visible text, innerText. - - -goog.provide('goog.dom'); -goog.provide('goog.dom.Appendable'); -goog.provide('goog.dom.DomHelper'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom.BrowserFeature'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.safe'); -goog.require('goog.html.SafeHtml'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Size'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.string.Unicode'); -goog.require('goog.userAgent'); + var h1 = hsl1[0] / 360.0; + var h2 = hsl2[0] / 360.0; + var dh = (h1 - h2) * 2.0 * Math.PI; + return (hsl1[2] - hsl2[2]) * (hsl1[2] - hsl2[2]) + + sl1 * sl1 + sl2 * sl2 - 2 * sl1 * sl2 * Math.cos(dh); +}; /** - * @define {boolean} Whether we know at compile time that the browser is in - * quirks mode. + * Blend two colors together, using the specified factor to indicate the weight + * given to the first color + * @param {goog.color.Rgb} rgb1 First color represented in rgb. + * @param {goog.color.Rgb} rgb2 Second color represented in rgb. + * @param {number} factor The weight to be given to rgb1 over rgb2. Values + * should be in the range [0, 1]. If less than 0, factor will be set to 0. + * If greater than 1, factor will be set to 1. + * @return {!goog.color.Rgb} Combined color represented in rgb. */ -goog.define('goog.dom.ASSUME_QUIRKS_MODE', false); - +goog.color.blend = function(rgb1, rgb2, factor) { + factor = goog.math.clamp(factor, 0, 1); -/** - * @define {boolean} Whether we know at compile time that the browser is in - * standards compliance mode. - */ -goog.define('goog.dom.ASSUME_STANDARDS_MODE', false); + return [ + Math.round(factor * rgb1[0] + (1.0 - factor) * rgb2[0]), + Math.round(factor * rgb1[1] + (1.0 - factor) * rgb2[1]), + Math.round(factor * rgb1[2] + (1.0 - factor) * rgb2[2]) + ]; +}; /** - * Whether we know the compatibility mode at compile time. - * @type {boolean} - * @private + * Adds black to the specified color, darkening it + * @param {goog.color.Rgb} rgb rgb representation of the color. + * @param {number} factor Number in the range [0, 1]. 0 will do nothing, while + * 1 will return black. If less than 0, factor will be set to 0. If greater + * than 1, factor will be set to 1. + * @return {!goog.color.Rgb} Combined rgb color. */ -goog.dom.COMPAT_MODE_KNOWN_ = - goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE; +goog.color.darken = function(rgb, factor) { + var black = [0, 0, 0]; + return goog.color.blend(black, rgb, factor); +}; /** - * Gets the DomHelper object for the document where the element resides. - * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this - * element. - * @return {!goog.dom.DomHelper} The DomHelper. + * Adds white to the specified color, lightening it + * @param {goog.color.Rgb} rgb rgb representation of the color. + * @param {number} factor Number in the range [0, 1]. 0 will do nothing, while + * 1 will return white. If less than 0, factor will be set to 0. If greater + * than 1, factor will be set to 1. + * @return {!goog.color.Rgb} Combined rgb color. */ -goog.dom.getDomHelper = function(opt_element) { - return opt_element ? - new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : - (goog.dom.defaultDomHelper_ || - (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper())); +goog.color.lighten = function(rgb, factor) { + var white = [255, 255, 255]; + return goog.color.blend(white, rgb, factor); }; /** - * Cached default DOM helper. - * @type {goog.dom.DomHelper} - * @private + * Find the "best" (highest-contrast) of the suggested colors for the prime + * color. Uses W3C formula for judging readability and visual accessibility: + * http://www.w3.org/TR/AERT#color-contrast + * @param {goog.color.Rgb} prime Color represented as a rgb array. + * @param {Array<goog.color.Rgb>} suggestions Array of colors, + * each representing a rgb array. + * @return {!goog.color.Rgb} Highest-contrast color represented by an array.. */ -goog.dom.defaultDomHelper_; +goog.color.highContrast = function(prime, suggestions) { + var suggestionsWithDiff = []; + for (var i = 0; i < suggestions.length; i++) { + suggestionsWithDiff.push({ + color: suggestions[i], + diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) + + goog.color.colorDiff_(suggestions[i], prime) + }); + } + suggestionsWithDiff.sort(function(a, b) { + return b.diff - a.diff; + }); + return suggestionsWithDiff[0].color; +}; /** - * Gets the document object being used by the dom library. - * @return {!Document} Document object. + * Calculate brightness of a color according to YIQ formula (brightness is Y). + * More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for + * goog.color.highContrast() + * @param {goog.color.Rgb} rgb Color represented by a rgb array. + * @return {number} brightness (Y). + * @private */ -goog.dom.getDocument = function() { - return document; +goog.color.yiqBrightness_ = function(rgb) { + return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000); }; /** - * Gets an element from the current document by element id. - * - * If an Element is passed in, it is returned. - * - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. + * Calculate difference in brightness of two colors. Helper method for + * goog.color.highContrast() + * @param {goog.color.Rgb} rgb1 Color represented by a rgb array. + * @param {goog.color.Rgb} rgb2 Color represented by a rgb array. + * @return {number} Brightness difference. + * @private */ -goog.dom.getElement = function(element) { - return goog.dom.getElementHelper_(document, element); +goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) { + return Math.abs(goog.color.yiqBrightness_(rgb1) - + goog.color.yiqBrightness_(rgb2)); }; /** - * Gets an element by id from the given document (if present). - * If an element is given, it is returned. - * @param {!Document} doc - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The resulting element. + * Calculate color difference between two colors. Helper method for + * goog.color.highContrast() + * @param {goog.color.Rgb} rgb1 Color represented by a rgb array. + * @param {goog.color.Rgb} rgb2 Color represented by a rgb array. + * @return {number} Color difference. * @private */ -goog.dom.getElementHelper_ = function(doc, element) { - return goog.isString(element) ? - doc.getElementById(element) : - element; +goog.color.colorDiff_ = function(rgb1, rgb2) { + return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) + + Math.abs(rgb1[2] - rgb2[2]); }; +// We can't use goog.color or goog.color.alpha because they interally use a hex +// string representation that encodes each channel in a single byte. This +// causes occasional loss of precision and rounding errors, especially in the +// alpha channel. + +goog.provide('ol.Color'); +goog.provide('ol.color'); + +goog.require('goog.asserts'); +goog.require('goog.color'); +goog.require('goog.color.names'); +goog.require('goog.math'); +goog.require('goog.vec.Mat4'); +goog.require('ol'); + /** - * Gets an element by id, asserting that the element is found. - * - * This is used when an element is expected to exist, and should fail with - * an assertion error if it does not (if assertions are enabled). - * - * @param {string} id Element ID. - * @return {!Element} The element with the given ID, if it exists. + * A color represented as a short array [red, green, blue, alpha]. + * red, green, and blue should be integers in the range 0..255 inclusive. + * alpha should be a float in the range 0..1 inclusive. + * @typedef {Array.<number>} + * @api */ -goog.dom.getRequiredElement = function(id) { - return goog.dom.getRequiredElementHelper_(document, id); -}; +ol.Color; /** - * Helper function for getRequiredElementHelper functions, both static and - * on DomHelper. Asserts the element with the given id exists. - * @param {!Document} doc - * @param {string} id - * @return {!Element} The element with the given ID, if it exists. + * This RegExp matches # followed by 3 or 6 hex digits. + * @const + * @type {RegExp} * @private */ -goog.dom.getRequiredElementHelper_ = function(doc, id) { - // To prevent users passing in Elements as is permitted in getElement(). - goog.asserts.assertString(id); - var element = goog.dom.getElementHelper_(doc, id); - element = goog.asserts.assertElement(element, - 'No element found with id: ' + id); - return element; -}; +ol.color.hexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i; /** - * Alias for getElement. - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. - * @deprecated Use {@link goog.dom.getElement} instead. + * @see goog.color.rgbColorRe_ + * @const + * @type {RegExp} + * @private */ -goog.dom.$ = goog.dom.getElement; +ol.color.rgbColorRe_ = + /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i; /** - * Looks up elements by both tag and class name, using browser native functions - * ({@code querySelectorAll}, {@code getElementsByTagName} or - * {@code getElementsByClassName}) where possible. This function - * is a useful, if limited, way of collecting a list of DOM elements - * with certain characteristics. {@code goog.dom.query} offers a - * more powerful and general solution which allows matching on CSS3 - * selector expressions, but at increased cost in code size. If all you - * need is particular tags belonging to a single class, this function - * is fast and sleek. - * - * Note that tag names are case sensitive in the SVG namespace, and this - * function converts opt_tag to uppercase for comparisons. For queries in the - * SVG namespace you should use querySelector or querySelectorAll instead. - * https://bugzilla.mozilla.org/show_bug.cgi?id=963870 - * https://bugs.webkit.org/show_bug.cgi?id=83438 - * - * @see {goog.dom.query} - * - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return { {length: number} } Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * @see goog.color.alpha.rgbaColorRe_ + * @const + * @type {RegExp} + * @private */ -goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { - return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class, - opt_el); -}; +ol.color.rgbaColorRe_ = + /^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i; /** - * Returns a static, array-like list of the elements with the provided - * className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return { {length: number} } The items found with the class name provided. + * @param {ol.Color} dst Destination. + * @param {ol.Color} src Source. + * @param {ol.Color=} opt_color Color. + * @return {ol.Color} Color. */ -goog.dom.getElementsByClass = function(className, opt_el) { - var parent = opt_el || document; - if (goog.dom.canUseQuerySelector_(parent)) { - return parent.querySelectorAll('.' + className); +ol.color.blend = function(dst, src, opt_color) { + // http://en.wikipedia.org/wiki/Alpha_compositing + // FIXME do we need to scale by 255? + var out = goog.isDef(opt_color) ? opt_color : []; + var dstA = dst[3]; + var srcA = src[3]; + if (dstA == 1) { + out[0] = (src[0] * srcA + dst[0] * (1 - srcA) + 0.5) | 0; + out[1] = (src[1] * srcA + dst[1] * (1 - srcA) + 0.5) | 0; + out[2] = (src[2] * srcA + dst[2] * (1 - srcA) + 0.5) | 0; + out[3] = 1; + } else if (srcA === 0) { + out[0] = dst[0]; + out[1] = dst[1]; + out[2] = dst[2]; + out[3] = dstA; + } else { + var outA = srcA + dstA * (1 - srcA); + if (outA === 0) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 0; + } else { + out[0] = ((src[0] * srcA + dst[0] * dstA * (1 - srcA)) / outA + 0.5) | 0; + out[1] = ((src[1] * srcA + dst[1] * dstA * (1 - srcA)) / outA + 0.5) | 0; + out[2] = ((src[2] * srcA + dst[2] * dstA * (1 - srcA)) / outA + 0.5) | 0; + out[3] = outA; + } } - return goog.dom.getElementsByTagNameAndClass_( - document, '*', className, opt_el); + goog.asserts.assert(ol.color.isValid(out), + 'Output color of blend should be a valid color'); + return out; }; /** - * Returns the first element with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {Element|Document=} opt_el Optional element to look in. - * @return {Element} The first item with the class name provided. + * Return the color as an array. This function maintains a cache of calculated + * arrays which means the result should not be modified. + * @param {ol.Color|string} color Color. + * @return {ol.Color} Color. + * @api */ -goog.dom.getElementByClass = function(className, opt_el) { - var parent = opt_el || document; - var retVal = null; - if (goog.dom.canUseQuerySelector_(parent)) { - retVal = parent.querySelector('.' + className); +ol.color.asArray = function(color) { + if (goog.isArray(color)) { + return color; } else { - retVal = goog.dom.getElementsByTagNameAndClass_( - document, '*', className, opt_el)[0]; + goog.asserts.assert(goog.isString(color), 'Color should be a string'); + return ol.color.fromString(color); } - return retVal || null; }; /** - * Ensures an element with the given className exists, and then returns the - * first element with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {!Element|!Document=} opt_root Optional element or document to look - * in. - * @return {!Element} The first item with the class name provided. - * @throws {goog.asserts.AssertionError} Thrown if no element is found. + * Return the color as an rgba string. + * @param {ol.Color|string} color Color. + * @return {string} Rgba string. + * @api */ -goog.dom.getRequiredElementByClass = function(className, opt_root) { - var retValue = goog.dom.getElementByClass(className, opt_root); - return goog.asserts.assert(retValue, - 'No element found with className: ' + className); +ol.color.asString = function(color) { + if (goog.isString(color)) { + return color; + } else { + goog.asserts.assert(goog.isArray(color), 'Color should be an array'); + return ol.color.toString(color); + } }; /** - * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and - * fast W3C Selectors API. - * @param {!(Element|Document)} parent The parent document object. - * @return {boolean} whether or not we can use parent.querySelector* APIs. - * @private + * @param {ol.Color} color1 Color1. + * @param {ol.Color} color2 Color2. + * @return {boolean} Equals. */ -goog.dom.canUseQuerySelector_ = function(parent) { - return !!(parent.querySelectorAll && parent.querySelector); +ol.color.equals = function(color1, color2) { + return color1 === color2 || ( + color1[0] == color2[0] && color1[1] == color2[1] && + color1[2] == color2[2] && color1[3] == color2[3]); }; /** - * Helper for {@code getElementsByTagNameAndClass}. - * @param {!Document} doc The document to get the elements in. - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return { {length: number} } Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). - * @private + * @param {string} s String. + * @return {ol.Color} Color. */ -goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class, - opt_el) { - var parent = opt_el || doc; - var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : ''; - - if (goog.dom.canUseQuerySelector_(parent) && - (tagName || opt_class)) { - var query = tagName + (opt_class ? '.' + opt_class : ''); - return parent.querySelectorAll(query); - } +ol.color.fromString = ( + /** + * @return {function(string): ol.Color} + */ + function() { - // Use the native getElementsByClassName if available, under the assumption - // that even when the tag name is specified, there will be fewer elements to - // filter through when going by class than by tag name - if (opt_class && parent.getElementsByClassName) { - var els = parent.getElementsByClassName(opt_class); + // We maintain a small cache of parsed strings. To provide cheap LRU-like + // semantics, whenever the cache grows too large we simply delete an + // arbitrary 25% of the entries. - if (tagName) { - var arrayLike = {}; - var len = 0; + /** + * @const + * @type {number} + */ + var MAX_CACHE_SIZE = 1024; - // Filter for specific tags if requested. - for (var i = 0, el; el = els[i]; i++) { - if (tagName == el.nodeName) { - arrayLike[len++] = el; - } - } - arrayLike.length = len; + /** + * @type {Object.<string, ol.Color>} + */ + var cache = {}; - return arrayLike; - } else { - return els; - } - } + /** + * @type {number} + */ + var cacheSize = 0; - var els = parent.getElementsByTagName(tagName || '*'); + return ( + /** + * @param {string} s String. + * @return {ol.Color} Color. + */ + function(s) { + var color; + if (cache.hasOwnProperty(s)) { + color = cache[s]; + } else { + if (cacheSize >= MAX_CACHE_SIZE) { + var i = 0; + var key; + for (key in cache) { + if ((i++ & 3) === 0) { + delete cache[key]; + --cacheSize; + } + } + } + color = ol.color.fromStringInternal_(s); + cache[s] = color; + ++cacheSize; + } + return color; + }); - if (opt_class) { - var arrayLike = {}; - var len = 0; - for (var i = 0, el; el = els[i]; i++) { - var className = el.className; - // Check if className has a split function since SVG className does not. - if (typeof className.split == 'function' && - goog.array.contains(className.split(/\s+/), opt_class)) { - arrayLike[len++] = el; - } + })(); + + +/** + * @param {string} s String. + * @private + * @return {ol.Color} Color. + */ +ol.color.fromStringInternal_ = function(s) { + + var isHex = false; + if (ol.ENABLE_NAMED_COLORS && goog.color.names.hasOwnProperty(s)) { + // goog.color.names does not have a type declaration, so add a typecast + s = /** @type {string} */ (goog.color.names[s]); + isHex = true; + } + + var r, g, b, a, color, match; + if (isHex || (match = ol.color.hexColorRe_.exec(s))) { // hex + var n = s.length - 1; // number of hex digits + goog.asserts.assert(n == 3 || n == 6, + 'Color string length should be 3 or 6'); + var d = n == 3 ? 1 : 2; // number of digits per channel + r = parseInt(s.substr(1 + 0 * d, d), 16); + g = parseInt(s.substr(1 + 1 * d, d), 16); + b = parseInt(s.substr(1 + 2 * d, d), 16); + if (d == 1) { + r = (r << 4) + r; + g = (g << 4) + g; + b = (b << 4) + b; } - arrayLike.length = len; - return arrayLike; + a = 1; + color = [r, g, b, a]; + goog.asserts.assert(ol.color.isValid(color), + 'Color should be a valid color'); + return color; + } else if ((match = ol.color.rgbaColorRe_.exec(s))) { // rgba() + r = Number(match[1]); + g = Number(match[2]); + b = Number(match[3]); + a = Number(match[4]); + color = [r, g, b, a]; + return ol.color.normalize(color, color); + } else if ((match = ol.color.rgbColorRe_.exec(s))) { // rgb() + r = Number(match[1]); + g = Number(match[2]); + b = Number(match[3]); + color = [r, g, b, 1]; + return ol.color.normalize(color, color); } else { - return els; + goog.asserts.fail(s + ' is not a valid color'); } + }; /** - * Alias for {@code getElementsByTagNameAndClass}. - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {Element=} opt_el Optional element to look in. - * @return { {length: number} } Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). - * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead. + * @param {ol.Color} color Color. + * @return {boolean} Is valid. */ -goog.dom.$$ = goog.dom.getElementsByTagNameAndClass; +ol.color.isValid = function(color) { + return 0 <= color[0] && color[0] < 256 && + 0 <= color[1] && color[1] < 256 && + 0 <= color[2] && color[2] < 256 && + 0 <= color[3] && color[3] <= 1; +}; /** - * Sets multiple properties on a node. - * @param {Element} element DOM node to set properties on. - * @param {Object} properties Hash of property:value pairs. + * @param {ol.Color} color Color. + * @param {ol.Color=} opt_color Color. + * @return {ol.Color} Clamped color. */ -goog.dom.setProperties = function(element, properties) { - goog.object.forEach(properties, function(val, key) { - if (key == 'style') { - element.style.cssText = val; - } else if (key == 'class') { - element.className = val; - } else if (key == 'for') { - element.htmlFor = val; - } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) { - element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val); - } else if (goog.string.startsWith(key, 'aria-') || - goog.string.startsWith(key, 'data-')) { - element.setAttribute(key, val); - } else { - element[key] = val; - } - }); +ol.color.normalize = function(color, opt_color) { + var result = goog.isDef(opt_color) ? opt_color : []; + result[0] = goog.math.clamp((color[0] + 0.5) | 0, 0, 255); + result[1] = goog.math.clamp((color[1] + 0.5) | 0, 0, 255); + result[2] = goog.math.clamp((color[2] + 0.5) | 0, 0, 255); + result[3] = goog.math.clamp(color[3], 0, 1); + return result; }; /** - * Map of attributes that should be set using - * element.setAttribute(key, val) instead of element[key] = val. Used - * by goog.dom.setProperties. - * - * @private {!Object<string, string>} - * @const + * @param {ol.Color} color Color. + * @return {string} String. */ -goog.dom.DIRECT_ATTRIBUTE_MAP_ = { - 'cellpadding': 'cellPadding', - 'cellspacing': 'cellSpacing', - 'colspan': 'colSpan', - 'frameborder': 'frameBorder', - 'height': 'height', - 'maxlength': 'maxLength', - 'role': 'role', - 'rowspan': 'rowSpan', - 'type': 'type', - 'usemap': 'useMap', - 'valign': 'vAlign', - 'width': 'width' +ol.color.toString = function(color) { + var r = color[0]; + if (r != (r | 0)) { + r = (r + 0.5) | 0; + } + var g = color[1]; + if (g != (g | 0)) { + g = (g + 0.5) | 0; + } + var b = color[2]; + if (b != (b | 0)) { + b = (b + 0.5) | 0; + } + var a = color[3]; + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; }; /** - * Gets the dimensions of the viewport. - * - * Gecko Standards mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Width of viewport including scrollbar. - * body.clientWidth Width of body element. - * - * docEl.clientHeight Height of viewport excluding scrollbar. - * win.innerHeight Height of viewport including scrollbar. - * body.clientHeight Height of document. - * - * Gecko Backwards compatible mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Width of viewport including scrollbar. - * body.clientWidth Width of viewport excluding scrollbar. - * - * docEl.clientHeight Height of document. - * win.innerHeight Height of viewport including scrollbar. - * body.clientHeight Height of viewport excluding scrollbar. - * - * IE6/7 Standards mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Undefined. - * body.clientWidth Width of body element. - * - * docEl.clientHeight Height of viewport excluding scrollbar. - * win.innerHeight Undefined. - * body.clientHeight Height of document element. - * - * IE5 + IE6/7 Backwards compatible mode: - * docEl.clientWidth 0. - * win.innerWidth Undefined. - * body.clientWidth Width of viewport excluding scrollbar. - * - * docEl.clientHeight 0. - * win.innerHeight Undefined. - * body.clientHeight Height of viewport excluding scrollbar. - * - * Opera 9 Standards and backwards compatible mode: - * docEl.clientWidth Width of viewport excluding scrollbar. - * win.innerWidth Width of viewport including scrollbar. - * body.clientWidth Width of viewport excluding scrollbar. - * - * docEl.clientHeight Height of document. - * win.innerHeight Height of viewport including scrollbar. - * body.clientHeight Height of viewport excluding scrollbar. - * - * WebKit: - * Safari 2 - * docEl.clientHeight Same as scrollHeight. - * docEl.clientWidth Same as innerWidth. - * win.innerWidth Width of viewport excluding scrollbar. - * win.innerHeight Height of the viewport including scrollbar. - * frame.innerHeight Height of the viewport exluding scrollbar. - * - * Safari 3 (tested in 522) - * - * docEl.clientWidth Width of viewport excluding scrollbar. - * docEl.clientHeight Height of viewport excluding scrollbar in strict mode. - * body.clientHeight Height of viewport excluding scrollbar in quirks mode. - * - * @param {Window=} opt_window Optional window element to test. - * @return {!goog.math.Size} Object with values 'width' and 'height'. + * @param {!ol.Color} color Color. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {!ol.Color=} opt_color Color. + * @return {ol.Color} Transformed color. */ -goog.dom.getViewportSize = function(opt_window) { - // TODO(arv): This should not take an argument - return goog.dom.getViewportSize_(opt_window || window); +ol.color.transform = function(color, transform, opt_color) { + var result = goog.isDef(opt_color) ? opt_color : []; + result = goog.vec.Mat4.multVec3(transform, color, result); + goog.asserts.assert(goog.isArray(result), 'result should be an array'); + result[3] = color[3]; + return ol.color.normalize(result, result); }; /** - * Helper for {@code getViewportSize}. - * @param {Window} win The window to get the view port size for. - * @return {!goog.math.Size} Object with values 'width' and 'height'. - * @private + * @param {ol.Color|string} color1 Color2. + * @param {ol.Color|string} color2 Color2. + * @return {boolean} Equals. */ -goog.dom.getViewportSize_ = function(win) { - var doc = win.document; - var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body; - return new goog.math.Size(el.clientWidth, el.clientHeight); +ol.color.stringOrColorEquals = function(color1, color2) { + if (color1 === color2 || color1 == color2) { + return true; + } + if (goog.isString(color1)) { + color1 = ol.color.fromString(color1); + } + if (goog.isString(color2)) { + color2 = ol.color.fromString(color2); + } + return ol.color.equals(color1, color2); }; +goog.provide('ol.color.Matrix'); + +goog.require('goog.vec.Mat4'); -/** - * Calculates the height of the document. - * - * @return {number} The height of the current document. - */ -goog.dom.getDocumentHeight = function() { - return goog.dom.getDocumentHeight_(window); -}; /** - * Calculates the height of the document of the given window. - * - * Function code copied from the opensocial gadget api: - * gadgets.window.adjustHeight(opt_height) - * - * @private - * @param {!Window} win The window whose document height to retrieve. - * @return {number} The height of the document of the given window. + * @constructor */ -goog.dom.getDocumentHeight_ = function(win) { - // NOTE(eae): This method will return the window size rather than the document - // size in webkit quirks mode. - var doc = win.document; - var height = 0; +ol.color.Matrix = function() { - if (doc) { - // Calculating inner content height is hard and different between - // browsers rendering in Strict vs. Quirks mode. We use a combination of - // three properties within document.body and document.documentElement: - // - scrollHeight - // - offsetHeight - // - clientHeight - // These values differ significantly between browsers and rendering modes. - // But there are patterns. It just takes a lot of time and persistence - // to figure out. + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.colorMatrix_ = goog.vec.Mat4.createNumber(); - var body = doc.body; - var docEl = doc.documentElement; - if (!(docEl && body)) { - return 0; - } + /** + * @private + * @type {number|undefined} + */ + this.brightness_ = undefined; - // Get the height of the viewport - var vh = goog.dom.getViewportSize_(win).height; - if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) { - // In Strict mode: - // The inner content height is contained in either: - // document.documentElement.scrollHeight - // document.documentElement.offsetHeight - // Based on studying the values output by different browsers, - // use the value that's NOT equal to the viewport height found above. - height = docEl.scrollHeight != vh ? - docEl.scrollHeight : docEl.offsetHeight; - } else { - // In Quirks mode: - // documentElement.clientHeight is equal to documentElement.offsetHeight - // except in IE. In most browsers, document.documentElement can be used - // to calculate the inner content height. - // However, in other browsers (e.g. IE), document.body must be used - // instead. How do we know which one to use? - // If document.documentElement.clientHeight does NOT equal - // document.documentElement.offsetHeight, then use document.body. - var sh = docEl.scrollHeight; - var oh = docEl.offsetHeight; - if (docEl.clientHeight != oh) { - sh = body.scrollHeight; - oh = body.offsetHeight; - } + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.brightnessMatrix_ = goog.vec.Mat4.createNumber(); - // Detect whether the inner content height is bigger or smaller - // than the bounding box (viewport). If bigger, take the larger - // value. If smaller, take the smaller value. - if (sh > vh) { - // Content is larger - height = sh > oh ? sh : oh; - } else { - // Content is smaller - height = sh < oh ? sh : oh; - } - } - } + /** + * @private + * @type {number|undefined} + */ + this.contrast_ = undefined; - return height; -}; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.contrastMatrix_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {number|undefined} + */ + this.hue_ = undefined; -/** - * Gets the page scroll distance as a coordinate object. - * - * @param {Window=} opt_window Optional window element to test. - * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. - * @deprecated Use {@link goog.dom.getDocumentScroll} instead. - */ -goog.dom.getPageScroll = function(opt_window) { - var win = opt_window || goog.global || window; - return goog.dom.getDomHelper(win.document).getDocumentScroll(); -}; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.hueMatrix_ = goog.vec.Mat4.createNumber(); + + /** + * @private + * @type {number|undefined} + */ + this.saturation_ = undefined; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.saturationMatrix_ = goog.vec.Mat4.createNumber(); -/** - * Gets the document scroll distance as a coordinate object. - * - * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. - */ -goog.dom.getDocumentScroll = function() { - return goog.dom.getDocumentScroll_(document); }; /** - * Helper for {@code getDocumentScroll}. - * - * @param {!Document} doc The document to get the scroll for. - * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. - * @private + * @param {!goog.vec.Mat4.Number} matrix Matrix. + * @param {number} value Brightness value. + * @return {!goog.vec.Mat4.Number} Matrix. */ -goog.dom.getDocumentScroll_ = function(doc) { - var el = goog.dom.getDocumentScrollElement_(doc); - var win = goog.dom.getWindow_(doc); - if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') && - win.pageYOffset != el.scrollTop) { - // The keyboard on IE10 touch devices shifts the page using the pageYOffset - // without modifying scrollTop. For this case, we want the body scroll - // offsets. - return new goog.math.Coordinate(el.scrollLeft, el.scrollTop); - } - return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft, - win.pageYOffset || el.scrollTop); +ol.color.Matrix.makeBrightness = function(matrix, value) { + goog.vec.Mat4.makeTranslate(matrix, value, value, value); + return matrix; }; /** - * Gets the document scroll element. - * @return {!Element} Scrolling element. + * @param {!goog.vec.Mat4.Number} matrix Matrix. + * @param {number} value Contrast value. + * @return {!goog.vec.Mat4.Number} Matrix. */ -goog.dom.getDocumentScrollElement = function() { - return goog.dom.getDocumentScrollElement_(document); +ol.color.Matrix.makeContrast = function(matrix, value) { + goog.vec.Mat4.makeScale(matrix, value, value, value); + var translateValue = (-0.5 * value + 0.5); + goog.vec.Mat4.setColumnValues(matrix, 3, + translateValue, translateValue, translateValue, 1); + return matrix; }; /** - * Helper for {@code getDocumentScrollElement}. - * @param {!Document} doc The document to get the scroll element for. - * @return {!Element} Scrolling element. - * @private + * @param {!goog.vec.Mat4.Number} matrix Matrix. + * @param {number} value Hue value. + * @return {!goog.vec.Mat4.Number} Matrix. */ -goog.dom.getDocumentScrollElement_ = function(doc) { - // WebKit needs body.scrollLeft in both quirks mode and strict mode. We also - // default to the documentElement if the document does not have a body (e.g. - // a SVG document). - if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) { - return doc.documentElement; - } - return doc.body || doc.documentElement; +ol.color.Matrix.makeHue = function(matrix, value) { + var cosHue = Math.cos(value); + var sinHue = Math.sin(value); + var v00 = 0.213 + cosHue * 0.787 - sinHue * 0.213; + var v01 = 0.715 - cosHue * 0.715 - sinHue * 0.715; + var v02 = 0.072 - cosHue * 0.072 + sinHue * 0.928; + var v03 = 0; + var v10 = 0.213 - cosHue * 0.213 + sinHue * 0.143; + var v11 = 0.715 + cosHue * 0.285 + sinHue * 0.140; + var v12 = 0.072 - cosHue * 0.072 - sinHue * 0.283; + var v13 = 0; + var v20 = 0.213 - cosHue * 0.213 - sinHue * 0.787; + var v21 = 0.715 - cosHue * 0.715 + sinHue * 0.715; + var v22 = 0.072 + cosHue * 0.928 + sinHue * 0.072; + var v23 = 0; + var v30 = 0; + var v31 = 0; + var v32 = 0; + var v33 = 1; + goog.vec.Mat4.setFromValues(matrix, + v00, v10, v20, v30, + v01, v11, v21, v31, + v02, v12, v22, v32, + v03, v13, v23, v33); + return matrix; }; /** - * Gets the window object associated with the given document. - * - * @param {Document=} opt_doc Document object to get window for. - * @return {!Window} The window associated with the given document. - */ -goog.dom.getWindow = function(opt_doc) { - // TODO(arv): This should not take an argument. - return opt_doc ? goog.dom.getWindow_(opt_doc) : window; + * @param {!goog.vec.Mat4.Number} matrix Matrix. + * @param {number} value Saturation value. + * @return {!goog.vec.Mat4.Number} Matrix. + */ +ol.color.Matrix.makeSaturation = function(matrix, value) { + var v00 = 0.213 + 0.787 * value; + var v01 = 0.715 - 0.715 * value; + var v02 = 0.072 - 0.072 * value; + var v03 = 0; + var v10 = 0.213 - 0.213 * value; + var v11 = 0.715 + 0.285 * value; + var v12 = 0.072 - 0.072 * value; + var v13 = 0; + var v20 = 0.213 - 0.213 * value; + var v21 = 0.715 - 0.715 * value; + var v22 = 0.072 + 0.928 * value; + var v23 = 0; + var v30 = 0; + var v31 = 0; + var v32 = 0; + var v33 = 1; + goog.vec.Mat4.setFromValues(matrix, + v00, v10, v20, v30, + v01, v11, v21, v31, + v02, v12, v22, v32, + v03, v13, v23, v33); + return matrix; }; /** - * Helper for {@code getWindow}. - * - * @param {!Document} doc Document object to get window for. - * @return {!Window} The window associated with the given document. - * @private + * @param {number|undefined} brightness Brightness. + * @param {number|undefined} contrast Contrast. + * @param {number|undefined} hue Hue. + * @param {number|undefined} saturation Saturation. + * @return {!goog.vec.Mat4.Number} Matrix. */ -goog.dom.getWindow_ = function(doc) { - return doc.parentWindow || doc.defaultView; +ol.color.Matrix.prototype.getMatrix = function( + brightness, contrast, hue, saturation) { + var colorMatrixDirty = false; + if (goog.isDef(brightness) && brightness !== this.brightness_) { + ol.color.Matrix.makeBrightness(this.brightnessMatrix_, brightness); + this.brightness_ = brightness; + colorMatrixDirty = true; + } + if (goog.isDef(contrast) && contrast !== this.contrast_) { + ol.color.Matrix.makeContrast(this.contrastMatrix_, contrast); + this.contrast_ = contrast; + colorMatrixDirty = true; + } + if (goog.isDef(hue) && hue !== this.hue_) { + ol.color.Matrix.makeHue(this.hueMatrix_, hue); + this.hue_ = hue; + colorMatrixDirty = true; + } + if (goog.isDef(saturation) && saturation !== this.saturation_) { + ol.color.Matrix.makeSaturation(this.saturationMatrix_, saturation); + this.saturation_ = saturation; + colorMatrixDirty = true; + } + if (colorMatrixDirty) { + var colorMatrix = this.colorMatrix_; + goog.vec.Mat4.makeIdentity(colorMatrix); + if (goog.isDef(contrast)) { + goog.vec.Mat4.multMat(colorMatrix, this.contrastMatrix_, colorMatrix); + } + if (goog.isDef(brightness)) { + goog.vec.Mat4.multMat(colorMatrix, this.brightnessMatrix_, colorMatrix); + } + if (goog.isDef(saturation)) { + goog.vec.Mat4.multMat(colorMatrix, this.saturationMatrix_, colorMatrix); + } + if (goog.isDef(hue)) { + goog.vec.Mat4.multMat(colorMatrix, this.hueMatrix_, colorMatrix); + } + } + return this.colorMatrix_; }; +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Returns a dom node with a set of attributes. This function accepts varargs - * for subsequent nodes to be added. Subsequent nodes will be added to the - * first node as childNodes. - * - * So: - * <code>createDom('div', null, createDom('p'), createDom('p'));</code> - * would return a div with two child paragraphs + * @fileoverview Browser capability checks for the dom package. * - * @param {string} tagName Tag to create. - * @param {(Object|Array<string>|string)=} opt_attributes If object, then a map - * of name-value pairs for attributes. If a string, then this is the - * className of the new element. If an array, the elements will be joined - * together as the className of the new element. - * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or - * strings for text nodes. If one of the var_args is an array or NodeList,i - * its elements will be added as childNodes instead. - * @return {!Element} Reference to a DOM node. */ -goog.dom.createDom = function(tagName, opt_attributes, var_args) { - return goog.dom.createDom_(document, arguments); -}; -/** - * Helper for {@code createDom}. - * @param {!Document} doc The document to create the DOM in. - * @param {!Arguments} args Argument object passed from the callers. See - * {@code goog.dom.createDom} for details. - * @return {!Element} Reference to a DOM node. - * @private - */ -goog.dom.createDom_ = function(doc, args) { - var tagName = args[0]; - var attributes = args[1]; +goog.provide('goog.dom.BrowserFeature'); - // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/ - // dhtml/reference/properties/name_2.asp - // Also does not allow setting of 'type' attribute on 'input' or 'button'. - if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes && - (attributes.name || attributes.type)) { - var tagNameArr = ['<', tagName]; - if (attributes.name) { - tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), - '"'); - } - if (attributes.type) { - tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), - '"'); +goog.require('goog.userAgent'); - // Clone attributes map to remove 'type' without mutating the input. - var clone = {}; - goog.object.extend(clone, attributes); - // JSCompiler can't see how goog.object.extend added this property, - // because it was essentially added by reflection. - // So it needs to be quoted. - delete clone['type']; +/** + * Enum of browser capabilities. + * @enum {boolean} + */ +goog.dom.BrowserFeature = { + /** + * Whether attributes 'name' and 'type' can be added to an element after it's + * created. False in Internet Explorer prior to version 9. + */ + CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE || + goog.userAgent.isDocumentModeOrHigher(9), - attributes = clone; - } - tagNameArr.push('>'); - tagName = tagNameArr.join(''); - } + /** + * Whether we can use element.children to access an element's Element + * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment + * nodes in the collection.) + */ + CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE || + goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) || + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'), - var element = doc.createElement(tagName); + /** + * Opera, Safari 3, and Internet Explorer 9 all support innerText but they + * include text nodes in script and style tags. Not document-mode-dependent. + */ + CAN_USE_INNER_TEXT: ( + goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')), - if (attributes) { - if (goog.isString(attributes)) { - element.className = attributes; - } else if (goog.isArray(attributes)) { - element.className = attributes.join(' '); - } else { - goog.dom.setProperties(element, attributes); - } - } + /** + * MSIE, Opera, and Safari>=4 support element.parentElement to access an + * element's parent if it is an Element. + */ + CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA || + goog.userAgent.WEBKIT, - if (args.length > 2) { - goog.dom.append_(doc, element, args, 2); - } + /** + * Whether NoScope elements need a scoped element written before them in + * innerHTML. + * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1 + */ + INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE, - return element; + /** + * Whether we use legacy IE range API. + */ + LEGACY_IE_RANGES: goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) }; +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Appends a node with text or other nodes. - * @param {!Document} doc The document to create new nodes in. - * @param {!Node} parent The node to append nodes to. - * @param {!Arguments} args The values to add. See {@code goog.dom.append}. - * @param {number} startIndex The index of the array to start from. - * @private + * @fileoverview Defines the goog.dom.TagName enum. This enumerates + * all HTML tag names specified in either the the W3C HTML 4.01 index of + * elements or the HTML5 draft specification. + * + * References: + * http://www.w3.org/TR/html401/index/elements.html + * http://dev.w3.org/html5/spec/section-index.html + * */ -goog.dom.append_ = function(doc, parent, args, startIndex) { - function childHandler(child) { - // TODO(user): More coercion, ala MochiKit? - if (child) { - parent.appendChild(goog.isString(child) ? - doc.createTextNode(child) : child); - } - } - - for (var i = startIndex; i < args.length; i++) { - var arg = args[i]; - // TODO(attila): Fix isArrayLike to return false for a text node. - if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) { - // If the argument is a node list, not a real array, use a clone, - // because forEach can't be used to mutate a NodeList. - goog.array.forEach(goog.dom.isNodeList(arg) ? - goog.array.toArray(arg) : arg, - childHandler); - } else { - childHandler(arg); - } - } -}; +goog.provide('goog.dom.TagName'); /** - * Alias for {@code createDom}. - * @param {string} tagName Tag to create. - * @param {(string|Object)=} opt_attributes If object, then a map of name-value - * pairs for attributes. If a string, then this is the className of the new - * element. - * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or - * strings for text nodes. If one of the var_args is an array, its - * children will be added as childNodes instead. - * @return {!Element} Reference to a DOM node. - * @deprecated Use {@link goog.dom.createDom} instead. + * Enum of all html tag names specified by the W3C HTML4.01 and HTML5 + * specifications. + * @enum {string} */ -goog.dom.$dom = goog.dom.createDom; +goog.dom.TagName = { + A: 'A', + ABBR: 'ABBR', + ACRONYM: 'ACRONYM', + ADDRESS: 'ADDRESS', + APPLET: 'APPLET', + AREA: 'AREA', + ARTICLE: 'ARTICLE', + ASIDE: 'ASIDE', + AUDIO: 'AUDIO', + B: 'B', + BASE: 'BASE', + BASEFONT: 'BASEFONT', + BDI: 'BDI', + BDO: 'BDO', + BIG: 'BIG', + BLOCKQUOTE: 'BLOCKQUOTE', + BODY: 'BODY', + BR: 'BR', + BUTTON: 'BUTTON', + CANVAS: 'CANVAS', + CAPTION: 'CAPTION', + CENTER: 'CENTER', + CITE: 'CITE', + CODE: 'CODE', + COL: 'COL', + COLGROUP: 'COLGROUP', + COMMAND: 'COMMAND', + DATA: 'DATA', + DATALIST: 'DATALIST', + DD: 'DD', + DEL: 'DEL', + DETAILS: 'DETAILS', + DFN: 'DFN', + DIALOG: 'DIALOG', + DIR: 'DIR', + DIV: 'DIV', + DL: 'DL', + DT: 'DT', + EM: 'EM', + EMBED: 'EMBED', + FIELDSET: 'FIELDSET', + FIGCAPTION: 'FIGCAPTION', + FIGURE: 'FIGURE', + FONT: 'FONT', + FOOTER: 'FOOTER', + FORM: 'FORM', + FRAME: 'FRAME', + FRAMESET: 'FRAMESET', + H1: 'H1', + H2: 'H2', + H3: 'H3', + H4: 'H4', + H5: 'H5', + H6: 'H6', + HEAD: 'HEAD', + HEADER: 'HEADER', + HGROUP: 'HGROUP', + HR: 'HR', + HTML: 'HTML', + I: 'I', + IFRAME: 'IFRAME', + IMG: 'IMG', + INPUT: 'INPUT', + INS: 'INS', + ISINDEX: 'ISINDEX', + KBD: 'KBD', + KEYGEN: 'KEYGEN', + LABEL: 'LABEL', + LEGEND: 'LEGEND', + LI: 'LI', + LINK: 'LINK', + MAP: 'MAP', + MARK: 'MARK', + MATH: 'MATH', + MENU: 'MENU', + META: 'META', + METER: 'METER', + NAV: 'NAV', + NOFRAMES: 'NOFRAMES', + NOSCRIPT: 'NOSCRIPT', + OBJECT: 'OBJECT', + OL: 'OL', + OPTGROUP: 'OPTGROUP', + OPTION: 'OPTION', + OUTPUT: 'OUTPUT', + P: 'P', + PARAM: 'PARAM', + PRE: 'PRE', + PROGRESS: 'PROGRESS', + Q: 'Q', + RP: 'RP', + RT: 'RT', + RUBY: 'RUBY', + S: 'S', + SAMP: 'SAMP', + SCRIPT: 'SCRIPT', + SECTION: 'SECTION', + SELECT: 'SELECT', + SMALL: 'SMALL', + SOURCE: 'SOURCE', + SPAN: 'SPAN', + STRIKE: 'STRIKE', + STRONG: 'STRONG', + STYLE: 'STYLE', + SUB: 'SUB', + SUMMARY: 'SUMMARY', + SUP: 'SUP', + SVG: 'SVG', + TABLE: 'TABLE', + TBODY: 'TBODY', + TD: 'TD', + TEXTAREA: 'TEXTAREA', + TFOOT: 'TFOOT', + TH: 'TH', + THEAD: 'THEAD', + TIME: 'TIME', + TITLE: 'TITLE', + TR: 'TR', + TRACK: 'TRACK', + TT: 'TT', + U: 'U', + UL: 'UL', + VAR: 'VAR', + VIDEO: 'VIDEO', + WBR: 'WBR' +}; +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Creates a new element. - * @param {string} name Tag name. - * @return {!Element} The new element. + * @fileoverview Utilities for HTML element tag names. */ -goog.dom.createElement = function(name) { - return document.createElement(name); -}; +goog.provide('goog.dom.tags'); + +goog.require('goog.object'); /** - * Creates a new text node. - * @param {number|string} content Content. - * @return {!Text} The new text node. + * The void elements specified by + * http://www.w3.org/TR/html-markup/syntax.html#void-elements. + * @const + * @type {!Object} + * @private */ -goog.dom.createTextNode = function(content) { - return document.createTextNode(String(content)); -}; +goog.dom.tags.VOID_TAGS_ = goog.object.createSet(('area,base,br,col,command,' + + 'embed,hr,img,input,keygen,link,meta,param,source,track,wbr').split(',')); /** - * Create a table. - * @param {number} rows The number of rows in the table. Must be >= 1. - * @param {number} columns The number of columns in the table. Must be >= 1. - * @param {boolean=} opt_fillWithNbsp If true, fills table entries with - * {@code goog.string.Unicode.NBSP} characters. - * @return {!Element} The created table. + * Checks whether the tag is void (with no contents allowed and no legal end + * tag), for example 'br'. + * @param {string} tagName The tag name in lower case. + * @return {boolean} */ -goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) { - // TODO(user): Return HTMLTableElement, also in prototype function. - // Callers need to be updated to e.g. not assign numbers to table.cellSpacing. - return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp); +goog.dom.tags.isVoidTag = function(tagName) { + return goog.dom.tags.VOID_TAGS_[tagName] === true; }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.string.TypedString'); -/** - * Create a table. - * @param {!Document} doc Document object to use to create the table. - * @param {number} rows The number of rows in the table. Must be >= 1. - * @param {number} columns The number of columns in the table. Must be >= 1. - * @param {boolean} fillWithNbsp If true, fills table entries with - * {@code goog.string.Unicode.NBSP} characters. - * @return {!HTMLTableElement} The created table. - * @private - */ -goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) { - var table = /** @type {!HTMLTableElement} */ - (doc.createElement(goog.dom.TagName.TABLE)); - var tbody = table.appendChild(doc.createElement(goog.dom.TagName.TBODY)); - for (var i = 0; i < rows; i++) { - var tr = doc.createElement(goog.dom.TagName.TR); - for (var j = 0; j < columns; j++) { - var td = doc.createElement(goog.dom.TagName.TD); - // IE <= 9 will create a text node if we set text content to the empty - // string, so we avoid doing it unless necessary. This ensures that the - // same DOM tree is returned on all browsers. - if (fillWithNbsp) { - goog.dom.setTextContent(td, goog.string.Unicode.NBSP); - } - tr.appendChild(td); - } - tbody.appendChild(tr); - } - return table; -}; /** - * Converts HTML markup into a node. - * @param {!goog.html.SafeHtml} html The HTML markup to convert. - * @return {!Node} The resulting node. + * Wrapper for strings that conform to a data type or language. + * + * Implementations of this interface are wrappers for strings, and typically + * associate a type contract with the wrapped string. Concrete implementations + * of this interface may choose to implement additional run-time type checking, + * see for example {@code goog.html.SafeHtml}. If available, client code that + * needs to ensure type membership of an object should use the type's function + * to assert type membership, such as {@code goog.html.SafeHtml.unwrap}. + * @interface */ -goog.dom.safeHtmlToNode = function(html) { - return goog.dom.safeHtmlToNode_(document, html); -}; +goog.string.TypedString = function() {}; /** - * Helper for {@code safeHtmlToNode}. - * @param {!Document} doc The document. - * @param {!goog.html.SafeHtml} html The HTML markup to convert. - * @return {!Node} The resulting node. - * @private + * Interface marker of the TypedString interface. + * + * This property can be used to determine at runtime whether or not an object + * implements this interface. All implementations of this interface set this + * property to {@code true}. + * @type {boolean} */ -goog.dom.safeHtmlToNode_ = function(doc, html) { - var tempDiv = doc.createElement('div'); - if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { - goog.dom.safe.setInnerHtml(tempDiv, - goog.html.SafeHtml.concat(goog.html.SafeHtml.create('br'), html)); - tempDiv.removeChild(tempDiv.firstChild); - } else { - goog.dom.safe.setInnerHtml(tempDiv, html); - } - return goog.dom.childrenToNode_(doc, tempDiv); -}; +goog.string.TypedString.prototype.implementsGoogStringTypedString; /** - * Converts an HTML string into a document fragment. The string must be - * sanitized in order to avoid cross-site scripting. For example - * {@code goog.dom.htmlToDocumentFragment('<img src=x onerror=alert(0)>')} - * triggers an alert in all browsers, even if the returned document fragment - * is thrown away immediately. - * - * @param {string} htmlString The HTML string to convert. - * @return {!Node} The resulting document fragment. + * Retrieves this wrapped string's value. + * @return {!string} The wrapped string's value. */ -goog.dom.htmlToDocumentFragment = function(htmlString) { - return goog.dom.htmlToDocumentFragment_(document, htmlString); -}; +goog.string.TypedString.prototype.getTypedStringValue; + +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.string.Const'); + +goog.require('goog.asserts'); +goog.require('goog.string.TypedString'); + -// TODO(jakubvrana): Merge with {@code safeHtmlToNode_}. /** - * Helper for {@code htmlToDocumentFragment}. + * Wrapper for compile-time-constant strings. * - * @param {!Document} doc The document. - * @param {string} htmlString The HTML string to convert. - * @return {!Node} The resulting document fragment. - * @private + * Const is a wrapper for strings that can only be created from program + * constants (i.e., string literals). This property relies on a custom Closure + * compiler check that {@code goog.string.Const.from} is only invoked on + * compile-time-constant expressions. + * + * Const is useful in APIs whose correct and secure use requires that certain + * arguments are not attacker controlled: Compile-time constants are inherently + * under the control of the application and not under control of external + * attackers, and hence are safe to use in such contexts. + * + * Instances of this type must be created via its factory method + * {@code goog.string.Const.from} and not by invoking its constructor. The + * constructor intentionally takes no parameters and the type is immutable; + * hence only a default instance corresponding to the empty string can be + * obtained via constructor invocation. + * + * @see goog.string.Const#from + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} */ -goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) { - var tempDiv = doc.createElement('div'); - if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { - tempDiv.innerHTML = '<br>' + htmlString; - tempDiv.removeChild(tempDiv.firstChild); - } else { - tempDiv.innerHTML = htmlString; - } - return goog.dom.childrenToNode_(doc, tempDiv); +goog.string.Const = function() { + /** + * The wrapped value of this Const object. The field has a purposely ugly + * name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.string.Const#unwrap + * @const + * @private + */ + this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ = + goog.string.Const.TYPE_MARKER_; }; /** - * Helper for {@code htmlToDocumentFragment_}. - * @param {!Document} doc The document. - * @param {!Node} tempDiv The input node. - * @return {!Node} The resulting node. - * @private + * @override + * @const */ -goog.dom.childrenToNode_ = function(doc, tempDiv) { - if (tempDiv.childNodes.length == 1) { - return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild)); - } else { - var fragment = doc.createDocumentFragment(); - while (tempDiv.firstChild) { - fragment.appendChild(tempDiv.firstChild); - } - return fragment; - } +goog.string.Const.prototype.implementsGoogStringTypedString = true; + + +/** + * Returns this Const's value a string. + * + * IMPORTANT: In code where it is security-relevant that an object's type is + * indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap} + * instead of this method. + * + * @see goog.string.Const#unwrap + * @override + */ +goog.string.Const.prototype.getTypedStringValue = function() { + return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_; }; /** - * Returns true if the browser is in "CSS1-compatible" (standards-compliant) - * mode, false otherwise. - * @return {boolean} True if in CSS1-compatible mode. + * Returns a debug-string representation of this value. + * + * To obtain the actual string value wrapped inside an object of this type, + * use {@code goog.string.Const.unwrap}. + * + * @see goog.string.Const#unwrap + * @override */ -goog.dom.isCss1CompatMode = function() { - return goog.dom.isCss1CompatMode_(document); +goog.string.Const.prototype.toString = function() { + return 'Const{' + + this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ + + '}'; }; /** - * Returns true if the browser is in "CSS1-compatible" (standards-compliant) - * mode, false otherwise. - * @param {!Document} doc The document to check. - * @return {boolean} True if in CSS1-compatible mode. - * @private + * Performs a runtime check that the provided object is indeed an instance + * of {@code goog.string.Const}, and returns its value. + * @param {!goog.string.Const} stringConst The object to extract from. + * @return {string} The Const object's contained string, unless the run-time + * type check fails. In that case, {@code unwrap} returns an innocuous + * string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. */ -goog.dom.isCss1CompatMode_ = function(doc) { - if (goog.dom.COMPAT_MODE_KNOWN_) { - return goog.dom.ASSUME_STANDARDS_MODE; +goog.string.Const.unwrap = function(stringConst) { + // Perform additional run-time type-checking to ensure that stringConst is + // indeed an instance of the expected type. This provides some additional + // protection against security bugs due to application code that disables type + // checks. + if (stringConst instanceof goog.string.Const && + stringConst.constructor === goog.string.Const && + stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ === + goog.string.Const.TYPE_MARKER_) { + return stringConst. + stringConstValueWithSecurityContract__googStringSecurityPrivate_; + } else { + goog.asserts.fail('expected object of type Const, got \'' + + stringConst + '\''); + return 'type_error:Const'; } - - return doc.compatMode == 'CSS1Compat'; }; /** - * Determines if the given node can contain children, intended to be used for - * HTML generation. - * - * IE natively supports node.canHaveChildren but has inconsistent behavior. - * Prior to IE8 the base tag allows children and in IE9 all nodes return true - * for canHaveChildren. + * Creates a Const object from a compile-time constant string. * - * In practice all non-IE browsers allow you to add children to any node, but - * the behavior is inconsistent: + * It is illegal to invoke this function on an expression whose + * compile-time-contant value cannot be determined by the Closure compiler. * + * Correct invocations include, * <pre> - * var a = document.createElement('br'); - * a.appendChild(document.createTextNode('foo')); - * a.appendChild(document.createTextNode('bar')); - * console.log(a.childNodes.length); // 2 - * console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar" + * var s = goog.string.Const.from('hello'); + * var t = goog.string.Const.from('hello' + 'world'); * </pre> * - * For more information, see: - * http://dev.w3.org/html5/markup/syntax.html#syntax-elements + * In contrast, the following are illegal: + * <pre> + * var s = goog.string.Const.from(getHello()); + * var t = goog.string.Const.from('hello' + world); + * </pre> * - * TODO(user): Rename shouldAllowChildren() ? + * TODO(user): Compile-time checks that this function is only called + * with compile-time constant expressions. * - * @param {Node} node The node to check. - * @return {boolean} Whether the node can contain children. + * @param {string} s A constant string from which to create a Const. + * @return {!goog.string.Const} A Const object initialized to stringConst. */ -goog.dom.canHaveChildren = function(node) { - if (node.nodeType != goog.dom.NodeType.ELEMENT) { - return false; - } - switch (node.tagName) { - case goog.dom.TagName.APPLET: - case goog.dom.TagName.AREA: - case goog.dom.TagName.BASE: - case goog.dom.TagName.BR: - case goog.dom.TagName.COL: - case goog.dom.TagName.COMMAND: - case goog.dom.TagName.EMBED: - case goog.dom.TagName.FRAME: - case goog.dom.TagName.HR: - case goog.dom.TagName.IMG: - case goog.dom.TagName.INPUT: - case goog.dom.TagName.IFRAME: - case goog.dom.TagName.ISINDEX: - case goog.dom.TagName.KEYGEN: - case goog.dom.TagName.LINK: - case goog.dom.TagName.NOFRAMES: - case goog.dom.TagName.NOSCRIPT: - case goog.dom.TagName.META: - case goog.dom.TagName.OBJECT: - case goog.dom.TagName.PARAM: - case goog.dom.TagName.SCRIPT: - case goog.dom.TagName.SOURCE: - case goog.dom.TagName.STYLE: - case goog.dom.TagName.TRACK: - case goog.dom.TagName.WBR: - return false; - } - return true; +goog.string.Const.from = function(s) { + return goog.string.Const.create__googStringSecurityPrivate_(s); }; /** - * Appends a child to a node. - * @param {Node} parent Parent. - * @param {Node} child Child. + * Type marker for the Const type, used to implement additional run-time + * type checking. + * @const + * @private */ -goog.dom.appendChild = function(parent, child) { - parent.appendChild(child); -}; +goog.string.Const.TYPE_MARKER_ = {}; /** - * Appends a node with text or other nodes. - * @param {!Node} parent The node to append nodes to. - * @param {...goog.dom.Appendable} var_args The things to append to the node. - * If this is a Node it is appended as is. - * If this is a string then a text node is appended. - * If this is an array like object then fields 0 to length - 1 are appended. + * Utility method to create Const instances. + * @param {string} s The string to initialize the Const object with. + * @return {!goog.string.Const} The initialized Const object. + * @private */ -goog.dom.append = function(parent, var_args) { - goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1); +goog.string.Const.create__googStringSecurityPrivate_ = function(s) { + var stringConst = new goog.string.Const(); + stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = + s; + return stringConst; }; +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Removes all the child nodes on a DOM node. - * @param {Node} node Node to remove children from. + * @fileoverview The SafeStyle type and its builders. + * + * TODO(user): Link to document stating type contract. */ -goog.dom.removeChildren = function(node) { - // Note: Iterations over live collections can be slow, this is the fastest - // we could find. The double parenthesis are used to prevent JsCompiler and - // strict warnings. - var child; - while ((child = node.firstChild)) { - node.removeChild(child); - } -}; + +goog.provide('goog.html.SafeStyle'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + /** - * Inserts a new node before an existing reference node (i.e. as the previous - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert before. + * A string-like object which represents a sequence of CSS declarations + * ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...}) + * and that carries the security type contract that its value, as a string, + * will not cause untrusted script execution (XSS) when evaluated as CSS in a + * browser. + * + * Instances of this type must be created via the factory methods + * ({@code goog.html.SafeStyle.create} or + * {@code goog.html.SafeStyle.fromConstant}) and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty string + * can be obtained via constructor invocation. + * + * A SafeStyle's string representation ({@link #getSafeStyleString()}) can + * safely: + * <ul> + * <li>Be interpolated as the entire content of a *quoted* HTML style + * attribute, or before already existing properties. The SafeStyle string + * *must be HTML-attribute-escaped* (where " and ' are escaped) before + * interpolation. + * <li>Be interpolated as the entire content of a {}-wrapped block within a + * stylesheet, or before already existing properties. The SafeStyle string + * should not be escaped before interpolation. SafeStyle's contract also + * guarantees that the string will not be able to introduce new properties + * or elide existing ones. + * <li>Be assigned to the style property of a DOM node. The SafeStyle string + * should not be escaped before being assigned to the property. + * </ul> + * + * A SafeStyle may never contain literal angle brackets. Otherwise, it could + * be unsafe to place a SafeStyle into a <style> tag (where it can't + * be HTML escaped). For example, if the SafeStyle containing + * "{@code font: 'foo <style/><script>evil</script>'}" were + * interpolated within a <style> tag, this would then break out of the + * style context into HTML. + * + * A SafeStyle may contain literal single or double quotes, and as such the + * entire style string must be escaped when used in a style attribute (if + * this were not the case, the string could contain a matching quote that + * would escape from the style attribute). + * + * Values of this type must be composable, i.e. for any two values + * {@code style1} and {@code style2} of this type, + * {@code goog.html.SafeStyle.unwrap(style1) + + * goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies + * the SafeStyle type constraint. This requirement implies that for any value + * {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must + * not end in a "property value" or "property name" context. For example, + * a value of {@code background:url("} or {@code font-} would not satisfy the + * SafeStyle contract. This is because concatenating such strings with a + * second value that itself does not contain unsafe CSS can result in an + * overall string that does. For example, if {@code javascript:evil())"} is + * appended to {@code background:url("}, the resulting string may result in + * the execution of a malicious script. + * + * TODO(user): Consider whether we should implement UTF-8 interchange + * validity checks and blacklisting of newlines (including Unicode ones) and + * other whitespace characters (\t, \f). Document here if so and also update + * SafeStyle.fromConstant(). + * + * The following example values comply with this type's contract: + * <ul> + * <li><pre>width: 1em;</pre> + * <li><pre>height:1em;</pre> + * <li><pre>width: 1em;height: 1em;</pre> + * <li><pre>background:url('http://url');</pre> + * </ul> + * In addition, the empty string is safe for use in a CSS attribute. + * + * The following example values do NOT comply with this type's contract: + * <ul> + * <li><pre>background: red</pre> (missing a trailing semi-colon) + * <li><pre>background:</pre> (missing a value and a trailing semi-colon) + * <li><pre>1em</pre> (missing an attribute name, which provides context for + * the value) + * </ul> + * + * @see goog.html.SafeStyle#create + * @see goog.html.SafeStyle#fromConstant + * @see http://www.w3.org/TR/css3-syntax/ + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} */ -goog.dom.insertSiblingBefore = function(newNode, refNode) { - if (refNode.parentNode) { - refNode.parentNode.insertBefore(newNode, refNode); - } +goog.html.SafeStyle = function() { + /** + * The contained value of this SafeStyle. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeStyle#unwrap + * @const + * @private + */ + this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; }; /** - * Inserts a new node after an existing reference node (i.e. as the next - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert after. + * @override + * @const */ -goog.dom.insertSiblingAfter = function(newNode, refNode) { - if (refNode.parentNode) { - refNode.parentNode.insertBefore(newNode, refNode.nextSibling); - } -}; +goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true; /** - * Insert a child at a given index. If index is larger than the number of child - * nodes that the parent currently has, the node is inserted as the last child - * node. - * @param {Element} parent The element into which to insert the child. - * @param {Node} child The element to insert. - * @param {number} index The index at which to insert the new child node. Must - * not be negative. + * Type marker for the SafeStyle type, used to implement additional + * run-time type checking. + * @const + * @private */ -goog.dom.insertChildAt = function(parent, child, index) { - // Note that if the second argument is null, insertBefore - // will append the child at the end of the list of children. - parent.insertBefore(child, parent.childNodes[index] || null); -}; +goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; /** - * Removes a node from its parent. - * @param {Node} node The node to remove. - * @return {Node} The node removed if removed; else, null. + * Creates a SafeStyle object from a compile-time constant string. + * + * {@code style} should be in the format + * {@code name: value; [name: value; ...]} and must not have any < or > + * characters in it. This is so that SafeStyle's contract is preserved, + * allowing the SafeStyle to correctly be interpreted as a sequence of CSS + * declarations and without affecting the syntactic structure of any + * surrounding CSS and HTML. + * + * This method performs basic sanity checks on the format of {@code style} + * but does not constrain the format of {@code name} and {@code value}, except + * for disallowing tag characters. + * + * @param {!goog.string.Const} style A compile-time-constant string from which + * to create a SafeStyle. + * @return {!goog.html.SafeStyle} A SafeStyle object initialized to + * {@code style}. */ -goog.dom.removeNode = function(node) { - return node && node.parentNode ? node.parentNode.removeChild(node) : null; +goog.html.SafeStyle.fromConstant = function(style) { + var styleString = goog.string.Const.unwrap(style); + if (styleString.length === 0) { + return goog.html.SafeStyle.EMPTY; + } + goog.html.SafeStyle.checkStyle_(styleString); + goog.asserts.assert(goog.string.endsWith(styleString, ';'), + 'Last character of style string is not \';\': ' + styleString); + goog.asserts.assert(goog.string.contains(styleString, ':'), + 'Style string must contain at least one \':\', to ' + + 'specify a "name: value" pair: ' + styleString); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + styleString); }; /** - * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no - * parent. - * @param {Node} newNode Node to insert. - * @param {Node} oldNode Node to replace. + * Checks if the style definition is valid. + * @param {string} style + * @private */ -goog.dom.replaceNode = function(newNode, oldNode) { - var parent = oldNode.parentNode; - if (parent) { - parent.replaceChild(newNode, oldNode); - } +goog.html.SafeStyle.checkStyle_ = function(style) { + goog.asserts.assert(!/[<>]/.test(style), + 'Forbidden characters in style string: ' + style); }; /** - * Flattens an element. That is, removes it and replace it with its children. - * Does nothing if the element is not in the document. - * @param {Element} element The element to flatten. - * @return {Element|undefined} The original element, detached from the document - * tree, sans children; or undefined, if the element was not in the document - * to begin with. + * Returns this SafeStyle's value as a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of + * this method. If in doubt, assume that it's security relevant. In particular, + * note that goog.html functions which return a goog.html type do not guarantee + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeStyle#unwrap + * @override */ -goog.dom.flattenElement = function(element) { - var child, parent = element.parentNode; - if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) { - // Use IE DOM method (supported by Opera too) if available - if (element.removeNode) { - return /** @type {Element} */ (element.removeNode(false)); - } else { - // Move all children of the original node up one level. - while ((child = element.firstChild)) { - parent.insertBefore(child, element); - } - - // Detach the original element. - return /** @type {Element} */ (goog.dom.removeNode(element)); - } - } +goog.html.SafeStyle.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeStyleWrappedValue_; }; +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeStyle, use + * {@code goog.html.SafeStyle.unwrap}. + * + * @see goog.html.SafeStyle#unwrap + * @override + */ + goog.html.SafeStyle.prototype.toString = function() { + return 'SafeStyle{' + + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + '}'; + }; +} + + /** - * Returns an array containing just the element children of the given element. - * @param {Element} element The element whose element children we want. - * @return {!(Array|NodeList)} An array or array-like list of just the element - * children of the given element. + * Performs a runtime check that the provided object is indeed a + * SafeStyle object, and returns its value. + * + * @param {!goog.html.SafeStyle} safeStyle The object to extract from. + * @return {string} The safeStyle object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. */ -goog.dom.getChildren = function(element) { - // We check if the children attribute is supported for child elements - // since IE8 misuses the attribute by also including comments. - if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE && - element.children != undefined) { - return element.children; +goog.html.SafeStyle.unwrap = function(safeStyle) { + // Perform additional Run-time type-checking to ensure that + // safeStyle is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeStyle instanceof goog.html.SafeStyle && + safeStyle.constructor === goog.html.SafeStyle && + safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_; + } else { + goog.asserts.fail( + 'expected object of type SafeStyle, got \'' + safeStyle + '\''); + return 'type_error:SafeStyle'; } - // Fall back to manually filtering the element's child nodes. - return goog.array.filter(element.childNodes, function(node) { - return node.nodeType == goog.dom.NodeType.ELEMENT; - }); }; /** - * Returns the first child node that is an element. - * @param {Node} node The node to get the first child element of. - * @return {Element} The first child node of {@code node} that is an element. + * Package-internal utility method to create SafeStyle instances. + * + * @param {string} style The string to initialize the SafeStyle object with. + * @return {!goog.html.SafeStyle} The initialized SafeStyle object. + * @package */ -goog.dom.getFirstElementChild = function(node) { - if (node.firstElementChild != undefined) { - return /** @type {!Element} */(node).firstElementChild; - } - return goog.dom.getNextElementNode_(node.firstChild, true); +goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse = + function(style) { + var safeStyle = new goog.html.SafeStyle(); + safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style; + return safeStyle; }; /** - * Returns the last child node that is an element. - * @param {Node} node The node to get the last child element of. - * @return {Element} The last child node of {@code node} that is an element. + * A SafeStyle instance corresponding to the empty string. + * @const {!goog.html.SafeStyle} */ -goog.dom.getLastElementChild = function(node) { - if (node.lastElementChild != undefined) { - return /** @type {!Element} */(node).lastElementChild; - } - return goog.dom.getNextElementNode_(node.lastChild, false); -}; +goog.html.SafeStyle.EMPTY = + goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(''); /** - * Returns the first next sibling that is an element. - * @param {Node} node The node to get the next sibling element of. - * @return {Element} The next sibling of {@code node} that is an element. + * The innocuous string generated by goog.html.SafeUrl.create when passed + * an unsafe value. + * @const {string} */ -goog.dom.getNextElementSibling = function(node) { - if (node.nextElementSibling != undefined) { - return /** @type {!Element} */(node).nextElementSibling; - } - return goog.dom.getNextElementNode_(node.nextSibling, true); -}; +goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez'; /** - * Returns the first previous sibling that is an element. - * @param {Node} node The node to get the previous sibling element of. - * @return {Element} The first previous sibling of {@code node} that is - * an element. + * Mapping of property names to their values. + * @typedef {!Object<string, goog.string.Const|string>} */ -goog.dom.getPreviousElementSibling = function(node) { - if (node.previousElementSibling != undefined) { - return /** @type {!Element} */(node).previousElementSibling; +goog.html.SafeStyle.PropertyMap; + + +/** + * Creates a new SafeStyle object from the properties specified in the map. + * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to + * their values, for example {'margin': '1px'}. Names must consist of + * [-_a-zA-Z0-9]. Values might be strings consisting of [-.%_!# a-zA-Z0-9]. + * Other values must be wrapped in goog.string.Const. Null value causes + * skipping the property. + * @return {!goog.html.SafeStyle} + * @throws {Error} If invalid name is provided. + * @throws {goog.asserts.AssertionError} If invalid value is provided. With + * disabled assertions, invalid value is replaced by + * goog.html.SafeStyle.INNOCUOUS_STRING. + */ +goog.html.SafeStyle.create = function(map) { + var style = ''; + for (var name in map) { + if (!/^[-_a-zA-Z0-9]+$/.test(name)) { + throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name); + } + var value = map[name]; + if (value == null) { + continue; + } + if (value instanceof goog.string.Const) { + value = goog.string.Const.unwrap(value); + // These characters can be used to change context and we don't want that + // even with const values. + goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].'); + } else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) { + goog.asserts.fail( + 'String value allows only [-.%_!# a-zA-Z0-9], got: ' + value); + value = goog.html.SafeStyle.INNOCUOUS_STRING; + } + style += name + ':' + value + ';'; } - return goog.dom.getNextElementNode_(node.previousSibling, false); + if (!style) { + return goog.html.SafeStyle.EMPTY; + } + goog.html.SafeStyle.checkStyle_(style); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); }; +// Keep in sync with the error string in create(). /** - * Returns the first node that is an element in the specified direction, - * starting with {@code node}. - * @param {Node} node The node to get the next element from. - * @param {boolean} forward Whether to look forwards or backwards. - * @return {Element} The first element. + * Regular expression for safe values. + * @const {!RegExp} * @private */ -goog.dom.getNextElementNode_ = function(node, forward) { - while (node && node.nodeType != goog.dom.NodeType.ELEMENT) { - node = forward ? node.nextSibling : node.previousSibling; - } - - return /** @type {Element} */ (node); -}; +goog.html.SafeStyle.VALUE_RE_ = /^[-.%_!# a-zA-Z0-9]+$/; /** - * Returns the next node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The next node in the DOM tree, or null if this was the last - * node. + * Creates a new SafeStyle object by concatenating the values. + * @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args + * SafeStyles to concatenate. + * @return {!goog.html.SafeStyle} */ -goog.dom.getNextNode = function(node) { - if (!node) { - return null; - } +goog.html.SafeStyle.concat = function(var_args) { + var style = ''; - if (node.firstChild) { - return node.firstChild; - } + /** + * @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument + */ + var addArgument = function(argument) { + if (goog.isArray(argument)) { + goog.array.forEach(argument, addArgument); + } else { + style += goog.html.SafeStyle.unwrap(argument); + } + }; - while (node && !node.nextSibling) { - node = node.parentNode; + goog.array.forEach(arguments, addArgument); + if (!style) { + return goog.html.SafeStyle.EMPTY; } - - return node ? node.nextSibling : null; + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); }; +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Returns the previous node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The previous node in the DOM tree, or null if this was the - * first node. + * @fileoverview The SafeStyleSheet type and its builders. + * + * TODO(user): Link to document stating type contract. */ -goog.dom.getPreviousNode = function(node) { - if (!node) { - return null; - } - if (!node.previousSibling) { - return node.parentNode; - } +goog.provide('goog.html.SafeStyleSheet'); - node = node.previousSibling; - while (node && node.lastChild) { - node = node.lastChild; - } +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); - return node; -}; /** - * Whether the object looks like a DOM node. - * @param {?} obj The object being tested for node likeness. - * @return {boolean} Whether the object looks like a DOM node. + * A string-like object which represents a CSS style sheet and that carries the + * security type contract that its value, as a string, will not cause untrusted + * script execution (XSS) when evaluated as CSS in a browser. + * + * Instances of this type must be created via the factory method + * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty string + * can be obtained via constructor invocation. + * + * A SafeStyleSheet's string representation can safely be interpolated as the + * content of a style element within HTML. The SafeStyleSheet string should + * not be escaped before interpolation. + * + * Values of this type must be composable, i.e. for any two values + * {@code styleSheet1} and {@code styleSheet2} of this type, + * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) + + * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that + * satisfies the SafeStyleSheet type constraint. This requirement implies that + * for any value {@code styleSheet} of this type, + * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in + * "beginning of rule" context. + + * A SafeStyleSheet can be constructed via security-reviewed unchecked + * conversions. In this case producers of SafeStyleSheet must ensure themselves + * that the SafeStyleSheet does not contain unsafe script. Note in particular + * that {@code <} is dangerous, even when inside CSS strings, and so should + * always be forbidden or CSS-escaped in user controlled input. For example, if + * {@code </style><script>evil</script>"} were interpolated + * inside a CSS string, it would break out of the context of the original + * style element and {@code evil} would execute. Also note that within an HTML + * style (raw text) element, HTML character references, such as + * {@code &lt;}, are not allowed. See + * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements + * (similar considerations apply to the style element). + * + * @see goog.html.SafeStyleSheet#fromConstant + * @constructor + * @final + * @struct + * @implements {goog.string.TypedString} */ -goog.dom.isNodeLike = function(obj) { - return goog.isObject(obj) && obj.nodeType > 0; +goog.html.SafeStyleSheet = function() { + /** + * The contained value of this SafeStyleSheet. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeStyleSheet#unwrap + * @const + * @private + */ + this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; }; /** - * Whether the object looks like an Element. - * @param {?} obj The object being tested for Element likeness. - * @return {boolean} Whether the object looks like an Element. + * @override + * @const */ -goog.dom.isElement = function(obj) { - return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT; -}; +goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true; /** - * Returns true if the specified value is a Window object. This includes the - * global window for HTML pages, and iframe windows. - * @param {?} obj Variable to test. - * @return {boolean} Whether the variable is a window. + * Type marker for the SafeStyleSheet type, used to implement additional + * run-time type checking. + * @const + * @private */ -goog.dom.isWindow = function(obj) { - return goog.isObject(obj) && obj['window'] == obj; -}; +goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; /** - * Returns an element's parent, if it's an Element. - * @param {Element} element The DOM element. - * @return {Element} The parent, or null if not an Element. + * Creates a new SafeStyleSheet object by concatenating values. + * @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)} + * var_args Values to concatenate. + * @return {!goog.html.SafeStyleSheet} */ -goog.dom.getParentElement = function(element) { - var parent; - if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) { - var isIe9 = goog.userAgent.IE && - goog.userAgent.isVersionOrHigher('9') && - !goog.userAgent.isVersionOrHigher('10'); - // SVG elements in IE9 can't use the parentElement property. - // goog.global['SVGElement'] is not defined in IE9 quirks mode. - if (!(isIe9 && goog.global['SVGElement'] && - element instanceof goog.global['SVGElement'])) { - parent = element.parentElement; - if (parent) { - return parent; - } +goog.html.SafeStyleSheet.concat = function(var_args) { + var result = ''; + + /** + * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>} + * argument + */ + var addArgument = function(argument) { + if (goog.isArray(argument)) { + goog.array.forEach(argument, addArgument); + } else { + result += goog.html.SafeStyleSheet.unwrap(argument); } - } - parent = element.parentNode; - return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null; + }; + + goog.array.forEach(arguments, addArgument); + return goog.html.SafeStyleSheet + .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result); }; /** - * Whether a node contains another node. - * @param {Node} parent The node that should contain the other node. - * @param {Node} descendant The node to test presence of. - * @return {boolean} Whether the parent node contains the descendent node. + * Creates a SafeStyleSheet object from a compile-time constant string. + * + * {@code styleSheet} must not have any < characters in it, so that + * the syntactic structure of the surrounding HTML is not affected. + * + * @param {!goog.string.Const} styleSheet A compile-time-constant string from + * which to create a SafeStyleSheet. + * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to + * {@code styleSheet}. */ -goog.dom.contains = function(parent, descendant) { - // We use browser specific methods for this if available since it is faster - // that way. - - // IE DOM - if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) { - return parent == descendant || parent.contains(descendant); - } - - // W3C DOM Level 3 - if (typeof parent.compareDocumentPosition != 'undefined') { - return parent == descendant || - Boolean(parent.compareDocumentPosition(descendant) & 16); - } - - // W3C DOM Level 1 - while (descendant && parent != descendant) { - descendant = descendant.parentNode; +goog.html.SafeStyleSheet.fromConstant = function(styleSheet) { + var styleSheetString = goog.string.Const.unwrap(styleSheet); + if (styleSheetString.length === 0) { + return goog.html.SafeStyleSheet.EMPTY; } - return descendant == parent; + // > is a valid character in CSS selectors and there's no strict need to + // block it if we already block <. + goog.asserts.assert(!goog.string.contains(styleSheetString, '<'), + "Forbidden '<' character in style sheet string: " + styleSheetString); + return goog.html.SafeStyleSheet. + createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString); }; /** - * Compares the document order of two nodes, returning 0 if they are the same - * node, a negative number if node1 is before node2, and a positive number if - * node2 is before node1. Note that we compare the order the tags appear in the - * document so in the tree <b><i>text</i></b> the B node is considered to be - * before the I node. + * Returns this SafeStyleSheet's value as a string. * - * @param {Node} node1 The first node to compare. - * @param {Node} node2 The second node to compare. - * @return {number} 0 if the nodes are the same node, a negative number if node1 - * is before node2, and a positive number if node2 is before node1. + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap} + * instead of this method. If in doubt, assume that it's security relevant. In + * particular, note that goog.html functions which return a goog.html type do + * not guarantee the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeStyleSheet#unwrap + * @override */ -goog.dom.compareNodeOrder = function(node1, node2) { - // Fall out quickly for equality. - if (node1 == node2) { - return 0; - } - - // Use compareDocumentPosition where available - if (node1.compareDocumentPosition) { - // 4 is the bitmask for FOLLOWS. - return node1.compareDocumentPosition(node2) & 2 ? 1 : -1; - } +goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; +}; - // Special case for document nodes on IE 7 and 8. - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { - if (node1.nodeType == goog.dom.NodeType.DOCUMENT) { - return -1; - } - if (node2.nodeType == goog.dom.NodeType.DOCUMENT) { - return 1; - } - } - // Process in IE using sourceIndex - we check to see if the first node has - // a source index or if its parent has one. - if ('sourceIndex' in node1 || - (node1.parentNode && 'sourceIndex' in node1.parentNode)) { - var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT; - var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT; +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeStyleSheet, use + * {@code goog.html.SafeStyleSheet.unwrap}. + * + * @see goog.html.SafeStyleSheet#unwrap + * @override + */ + goog.html.SafeStyleSheet.prototype.toString = function() { + return 'SafeStyleSheet{' + + this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}'; + }; +} - if (isElement1 && isElement2) { - return node1.sourceIndex - node2.sourceIndex; - } else { - var parent1 = node1.parentNode; - var parent2 = node2.parentNode; - if (parent1 == parent2) { - return goog.dom.compareSiblingOrder_(node1, node2); - } +/** + * Performs a runtime check that the provided object is indeed a + * SafeStyleSheet object, and returns its value. + * + * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from. + * @return {string} The safeStyleSheet object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. + */ +goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) { + // Perform additional Run-time type-checking to ensure that + // safeStyleSheet is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeStyleSheet instanceof goog.html.SafeStyleSheet && + safeStyleSheet.constructor === goog.html.SafeStyleSheet && + safeStyleSheet.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; + } else { + goog.asserts.fail( + "expected object of type SafeStyleSheet, got '" + safeStyleSheet + + "'"); + return 'type_error:SafeStyleSheet'; + } +}; - if (!isElement1 && goog.dom.contains(parent1, node2)) { - return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2); - } +/** + * Package-internal utility method to create SafeStyleSheet instances. + * + * @param {string} styleSheet The string to initialize the SafeStyleSheet + * object with. + * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object. + * @package + */ +goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse = + function(styleSheet) { + var safeStyleSheet = new goog.html.SafeStyleSheet(); + safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = + styleSheet; + return safeStyleSheet; +}; - if (!isElement2 && goog.dom.contains(parent2, node1)) { - return goog.dom.compareParentsDescendantNodeIe_(node2, node1); - } - return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) - - (isElement2 ? node2.sourceIndex : parent2.sourceIndex); - } - } +/** + * A SafeStyleSheet instance corresponding to the empty string. + * @const {!goog.html.SafeStyleSheet} + */ +goog.html.SafeStyleSheet.EMPTY = + goog.html.SafeStyleSheet. + createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(''); - // For Safari, we compare ranges. - var doc = goog.dom.getOwnerDocument(node1); +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. - var range1, range2; - range1 = doc.createRange(); - range1.selectNode(node1); - range1.collapse(true); +/** + * @fileoverview Utility functions for supporting Bidi issues. + */ - range2 = doc.createRange(); - range2.selectNode(node2); - range2.collapse(true); - return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END, - range2); -}; +/** + * Namespace for bidi supporting functions. + */ +goog.provide('goog.i18n.bidi'); +goog.provide('goog.i18n.bidi.Dir'); +goog.provide('goog.i18n.bidi.DirectionalString'); +goog.provide('goog.i18n.bidi.Format'); /** - * Utility function to compare the position of two nodes, when - * {@code textNode}'s parent is an ancestor of {@code node}. If this entry - * condition is not met, this function will attempt to reference a null object. - * @param {!Node} textNode The textNode to compare. - * @param {Node} node The node to compare. - * @return {number} -1 if node is before textNode, +1 otherwise. - * @private + * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant + * to say that the current locale is a RTL locale. This should only be used + * if you want to override the default behavior for deciding whether the + * current locale is RTL or not. + * + * {@see goog.i18n.bidi.IS_RTL} */ -goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) { - var parent = textNode.parentNode; - if (parent == node) { - // If textNode is a child of node, then node comes first. - return -1; - } - var sibling = node; - while (sibling.parentNode != parent) { - sibling = sibling.parentNode; - } - return goog.dom.compareSiblingOrder_(sibling, textNode); -}; +goog.define('goog.i18n.bidi.FORCE_RTL', false); /** - * Utility function to compare the position of two nodes known to be non-equal - * siblings. - * @param {Node} node1 The first node to compare. - * @param {!Node} node2 The second node to compare. - * @return {number} -1 if node1 is before node2, +1 otherwise. - * @private + * Constant that defines whether or not the current locale is a RTL locale. + * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default + * to check that {@link goog.LOCALE} is one of a few major RTL locales. + * + * <p>This is designed to be a maximally efficient compile-time constant. For + * example, for the default goog.LOCALE, compiling + * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It + * is this design consideration that limits the implementation to only + * supporting a few major RTL locales, as opposed to the broader repertoire of + * something like goog.i18n.bidi.isRtlLanguage. + * + * <p>Since this constant refers to the directionality of the locale, it is up + * to the caller to determine if this constant should also be used for the + * direction of the UI. + * + * {@see goog.LOCALE} + * + * @type {boolean} + * + * TODO(user): write a test that checks that this is a compile-time constant. */ -goog.dom.compareSiblingOrder_ = function(node1, node2) { - var s = node2; - while ((s = s.previousSibling)) { - if (s == node1) { - // We just found node1 before node2. - return -1; - } - } +goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL || + ( + (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'he' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' || + goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') && + (goog.LOCALE.length == 2 || + goog.LOCALE.substring(2, 3) == '-' || + goog.LOCALE.substring(2, 3) == '_') + ) || ( + goog.LOCALE.length >= 3 && + goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' && + (goog.LOCALE.length == 3 || + goog.LOCALE.substring(3, 4) == '-' || + goog.LOCALE.substring(3, 4) == '_') + ); - // Since we didn't find it, node1 must be after node2. - return 1; + +/** + * Unicode formatting characters and directionality string constants. + * @enum {string} + */ +goog.i18n.bidi.Format = { + /** Unicode "Left-To-Right Embedding" (LRE) character. */ + LRE: '\u202A', + /** Unicode "Right-To-Left Embedding" (RLE) character. */ + RLE: '\u202B', + /** Unicode "Pop Directional Formatting" (PDF) character. */ + PDF: '\u202C', + /** Unicode "Left-To-Right Mark" (LRM) character. */ + LRM: '\u200E', + /** Unicode "Right-To-Left Mark" (RLM) character. */ + RLM: '\u200F' }; /** - * Find the deepest common ancestor of the given nodes. - * @param {...Node} var_args The nodes to find a common ancestor of. - * @return {Node} The common ancestor of the nodes, or null if there is none. - * null will only be returned if two or more of the nodes are from different - * documents. + * Directionality enum. + * @enum {number} */ -goog.dom.findCommonAncestor = function(var_args) { - var i, count = arguments.length; - if (!count) { - return null; - } else if (count == 1) { - return arguments[0]; - } +goog.i18n.bidi.Dir = { + /** + * Left-to-right. + */ + LTR: 1, - var paths = []; - var minLength = Infinity; - for (i = 0; i < count; i++) { - // Compute the list of ancestors. - var ancestors = []; - var node = arguments[i]; - while (node) { - ancestors.unshift(node); - node = node.parentNode; - } + /** + * Right-to-left. + */ + RTL: -1, - // Save the list for comparison. - paths.push(ancestors); - minLength = Math.min(minLength, ancestors.length); - } - var output = null; - for (i = 0; i < minLength; i++) { - var first = paths[0][i]; - for (var j = 1; j < count; j++) { - if (first != paths[j][i]) { - return output; - } - } - output = first; - } - return output; + /** + * Neither left-to-right nor right-to-left. + */ + NEUTRAL: 0 }; /** - * Returns the owner document for a node. - * @param {Node|Window} node The node to get the document for. - * @return {!Document} The document owning the node. + * 'right' string constant. + * @type {string} */ -goog.dom.getOwnerDocument = function(node) { - // TODO(nnaze): Update param signature to be non-nullable. - goog.asserts.assert(node, 'Node cannot be null or undefined.'); - return /** @type {!Document} */ ( - node.nodeType == goog.dom.NodeType.DOCUMENT ? node : - node.ownerDocument || node.document); -}; +goog.i18n.bidi.RIGHT = 'right'; /** - * Cross-browser function for getting the document element of a frame or iframe. - * @param {Element} frame Frame element. - * @return {!Document} The frame content document. + * 'left' string constant. + * @type {string} */ -goog.dom.getFrameContentDocument = function(frame) { - var doc = frame.contentDocument || frame.contentWindow.document; - return doc; -}; +goog.i18n.bidi.LEFT = 'left'; /** - * Cross-browser function for getting the window of a frame or iframe. - * @param {Element} frame Frame element. - * @return {Window} The window associated with the given frame. + * 'left' if locale is RTL, 'right' if not. + * @type {string} */ -goog.dom.getFrameContentWindow = function(frame) { - return frame.contentWindow || - goog.dom.getWindow(goog.dom.getFrameContentDocument(frame)); -}; +goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : + goog.i18n.bidi.RIGHT; /** - * Sets the text content of a node, with cross-browser support. - * @param {Node} node The node to change the text content of. - * @param {string|number} text The value that should replace the node's content. + * 'right' if locale is RTL, 'left' if not. + * @type {string} */ -goog.dom.setTextContent = function(node, text) { - goog.asserts.assert(node != null, - 'goog.dom.setTextContent expects a non-null value for node'); - - if ('textContent' in node) { - node.textContent = text; - } else if (node.nodeType == goog.dom.NodeType.TEXT) { - node.data = text; - } else if (node.firstChild && - node.firstChild.nodeType == goog.dom.NodeType.TEXT) { - // If the first child is a text node we just change its data and remove the - // rest of the children. - while (node.lastChild != node.firstChild) { - node.removeChild(node.lastChild); - } - node.firstChild.data = text; - } else { - goog.dom.removeChildren(node); - var doc = goog.dom.getOwnerDocument(node); - node.appendChild(doc.createTextNode(String(text))); - } -}; +goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : + goog.i18n.bidi.LEFT; /** - * Gets the outerHTML of a node, which islike innerHTML, except that it - * actually contains the HTML of the node itself. - * @param {Element} element The element to get the HTML of. - * @return {string} The outerHTML of the given element. + * Convert a directionality given in various formats to a goog.i18n.bidi.Dir + * constant. Useful for interaction with different standards of directionality + * representation. + * + * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given + * in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LTR, negative = RTL, 0 = neutral). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. + * @param {boolean=} opt_noNeutral Whether a givenDir of zero or + * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in + * order to preserve legacy behavior. + * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the + * given directionality. If given null, returns null (i.e. unknown). */ -goog.dom.getOuterHtml = function(element) { - // IE, Opera and WebKit all have outerHTML. - if ('outerHTML' in element) { - return element.outerHTML; +goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) { + if (typeof givenDir == 'number') { + // This includes the non-null goog.i18n.bidi.Dir case. + return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : + givenDir < 0 ? goog.i18n.bidi.Dir.RTL : + opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL; + } else if (givenDir == null) { + return null; } else { - var doc = goog.dom.getOwnerDocument(element); - var div = doc.createElement('div'); - div.appendChild(element.cloneNode(true)); - return div.innerHTML; + // Must be typeof givenDir == 'boolean'. + return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; } }; /** - * Finds the first descendant node that matches the filter function, using - * a depth first search. This function offers the most general purpose way - * of finding a matching element. You may also wish to consider - * {@code goog.dom.query} which can express many matching criteria using - * CSS selector expressions. These expressions often result in a more - * compact representation of the desired result. - * @see goog.dom.query - * - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {Node|undefined} The found node or undefined if none is found. + * A practical pattern to identify strong LTR characters. This pattern is not + * theoretically correct according to the Unicode standard. It is simplified for + * performance and small code size. + * @type {string} + * @private */ -goog.dom.findNode = function(root, p) { - var rv = []; - var found = goog.dom.findNodes_(root, p, rv, true); - return found ? rv[0] : undefined; -}; +goog.i18n.bidi.ltrChars_ = + 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + + '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF'; /** - * Finds all the descendant nodes that match the filter function, using a - * a depth first search. This function offers the most general-purpose way - * of finding a set of matching elements. You may also wish to consider - * {@code goog.dom.query} which can express many matching criteria using - * CSS selector expressions. These expressions often result in a more - * compact representation of the desired result. + * A practical pattern to identify strong RTL character. This pattern is not + * theoretically correct according to the Unicode standard. It is simplified + * for performance and small code size. + * @type {string} + * @private + */ +goog.i18n.bidi.rtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC'; - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {!Array<!Node>} The found nodes or an empty array if none are found. + +/** + * Simplified regular expression for an HTML tag (opening or closing) or an HTML + * escape. We might want to skip over such expressions when estimating the text + * directionality. + * @type {RegExp} + * @private */ -goog.dom.findNodes = function(root, p) { - var rv = []; - goog.dom.findNodes_(root, p, rv, false); - return rv; -}; +goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g; /** - * Finds the first or all the descendant nodes that match the filter function, - * using a depth first search. - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @param {!Array<!Node>} rv The found nodes are added to this array. - * @param {boolean} findOne If true we exit after the first found node. - * @return {boolean} Whether the search is complete or not. True in case findOne - * is true and the node is found. False otherwise. + * Returns the input text with spaces instead of HTML tags or HTML escapes, if + * opt_isStripNeeded is true. Else returns the input as is. + * Useful for text directionality estimation. + * Note: the function should not be used in other contexts; it is not 100% + * correct, but rather a good-enough implementation for directionality + * estimation purposes. + * @param {string} str The given string. + * @param {boolean=} opt_isStripNeeded Whether to perform the stripping. + * Default: false (to retain consistency with calling functions). + * @return {string} The given string cleaned of HTML tags / escapes. * @private */ -goog.dom.findNodes_ = function(root, p, rv, findOne) { - if (root != null) { - var child = root.firstChild; - while (child) { - if (p(child)) { - rv.push(child); - if (findOne) { - return true; - } - } - if (goog.dom.findNodes_(child, p, rv, findOne)) { - return true; - } - child = child.nextSibling; - } - } - return false; +goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) { + return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : + str; }; /** - * Map of tags whose content to ignore when calculating text length. - * @private {!Object<string, number>} - * @const + * Regular expression to check for RTL characters. + * @type {RegExp} + * @private */ -goog.dom.TAGS_TO_IGNORE_ = { - 'SCRIPT': 1, - 'STYLE': 1, - 'HEAD': 1, - 'IFRAME': 1, - 'OBJECT': 1 -}; +goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']'); /** - * Map of tags which have predefined values with regard to whitespace. - * @private {!Object<string, string>} - * @const + * Regular expression to check for LTR characters. + * @type {RegExp} + * @private */ -goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'}; +goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']'); /** - * Returns true if the element has a tab index that allows it to receive - * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements - * natively support keyboard focus, even if they have no tab index. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a tab index that allows keyboard - * focus. - * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + * Test whether the given string has any RTL characters in it. + * @param {string} str The given string that need to be tested. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether the string contains RTL characters. */ -goog.dom.isFocusableTabIndex = function(element) { - return goog.dom.hasSpecifiedTabIndex_(element) && - goog.dom.isTabIndexFocusable_(element); +goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); }; /** - * Enables or disables keyboard focus support on the element via its tab index. - * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true - * (or elements that natively support keyboard focus, like form elements) can - * receive keyboard focus. See http://go/tabindex for more info. - * @param {Element} element Element whose tab index is to be changed. - * @param {boolean} enable Whether to set or remove a tab index on the element - * that supports keyboard focus. + * Test whether the given string has any RTL characters in it. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the string contains RTL characters. + * @deprecated Use hasAnyRtl. */ -goog.dom.setFocusableTabIndex = function(element, enable) { - if (enable) { - element.tabIndex = 0; - } else { - // Set tabIndex to -1 first, then remove it. This is a workaround for - // Safari (confirmed in version 4 on Windows). When removing the attribute - // without setting it to -1 first, the element remains keyboard focusable - // despite not having a tabIndex attribute anymore. - element.tabIndex = -1; - element.removeAttribute('tabIndex'); // Must be camelCase! - } -}; +goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl; /** - * Returns true if the element can be focused, i.e. it has a tab index that - * allows it to receive keyboard focus (tabIndex >= 0), or it is an element - * that natively supports keyboard focus. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element allows keyboard focus. + * Test whether the given string has any LTR characters in it. + * @param {string} str The given string that need to be tested. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether the string contains LTR characters. */ -goog.dom.isFocusable = function(element) { - var focusable; - // Some elements can have unspecified tab index and still receive focus. - if (goog.dom.nativelySupportsFocus_(element)) { - // Make sure the element is not disabled ... - focusable = !element.disabled && - // ... and if a tab index is specified, it allows focus. - (!goog.dom.hasSpecifiedTabIndex_(element) || - goog.dom.isTabIndexFocusable_(element)); - } else { - focusable = goog.dom.isFocusableTabIndex(element); - } - - // IE requires elements to be visible in order to focus them. - return focusable && goog.userAgent.IE ? - goog.dom.hasNonZeroBoundingRect_(element) : focusable; +goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); }; /** - * Returns true if the element has a specified tab index. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a specified tab index. + * Regular expression pattern to check if the first character in the string + * is LTR. + * @type {RegExp} * @private */ -goog.dom.hasSpecifiedTabIndex_ = function(element) { - // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(), - // which returns an object with a 'specified' property if tabIndex is - // specified. This works on other browsers, too. - var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase! - return goog.isDefAndNotNull(attrNode) && attrNode.specified; -}; +goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']'); /** - * Returns true if the element's tab index allows the element to be focused. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element's tab index allows focus. + * Regular expression pattern to check if the first character in the string + * is RTL. + * @type {RegExp} * @private */ -goog.dom.isTabIndexFocusable_ = function(element) { - var index = element.tabIndex; - // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534. - return goog.isNumber(index) && index >= 0 && index < 32768; -}; +goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']'); /** - * Returns true if the element is focusable even when tabIndex is not set. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element natively supports focus. - * @private + * Check if the first character in the string is RTL or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is an RTL char. */ -goog.dom.nativelySupportsFocus_ = function(element) { - return element.tagName == goog.dom.TagName.A || - element.tagName == goog.dom.TagName.INPUT || - element.tagName == goog.dom.TagName.TEXTAREA || - element.tagName == goog.dom.TagName.SELECT || - element.tagName == goog.dom.TagName.BUTTON; +goog.i18n.bidi.isRtlChar = function(str) { + return goog.i18n.bidi.rtlRe_.test(str); }; /** - * Returns true if the element has a bounding rectangle that would be visible - * (i.e. its width and height are greater than zero). - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a non-zero bounding rectangle. - * @private + * Check if the first character in the string is LTR or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is an LTR char. */ -goog.dom.hasNonZeroBoundingRect_ = function(element) { - var rect = goog.isFunction(element['getBoundingClientRect']) ? - element.getBoundingClientRect() : - {'height': element.offsetHeight, 'width': element.offsetWidth}; - return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0; +goog.i18n.bidi.isLtrChar = function(str) { + return goog.i18n.bidi.ltrRe_.test(str); }; /** - * Returns the text content of the current node, without markup and invisible - * symbols. New lines are stripped and whitespace is collapsed, - * such that each character would be visible. - * - * In browsers that support it, innerText is used. Other browsers attempt to - * simulate it via node traversal. Line breaks are canonicalized in IE. - * - * @param {Node} node The node from which we are getting content. - * @return {string} The text content. + * Check if the first character in the string is neutral or not. + * @param {string} str The given string that need to be tested. + * @return {boolean} Whether the first character in str is a neutral char. */ -goog.dom.getTextContent = function(node) { - var textContent; - // Note(arv): IE9, Opera, and Safari 3 support innerText but they include - // text nodes in script tags. So we revert to use a user agent test here. - if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) { - textContent = goog.string.canonicalizeNewlines(node.innerText); - // Unfortunately .innerText() returns text with ­ symbols - // We need to filter it out and then remove duplicate whitespaces - } else { - var buf = []; - goog.dom.getTextContent_(node, buf, true); - textContent = buf.join(''); - } - - // Strip ­ entities. goog.format.insertWordBreaks inserts them in Opera. - textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, ''); - // Strip ​ entities. goog.format.insertWordBreaks inserts them in IE8. - textContent = textContent.replace(/\u200B/g, ''); - - // Skip this replacement on old browsers with working innerText, which - // automatically turns into ' ' and / +/ into ' ' when reading - // innerText. - if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) { - textContent = textContent.replace(/ +/g, ' '); - } - if (textContent != ' ') { - textContent = textContent.replace(/^\s*/, ''); - } - - return textContent; +goog.i18n.bidi.isNeutralChar = function(str) { + return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str); }; /** - * Returns the text content of the current node, without markup. - * - * Unlike {@code getTextContent} this method does not collapse whitespaces - * or normalize lines breaks. - * - * @param {Node} node The node from which we are getting content. - * @return {string} The raw text content. + * Regular expressions to check if a piece of text is of LTR directionality + * on first character with strong directionality. + * @type {RegExp} + * @private */ -goog.dom.getRawTextContent = function(node) { - var buf = []; - goog.dom.getTextContent_(node, buf, false); - - return buf.join(''); -}; +goog.i18n.bidi.ltrDirCheckRe_ = new RegExp( + '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']'); /** - * Recursive support function for text content retrieval. - * - * @param {Node} node The node from which we are getting content. - * @param {Array<string>} buf string buffer. - * @param {boolean} normalizeWhitespace Whether to normalize whitespace. + * Regular expressions to check if a piece of text is of RTL directionality + * on first character with strong directionality. + * @type {RegExp} * @private */ -goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) { - if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) { - // ignore certain tags - } else if (node.nodeType == goog.dom.NodeType.TEXT) { - if (normalizeWhitespace) { - buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); - } else { - buf.push(node.nodeValue); - } - } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { - buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]); - } else { - var child = node.firstChild; - while (child) { - goog.dom.getTextContent_(child, buf, normalizeWhitespace); - child = child.nextSibling; - } - } -}; +goog.i18n.bidi.rtlDirCheckRe_ = new RegExp( + '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']'); /** - * Returns the text length of the text contained in a node, without markup. This - * is equivalent to the selection length if the node was selected, or the number - * of cursor movements to traverse the node. Images & BRs take one space. New - * lines are ignored. - * - * @param {Node} node The node whose text content length is being calculated. - * @return {number} The length of {@code node}'s text content. + * Check whether the first strongly directional character (if any) is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL directionality is detected using the first + * strongly-directional character method. */ -goog.dom.getNodeTextLength = function(node) { - return goog.dom.getTextContent(node).length; +goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); }; /** - * Returns the text offset of a node relative to one of its ancestors. The text - * length is the same as the length calculated by goog.dom.getNodeTextLength. - * - * @param {Node} node The node whose offset is being calculated. - * @param {Node=} opt_offsetParent The node relative to which the offset will - * be calculated. Defaults to the node's owner document's body. - * @return {number} The text offset. + * Check whether the first strongly directional character (if any) is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL directionality is detected using the first + * strongly-directional character method. + * @deprecated Use startsWithRtl. */ -goog.dom.getNodeTextOffset = function(node, opt_offsetParent) { - var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body; - var buf = []; - while (node && node != root) { - var cur = node; - while ((cur = cur.previousSibling)) { - buf.unshift(goog.dom.getTextContent(cur)); - } - node = node.parentNode; - } - // Trim left to deal with FF cases when there might be line breaks and empty - // nodes at the front of the text - return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length; -}; +goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl; /** - * Returns the node at a given offset in a parent node. If an object is - * provided for the optional third parameter, the node and the remainder of the - * offset will stored as properties of this object. - * @param {Node} parent The parent node. - * @param {number} offset The offset into the parent node. - * @param {Object=} opt_result Object to be used to store the return value. The - * return value will be stored in the form {node: Node, remainder: number} - * if this object is provided. - * @return {Node} The node at the given offset. + * Check whether the first strongly directional character (if any) is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR directionality is detected using the first + * strongly-directional character method. */ -goog.dom.getNodeAtOffset = function(parent, offset, opt_result) { - var stack = [parent], pos = 0, cur = null; - while (stack.length > 0 && pos < offset) { - cur = stack.pop(); - if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) { - // ignore certain tags - } else if (cur.nodeType == goog.dom.NodeType.TEXT) { - var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' '); - pos += text.length; - } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { - pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length; - } else { - for (var i = cur.childNodes.length - 1; i >= 0; i--) { - stack.push(cur.childNodes[i]); - } - } - } - if (goog.isObject(opt_result)) { - opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0; - opt_result.node = cur; - } - - return cur; +goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_( + str, opt_isHtml)); }; /** - * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, - * the object must have a numeric length property and an item function (which - * has type 'string' on IE for some reason). - * @param {Object} val Object to test. - * @return {boolean} Whether the object is a NodeList. + * Check whether the first strongly directional character (if any) is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR directionality is detected using the first + * strongly-directional character method. + * @deprecated Use startsWithLtr. */ -goog.dom.isNodeList = function(val) { - // TODO(attila): Now the isNodeList is part of goog.dom we can use - // goog.userAgent to make this simpler. - // A NodeList must have a length property of type 'number' on all platforms. - if (val && typeof val.length == 'number') { - // A NodeList is an object everywhere except Safari, where it's a function. - if (goog.isObject(val)) { - // A NodeList must have an item function (on non-IE platforms) or an item - // property of type 'string' (on IE). - return typeof val.item == 'function' || typeof val.item == 'string'; - } else if (goog.isFunction(val)) { - // On Safari, a NodeList is a function with an item property that is also - // a function. - return typeof val.item == 'function'; - } - } - - // Not a NodeList. - return false; -}; +goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr; /** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * tag name and/or class name. If the passed element matches the specified - * criteria, the element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or - * null/undefined to match only based on class name). - * @param {?string=} opt_class The class name to match (or null/undefined to - * match only based on tag name). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if no match is found. + * Regular expression to check if a string looks like something that must + * always be LTR even in RTL text, e.g. a URL. When estimating the + * directionality of text containing these, we treat these as weakly LTR, + * like numbers. + * @type {RegExp} + * @private */ -goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class, - opt_maxSearchSteps) { - if (!opt_tag && !opt_class) { - return null; - } - var tagName = opt_tag ? opt_tag.toUpperCase() : null; - return /** @type {Element} */ (goog.dom.getAncestor(element, - function(node) { - return (!tagName || node.nodeName == tagName) && - (!opt_class || goog.isString(node.className) && - goog.array.contains(node.className.split(/\s+/), opt_class)); - }, true, opt_maxSearchSteps)); -}; +goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/; /** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * class name. If the passed element matches the specified criteria, the - * element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {string} className The class name to match. - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if none match. + * Check whether the input string either contains no strongly directional + * characters or looks like a url. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether neutral directionality is detected. */ -goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) { - return goog.dom.getAncestorByTagNameAndClass(element, null, className, - opt_maxSearchSteps); +goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) { + str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml); + return goog.i18n.bidi.isRequiredLtrRe_.test(str) || + !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str); }; /** - * Walks up the DOM hierarchy returning the first ancestor that passes the - * matcher function. - * @param {Node} element The DOM node to start with. - * @param {function(Node) : boolean} matcher A function that returns true if the - * passed node matches the desired criteria. - * @param {boolean=} opt_includeNode If true, the node itself is included in - * the search (the first call to the matcher will pass startElement as - * the node to test). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Node} DOM node that matched the matcher, or null if there was - * no match. + * Regular expressions to check if the last strongly-directional character in a + * piece of text is LTR. + * @type {RegExp} + * @private */ -goog.dom.getAncestor = function( - element, matcher, opt_includeNode, opt_maxSearchSteps) { - if (!opt_includeNode) { - element = element.parentNode; - } - var ignoreSearchSteps = opt_maxSearchSteps == null; - var steps = 0; - while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) { - if (matcher(element)) { - return element; - } - element = element.parentNode; - steps++; - } - // Reached the root of the DOM without a match - return null; -}; +goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp( + '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$'); /** - * Determines the active element in the given document. - * @param {Document} doc The document to look in. - * @return {Element} The active element. + * Regular expressions to check if the last strongly-directional character in a + * piece of text is RTL. + * @type {RegExp} + * @private */ -goog.dom.getActiveElement = function(doc) { - try { - return doc && doc.activeElement; - } catch (e) { - // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE - // throws an exception. I'm not 100% sure why, but I suspect it chokes - // on document.activeElement if the activeElement has been recently - // removed from the DOM by a JS operation. - // - // We assume that an exception here simply means - // "there is no active element." - } - - return null; -}; +goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp( + '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$'); /** - * Gives the current devicePixelRatio. - * - * By default, this is the value of window.devicePixelRatio (which should be - * preferred if present). - * - * If window.devicePixelRatio is not present, the ratio is calculated with - * window.matchMedia, if present. Otherwise, gives 1.0. - * - * Some browsers (including Chrome) consider the browser zoom level in the pixel - * ratio, so the value may change across multiple calls. - * - * @return {number} The number of actual pixels per virtual pixel. + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. */ -goog.dom.getPixelRatio = function() { - var win = goog.dom.getWindow(); - - // devicePixelRatio does not work on Mobile firefox. - // TODO(user): Enable this check on a known working mobile Gecko version. - // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804 - var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE; - - if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) { - return win.devicePixelRatio; - } else if (win.matchMedia) { - return goog.dom.matchesPixelRatio_(.75) || - goog.dom.matchesPixelRatio_(1.5) || - goog.dom.matchesPixelRatio_(2) || - goog.dom.matchesPixelRatio_(3) || 1; - } - return 1; +goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) { + return goog.i18n.bidi.ltrExitDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); }; /** - * Calculates a mediaQuery to check if the current device supports the - * given actual to virtual pixel ratio. - * @param {number} pixelRatio The ratio of actual pixels to virtual pixels. - * @return {number} pixelRatio if applicable, otherwise 0. - * @private + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. + * @deprecated Use endsWithLtr. */ -goog.dom.matchesPixelRatio_ = function(pixelRatio) { - var win = goog.dom.getWindow(); - var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' + - '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' + - '(min-resolution: ' + pixelRatio + 'dppx)'); - return win.matchMedia(query).matches ? pixelRatio : 0; -}; - +goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr; /** - * Create an instance of a DOM helper with a new document object. - * @param {Document=} opt_document Document object to associate with this - * DOM helper. - * @constructor + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. */ -goog.dom.DomHelper = function(opt_document) { - /** - * Reference to the document object to use - * @type {!Document} - * @private - */ - this.document_ = opt_document || goog.global.document || document; +goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) { + return goog.i18n.bidi.rtlExitDirCheckRe_.test( + goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)); }; /** - * Gets the dom helper object for the document where the element resides. - * @param {Node=} opt_node If present, gets the DomHelper for this node. - * @return {!goog.dom.DomHelper} The DomHelper. - */ -goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper; + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str String being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. + * @deprecated Use endsWithRtl. + */ +goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl; /** - * Sets the document object. - * @param {!Document} document Document object. + * A regular expression for matching right-to-left language codes. + * See {@link #isRtlLanguage} for the design. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.setDocument = function(document) { - this.document_ = document; -}; +goog.i18n.bidi.rtlLocalesRe_ = new RegExp( + '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' + + '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' + + '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)', + 'i'); /** - * Gets the document object being used by the dom library. - * @return {!Document} Document object. + * Check if a BCP 47 / III language code indicates an RTL language, i.e. either: + * - a language code explicitly specifying one of the right-to-left scripts, + * e.g. "az-Arab", or<p> + * - a language code specifying one of the languages normally written in a + * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying + * Latin or Cyrillic script (which are the usual LTR alternatives).<p> + * The list of right-to-left scripts appears in the 100-199 range in + * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and + * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and + * Tifinagh, which also have significant modern usage. The rest (Syriac, + * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage + * and are not recognized to save on code size. + * The languages usually written in a right-to-left script are taken as those + * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in + * http://www.iana.org/assignments/language-subtag-registry, + * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug). + * Other subtags of the language code, e.g. regions like EG (Egypt), are + * ignored. + * @param {string} lang BCP 47 (a.k.a III) language code. + * @return {boolean} Whether the language code is an RTL language. */ -goog.dom.DomHelper.prototype.getDocument = function() { - return this.document_; +goog.i18n.bidi.isRtlLanguage = function(lang) { + return goog.i18n.bidi.rtlLocalesRe_.test(lang); }; /** - * Alias for {@code getElementById}. If a DOM node is passed in then we just - * return that. - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. + * Regular expression for bracket guard replacement in html. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.getElement = function(element) { - return goog.dom.getElementHelper_(this.document_, element); -}; +goog.i18n.bidi.bracketGuardHtmlRe_ = + /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)/g; /** - * Gets an element by id, asserting that the element is found. - * - * This is used when an element is expected to exist, and should fail with - * an assertion error if it does not (if assertions are enabled). - * - * @param {string} id Element ID. - * @return {!Element} The element with the given ID, if it exists. + * Regular expression for bracket guard replacement in text. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.getRequiredElement = function(id) { - return goog.dom.getRequiredElementHelper_(this.document_, id); -}; +goog.i18n.bidi.bracketGuardTextRe_ = + /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g; /** - * Alias for {@code getElement}. - * @param {string|Element} element Element ID or a DOM node. - * @return {Element} The element with the given ID, or the node passed in. - * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead. + * Apply bracket guard using html span tag. This is to address the problem of + * messy bracket display frequently happens in RTL layout. + * @param {string} s The string that need to be processed. + * @param {boolean=} opt_isRtlContext specifies default direction (usually + * direction of the UI). + * @return {string} The processed string, with all bracket guarded. */ -goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement; +goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) { + var useRtl = opt_isRtlContext === undefined ? + goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext; + if (useRtl) { + return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_, + '<span dir=rtl>$&</span>'); + } + return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_, + '<span dir=ltr>$&</span>'); +}; /** - * Looks up elements by both tag and class name, using browser native functions - * ({@code querySelectorAll}, {@code getElementsByTagName} or - * {@code getElementsByClassName}) where possible. The returned array is a live - * NodeList or a static list depending on the code path taken. - * - * @see goog.dom.query - * - * @param {?string=} opt_tag Element tag name or * for all tags. - * @param {?string=} opt_class Optional class name. - * @param {(Document|Element)=} opt_el Optional element to look in. - * @return { {length: number} } Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * Apply bracket guard using LRM and RLM. This is to address the problem of + * messy bracket display frequently happens in RTL layout. + * This version works for both plain text and html. But it does not work as + * good as guardBracketInHtml in some cases. + * @param {string} s The string that need to be processed. + * @param {boolean=} opt_isRtlContext specifies default direction (usually + * direction of the UI). + * @return {string} The processed string, with all bracket guarded. */ -goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag, - opt_class, - opt_el) { - return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag, - opt_class, opt_el); +goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) { + var useRtl = opt_isRtlContext === undefined ? + goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext; + var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM; + return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark); }; /** - * Returns an array of all the elements with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {Element|Document=} opt_el Optional element to look in. - * @return { {length: number} } The items found with the class name provided. + * Enforce the html snippet in RTL directionality regardless overall context. + * If the html piece was enclosed by tag, dir will be applied to existing + * tag, otherwise a span tag will be added as wrapper. For this reason, if + * html snippet start with with tag, this tag must enclose the whole piece. If + * the tag already has a dir specified, this new one will override existing + * one in behavior (tested on FF and IE). + * @param {string} html The string that need to be processed. + * @return {string} The processed string, with directionality enforced to RTL. */ -goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) { - var doc = opt_el || this.document_; - return goog.dom.getElementsByClass(className, doc); +goog.i18n.bidi.enforceRtlInHtml = function(html) { + if (html.charAt(0) == '<') { + return html.replace(/<\w+/, '$& dir=rtl'); + } + // '\n' is important for FF so that it won't incorrectly merge span groups + return '\n<span dir=rtl>' + html + '</span>'; }; /** - * Returns the first element we find matching the provided class name. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {(Element|Document)=} opt_el Optional element to look in. - * @return {Element} The first item found with the class name provided. + * Enforce RTL on both end of the given text piece using unicode BiDi formatting + * characters RLE and PDF. + * @param {string} text The piece of text that need to be wrapped. + * @return {string} The wrapped string after process. */ -goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) { - var doc = opt_el || this.document_; - return goog.dom.getElementByClass(className, doc); +goog.i18n.bidi.enforceRtlInText = function(text) { + return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF; }; /** - * Ensures an element with the given className exists, and then returns the - * first element with the provided className. - * @see {goog.dom.query} - * @param {string} className the name of the class to look for. - * @param {(!Element|!Document)=} opt_root Optional element or document to look - * in. - * @return {!Element} The first item found with the class name provided. - * @throws {goog.asserts.AssertionError} Thrown if no element is found. + * Enforce the html snippet in RTL directionality regardless overall context. + * If the html piece was enclosed by tag, dir will be applied to existing + * tag, otherwise a span tag will be added as wrapper. For this reason, if + * html snippet start with with tag, this tag must enclose the whole piece. If + * the tag already has a dir specified, this new one will override existing + * one in behavior (tested on FF and IE). + * @param {string} html The string that need to be processed. + * @return {string} The processed string, with directionality enforced to RTL. */ -goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className, - opt_root) { - var root = opt_root || this.document_; - return goog.dom.getRequiredElementByClass(className, root); +goog.i18n.bidi.enforceLtrInHtml = function(html) { + if (html.charAt(0) == '<') { + return html.replace(/<\w+/, '$& dir=ltr'); + } + // '\n' is important for FF so that it won't incorrectly merge span groups + return '\n<span dir=ltr>' + html + '</span>'; }; /** - * Alias for {@code getElementsByTagNameAndClass}. - * @deprecated Use DomHelper getElementsByTagNameAndClass. - * @see goog.dom.query - * - * @param {?string=} opt_tag Element tag name. - * @param {?string=} opt_class Optional class name. - * @param {Element=} opt_el Optional element to look in. - * @return { {length: number} } Array-like list of elements (only a length - * property and numerical indices are guaranteed to exist). + * Enforce LTR on both end of the given text piece using unicode BiDi formatting + * characters LRE and PDF. + * @param {string} text The piece of text that need to be wrapped. + * @return {string} The wrapped string after process. */ -goog.dom.DomHelper.prototype.$$ = - goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; +goog.i18n.bidi.enforceLtrInText = function(text) { + return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF; +}; /** - * Sets a number of properties on a node. - * @param {Element} element DOM node to set properties on. - * @param {Object} properties Hash of property:value pairs. + * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;" + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties; +goog.i18n.bidi.dimensionsRe_ = + /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g; /** - * Gets the dimensions of the viewport. - * @param {Window=} opt_window Optional window element to test. Defaults to - * the window of the Dom Helper. - * @return {!goog.math.Size} Object with values 'width' and 'height'. + * Regular expression for left. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) { - // TODO(arv): This should not take an argument. That breaks the rule of a - // a DomHelper representing a single frame/window/document. - return goog.dom.getViewportSize(opt_window || this.getWindow()); -}; +goog.i18n.bidi.leftRe_ = /left/gi; /** - * Calculates the height of the document. - * - * @return {number} The height of the document. + * Regular expression for right. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.getDocumentHeight = function() { - return goog.dom.getDocumentHeight_(this.getWindow()); -}; +goog.i18n.bidi.rightRe_ = /right/gi; /** - * Typedef for use with goog.dom.createDom and goog.dom.append. - * @typedef {Object|string|Array|NodeList} + * Placeholder regular expression for swapping. + * @type {RegExp} + * @private */ -goog.dom.Appendable; +goog.i18n.bidi.tempRe_ = /%%%%/g; /** - * Returns a dom node with a set of attributes. This function accepts varargs - * for subsequent nodes to be added. Subsequent nodes will be added to the - * first node as childNodes. - * - * So: - * <code>createDom('div', null, createDom('p'), createDom('p'));</code> - * would return a div with two child paragraphs - * - * An easy way to move all child nodes of an existing element to a new parent - * element is: - * <code>createDom('div', null, oldElement.childNodes);</code> - * which will remove all child nodes from the old element and add them as - * child nodes of the new DIV. - * - * @param {string} tagName Tag to create. - * @param {Object|string=} opt_attributes If object, then a map of name-value - * pairs for attributes. If a string, then this is the className of the new - * element. - * @param {...goog.dom.Appendable} var_args Further DOM nodes or - * strings for text nodes. If one of the var_args is an array or - * NodeList, its elements will be added as childNodes instead. - * @return {!Element} Reference to a DOM node. + * Swap location parameters and 'left'/'right' in CSS specification. The + * processed string will be suited for RTL layout. Though this function can + * cover most cases, there are always exceptions. It is suggested to put + * those exceptions in separate group of CSS string. + * @param {string} cssStr CSS spefication string. + * @return {string} Processed CSS specification string. */ -goog.dom.DomHelper.prototype.createDom = function(tagName, - opt_attributes, - var_args) { - return goog.dom.createDom_(this.document_, arguments); +goog.i18n.bidi.mirrorCSS = function(cssStr) { + return cssStr. + // reverse dimensions + replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2'). + replace(goog.i18n.bidi.leftRe_, '%%%%'). // swap left and right + replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT). + replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT); }; /** - * Alias for {@code createDom}. - * @param {string} tagName Tag to create. - * @param {(Object|string)=} opt_attributes If object, then a map of name-value - * pairs for attributes. If a string, then this is the className of the new - * element. - * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for - * text nodes. If one of the var_args is an array, its children will be - * added as childNodes instead. - * @return {!Element} Reference to a DOM node. - * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead. + * Regular expression for hebrew double quote substitution, finding quote + * directly after hebrew characters. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom; +goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g; /** - * Creates a new element. - * @param {string} name Tag name. - * @return {!Element} The new element. + * Regular expression for hebrew single quote substitution, finding quote + * directly after hebrew characters. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.createElement = function(name) { - return this.document_.createElement(name); -}; +goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g; /** - * Creates a new text node. - * @param {number|string} content Content. - * @return {!Text} The new text node. + * Replace the double and single quote directly after a Hebrew character with + * GERESH and GERSHAYIM. In such case, most likely that's user intention. + * @param {string} str String that need to be processed. + * @return {string} Processed string with double/single quote replaced. */ -goog.dom.DomHelper.prototype.createTextNode = function(content) { - return this.document_.createTextNode(String(content)); +goog.i18n.bidi.normalizeHebrewQuote = function(str) { + return str. + replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4'). + replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3'); }; /** - * Create a table. - * @param {number} rows The number of rows in the table. Must be >= 1. - * @param {number} columns The number of columns in the table. Must be >= 1. - * @param {boolean=} opt_fillWithNbsp If true, fills table entries with - * {@code goog.string.Unicode.NBSP} characters. - * @return {!HTMLElement} The created table. + * Regular expression to split a string into "words" for directionality + * estimation based on relative word counts. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.createTable = function(rows, columns, - opt_fillWithNbsp) { - return goog.dom.createTable_(this.document_, rows, columns, - !!opt_fillWithNbsp); -}; +goog.i18n.bidi.wordSeparatorRe_ = /\s+/; /** - * Converts an HTML into a node or a document fragment. A single Node is used if - * {@code html} only generates a single node. If {@code html} generates multiple - * nodes then these are put inside a {@code DocumentFragment}. - * @param {!goog.html.SafeHtml} html The HTML markup to convert. - * @return {!Node} The resulting node. + * Regular expression to check if a string contains any numerals. Used to + * differentiate between completely neutral strings and those containing + * numbers, which are weakly LTR. + * @type {RegExp} + * @private */ -goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) { - return goog.dom.safeHtmlToNode_(this.document_, html); -}; +goog.i18n.bidi.hasNumeralsRe_ = /\d/; /** - * Converts an HTML string into a node or a document fragment. A single Node - * is used if the {@code htmlString} only generates a single node. If the - * {@code htmlString} generates multiple nodes then these are put inside a - * {@code DocumentFragment}. - * - * @param {string} htmlString The HTML string to convert. - * @return {!Node} The resulting node. + * This constant controls threshold of RTL directionality. + * @type {number} + * @private */ -goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) { - return goog.dom.htmlToDocumentFragment_(this.document_, htmlString); -}; +goog.i18n.bidi.rtlDetectionThreshold_ = 0.40; /** - * Returns true if the browser is in "CSS1-compatible" (standards-compliant) - * mode, false otherwise. - * @return {boolean} True if in CSS1-compatible mode. + * Estimates the directionality of a string based on relative word counts. + * If the number of RTL words is above a certain percentage of the total number + * of strongly directional words, returns RTL. + * Otherwise, if any words are strongly or weakly LTR, returns LTR. + * Otherwise, returns UNKNOWN, which is used to mean "neutral". + * Numbers are counted as weakly LTR. + * @param {string} str The string to be checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}. */ -goog.dom.DomHelper.prototype.isCss1CompatMode = function() { - return goog.dom.isCss1CompatMode_(this.document_); -}; - +goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) { + var rtlCount = 0; + var totalCount = 0; + var hasWeaklyLtr = false; + var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml). + split(goog.i18n.bidi.wordSeparatorRe_); + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (goog.i18n.bidi.startsWithRtl(token)) { + rtlCount++; + totalCount++; + } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) { + hasWeaklyLtr = true; + } else if (goog.i18n.bidi.hasAnyLtr(token)) { + totalCount++; + } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) { + hasWeaklyLtr = true; + } + } -/** - * Gets the window object associated with the document. - * @return {!Window} The window associated with the given document. - */ -goog.dom.DomHelper.prototype.getWindow = function() { - return goog.dom.getWindow_(this.document_); + return totalCount == 0 ? + (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) : + (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ? + goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR); }; /** - * Gets the document scroll element. - * @return {!Element} Scrolling element. + * Check the directionality of a piece of text, return true if the piece of + * text should be laid out in RTL direction. + * @param {string} str The piece of text that need to be detected. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether this piece of text should be laid out in RTL. */ -goog.dom.DomHelper.prototype.getDocumentScrollElement = function() { - return goog.dom.getDocumentScrollElement_(this.document_); +goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) { + return goog.i18n.bidi.estimateDirection(str, opt_isHtml) == + goog.i18n.bidi.Dir.RTL; }; /** - * Gets the document scroll distance as a coordinate object. - * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'. + * Sets text input element's directionality and text alignment based on a + * given directionality. Does nothing if the given directionality is unknown or + * neutral. + * @param {Element} element Input field element to set directionality to. + * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality, + * given in one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LRT, negative = RTL, 0 = neutral). + * 3. A boolean (true = RTL, false = LTR). + * 4. A null for unknown directionality. */ -goog.dom.DomHelper.prototype.getDocumentScroll = function() { - return goog.dom.getDocumentScroll_(this.document_); +goog.i18n.bidi.setElementDirAndAlign = function(element, dir) { + if (element) { + dir = goog.i18n.bidi.toDir(dir); + if (dir) { + element.style.textAlign = + dir == goog.i18n.bidi.Dir.RTL ? + goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT; + element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr'; + } + } }; + /** - * Determines the active element in the given document. - * @param {Document=} opt_doc The document to look in. - * @return {Element} The active element. + * Strings that have an (optional) known direction. + * + * Implementations of this interface are string-like objects that carry an + * attached direction, if known. + * @interface */ -goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) { - return goog.dom.getActiveElement(opt_doc || this.document_); -}; +goog.i18n.bidi.DirectionalString = function() {}; /** - * Appends a child to a node. - * @param {Node} parent Parent. - * @param {Node} child Child. + * Interface marker of the DirectionalString interface. + * + * This property can be used to determine at runtime whether or not an object + * implements this interface. All implementations of this interface set this + * property to {@code true}. + * @type {boolean} */ -goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild; +goog.i18n.bidi.DirectionalString.prototype. + implementsGoogI18nBidiDirectionalString; /** - * Appends a node with text or other nodes. - * @param {!Node} parent The node to append nodes to. - * @param {...goog.dom.Appendable} var_args The things to append to the node. - * If this is a Node it is appended as is. - * If this is a string then a text node is appended. - * If this is an array like object then fields 0 to length - 1 are appended. + * Retrieves this object's known direction (if any). + * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown. */ -goog.dom.DomHelper.prototype.append = goog.dom.append; +goog.i18n.bidi.DirectionalString.prototype.getDirection; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Determines if the given node can contain children, intended to be used for - * HTML generation. + * @fileoverview The SafeUrl type and its builders. * - * @param {Node} node The node to check. - * @return {boolean} Whether the node can contain children. + * TODO(user): Link to document stating type contract. */ -goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren; +goog.provide('goog.html.SafeUrl'); -/** - * Removes all the child nodes on a DOM node. - * @param {Node} node Node to remove children from. - */ -goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren; - +goog.require('goog.asserts'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.DirectionalString'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); -/** - * Inserts a new node before an existing reference node (i.e., as the previous - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert before. - */ -goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore; /** - * Inserts a new node after an existing reference node (i.e., as the next - * sibling). If the reference node has no parent, then does nothing. - * @param {Node} newNode Node to insert. - * @param {Node} refNode Reference node to insert after. + * A string that is safe to use in URL context in DOM APIs and HTML documents. + * + * A SafeUrl is a string-like object that carries the security type contract + * that its value as a string will not cause untrusted script execution + * when evaluated as a hyperlink URL in a browser. + * + * Values of this type are guaranteed to be safe to use in URL/hyperlink + * contexts, such as, assignment to URL-valued DOM properties, or + * interpolation into a HTML template in URL context (e.g., inside a href + * attribute), in the sense that the use will not result in a + * Cross-Site-Scripting vulnerability. + * + * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's + * contract does not guarantee that instances are safe to interpolate into HTML + * without appropriate escaping. + * + * Note also that this type's contract does not imply any guarantees regarding + * the resource the URL refers to. In particular, SafeUrls are <b>not</b> + * safe to use in a context where the referred-to resource is interpreted as + * trusted code, e.g., as the src of a script tag. + * + * Instances of this type must be created via the factory methods + * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}), + * etc and not by invoking its constructor. The constructor intentionally + * takes no parameters and the type is immutable; hence only a default instance + * corresponding to the empty string can be obtained via constructor invocation. + * + * @see goog.html.SafeUrl#fromConstant + * @see goog.html.SafeUrl#from + * @see goog.html.SafeUrl#sanitize + * @constructor + * @final + * @struct + * @implements {goog.i18n.bidi.DirectionalString} + * @implements {goog.string.TypedString} */ -goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter; - +goog.html.SafeUrl = function() { + /** + * The contained value of this SafeUrl. The field has a purposely ugly + * name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; -/** - * Insert a child at a given index. If index is larger than the number of child - * nodes that the parent currently has, the node is inserted as the last child - * node. - * @param {Element} parent The element into which to insert the child. - * @param {Node} child The element to insert. - * @param {number} index The index at which to insert the new child node. Must - * not be negative. - */ -goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt; + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeUrl#unwrap + * @const + * @private + */ + this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; /** - * Removes a node from its parent. - * @param {Node} node The node to remove. - * @return {Node} The node removed if removed; else, null. + * The innocuous string generated by goog.html.SafeUrl.sanitize when passed + * an unsafe URL. + * + * about:invalid is registered in + * http://www.w3.org/TR/css3-values/#about-invalid. + * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to + * contain a fragment, which is not to be considered when determining if an + * about URL is well-known. + * + * Using about:invalid seems preferable to using a fixed data URL, since + * browsers might choose to not report CSP violations on it, as legitimate + * CSS function calls to attr() can result in this URL being produced. It is + * also a standard URL which matches exactly the semantics we need: + * "The about:invalid URI references a non-existent document with a generic + * error condition. It can be used when a URI is necessary, but the default + * value shouldn't be resolveable as any type of document". + * + * @const {string} */ -goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode; +goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez'; /** - * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no - * parent. - * @param {Node} newNode Node to insert. - * @param {Node} oldNode Node to replace. + * @override + * @const */ -goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode; +goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true; /** - * Flattens an element. That is, removes it and replace it with its children. - * @param {Element} element The element to flatten. - * @return {Element|undefined} The original element, detached from the document - * tree, sans children, or undefined if the element was already not in the - * document. + * Returns this SafeUrl's value a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this + * method. If in doubt, assume that it's security relevant. In particular, note + * that goog.html functions which return a goog.html type do not guarantee that + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof + * // goog.html.SafeHtml. + * </pre> + * + * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the + * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST + * be appropriately escaped before embedding in a HTML document. Note that the + * required escaping is context-sensitive (e.g. a different escaping is + * required for embedding a URL in a style property within a style + * attribute, as opposed to embedding in a href attribute). + * + * @see goog.html.SafeUrl#unwrap + * @override */ -goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement; +goog.html.SafeUrl.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +}; /** - * Returns an array containing just the element children of the given element. - * @param {Element} element The element whose element children we want. - * @return {!(Array|NodeList)} An array or array-like list of just the element - * children of the given element. + * @override + * @const */ -goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren; +goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true; /** - * Returns the first child node that is an element. - * @param {Node} node The node to get the first child element of. - * @return {Element} The first child node of {@code node} that is an element. + * Returns this URLs directionality, which is always {@code LTR}. + * @override */ -goog.dom.DomHelper.prototype.getFirstElementChild = - goog.dom.getFirstElementChild; +goog.html.SafeUrl.prototype.getDirection = function() { + return goog.i18n.bidi.Dir.LTR; +}; -/** - * Returns the last child node that is an element. - * @param {Node} node The node to get the last child element of. - * @return {Element} The last child node of {@code node} that is an element. - */ -goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild; +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeUrl, use + * {@code goog.html.SafeUrl.unwrap}. + * + * @see goog.html.SafeUrl#unwrap + * @override + */ + goog.html.SafeUrl.prototype.toString = function() { + return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + + '}'; + }; +} /** - * Returns the first next sibling that is an element. - * @param {Node} node The node to get the next sibling element of. - * @return {Element} The next sibling of {@code node} that is an element. + * Performs a runtime check that the provided object is indeed a SafeUrl + * object, and returns its value. + * + * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the + * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST + * be appropriately escaped before embedding in a HTML document. Note that the + * required escaping is context-sensitive (e.g. a different escaping is + * required for embedding a URL in a style property within a style + * attribute, as opposed to embedding in a href attribute). + * + * Note that the returned value does not necessarily correspond to the string + * with which the SafeUrl was constructed, since goog.html.SafeUrl.sanitize + * will percent-encode many characters. + * + * @param {!goog.html.SafeUrl} safeUrl The object to extract from. + * @return {string} The SafeUrl object's contained string, unless the run-time + * type check fails. In that case, {@code unwrap} returns an innocuous + * string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. */ -goog.dom.DomHelper.prototype.getNextElementSibling = - goog.dom.getNextElementSibling; +goog.html.SafeUrl.unwrap = function(safeUrl) { + // Perform additional Run-time type-checking to ensure that safeUrl is indeed + // an instance of the expected type. This provides some additional protection + // against security bugs due to application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeUrl instanceof goog.html.SafeUrl && + safeUrl.constructor === goog.html.SafeUrl && + safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeUrl, got \'' + + safeUrl + '\''); + return 'type_error:SafeUrl'; + + } +}; /** - * Returns the first previous sibling that is an element. - * @param {Node} node The node to get the previous sibling element of. - * @return {Element} The first previous sibling of {@code node} that is - * an element. + * Creates a SafeUrl object from a compile-time constant string. + * + * Compile-time constant strings are inherently program-controlled and hence + * trusted. + * + * @param {!goog.string.Const} url A compile-time-constant string from which to + * create a SafeUrl. + * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}. */ -goog.dom.DomHelper.prototype.getPreviousElementSibling = - goog.dom.getPreviousElementSibling; +goog.html.SafeUrl.fromConstant = function(url) { + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse( + goog.string.Const.unwrap(url)); +}; /** - * Returns the next node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The next node in the DOM tree, or null if this was the last - * node. + * A pattern that recognizes a commonly useful subset of URLs that satisfy + * the SafeUrl contract. + * + * This regular expression matches a subset of URLs that will not cause script + * execution if used in URL context within a HTML document. Specifically, this + * regular expression matches if (comment from here on and regex copied from + * Soy's EscapingConventions): + * (1) Either a protocol in a whitelist (http, https, mailto). + * (2) or no protocol. A protocol must be followed by a colon. The below + * allows that by allowing colons only after one of the characters [/?#]. + * A colon after a hash (#) must be in the fragment. + * Otherwise, a colon after a (?) must be in a query. + * Otherwise, a colon after a single solidus (/) must be in a path. + * Otherwise, a colon after a double solidus (//) must be in the authority + * (before port). + * + * The pattern disallows &, used in HTML entity declarations before + * one of the characters in [/?#]. This disallows HTML entities used in the + * protocol name, which should never happen, e.g. "http" for "http". + * It also disallows HTML entities in the first path part of a relative path, + * e.g. "foo<bar/baz". Our existing escaping functions should not produce + * that. More importantly, it disallows masking of a colon, + * e.g. "javascript:...". + * + * @private + * @const {!RegExp} */ -goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode; +goog.html.SAFE_URL_PATTERN_ = /^(?:(?:https?|mailto):|[^&:/?#]*(?:[/?#]|$))/i; /** - * Returns the previous node in source order from the given node. - * @param {Node} node The node. - * @return {Node} The previous node in the DOM tree, or null if this was the - * first node. + * Creates a SafeUrl object from {@code url}. If {@code url} is a + * goog.html.SafeUrl then it is simply returned. Otherwise the input string is + * validated to match a pattern of commonly used safe URLs. The string is + * converted to UTF-8 and non-whitelisted characters are percent-encoded. The + * string wrapped by the created SafeUrl will thus contain only ASCII printable + * characters. + * + * {@code url} may be a URL with the http, https, or mailto scheme, + * or a relative URL (i.e., a URL without a scheme; specifically, a + * scheme-relative, absolute-path-relative, or path-relative URL). + * + * {@code url} is converted to UTF-8 and non-whitelisted characters are + * percent-encoded. Whitelisted characters are '%' and, from RFC 3986, + * unreserved characters and reserved characters, with the exception of '\'', + * '(' and ')'. This ensures the the SafeUrl contains only ASCII-printable + * characters and reduces the chance of security bugs were it to be + * interpolated into a specific context without the necessary escaping. + * + * If {@code url} fails validation or does not UTF-16 decode correctly + * (JavaScript strings are UTF-16 encoded), this function returns a SafeUrl + * object containing an innocuous string, goog.html.SafeUrl.INNOCUOUS_STRING. + * + * @see http://url.spec.whatwg.org/#concept-relative-url + * @param {string|!goog.string.TypedString} url The URL to validate. + * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl. */ -goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode; +goog.html.SafeUrl.sanitize = function(url) { + if (url instanceof goog.html.SafeUrl) { + return url; + } + else if (url.implementsGoogStringTypedString) { + url = url.getTypedStringValue(); + } else { + url = String(url); + } + if (!goog.html.SAFE_URL_PATTERN_.test(url)) { + url = goog.html.SafeUrl.INNOCUOUS_STRING; + } else { + url = goog.html.SafeUrl.normalize_(url); + } + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +}; /** - * Whether the object looks like a DOM node. - * @param {?} obj The object being tested for node likeness. - * @return {boolean} Whether the object looks like a DOM node. + * Normalizes {@code url} the UTF-8 encoding of url, using a whitelist of + * characters. Whitelisted characters are not percent-encoded. + * @param {string} url The URL to normalize. + * @return {string} The normalized URL. + * @private */ -goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike; +goog.html.SafeUrl.normalize_ = function(url) { + try { + var normalized = encodeURI(url); + } catch (e) { // Happens if url contains invalid surrogate sequences. + return goog.html.SafeUrl.INNOCUOUS_STRING; + } + + return normalized.replace( + goog.html.SafeUrl.NORMALIZE_MATCHER_, + function(match) { + return goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_[match]; + }); +}; /** - * Whether the object looks like an Element. - * @param {?} obj The object being tested for Element likeness. - * @return {boolean} Whether the object looks like an Element. + * Matches characters and strings which need to be replaced in the string + * generated by encodeURI. Specifically: + * + * - '\'', '(' and ')' are not encoded. They are part of the reserved + * characters group in RFC 3986 but only appear in the obsolete mark + * production in Appendix D.2 of RFC 3986, so they can be encoded without + * changing semantics. + * - '[' and ']' are encoded by encodeURI, despite being reserved characters + * which can be used to represent IPv6 addresses. So they need to be decoded. + * - '%' is encoded by encodeURI. However, encoding '%' characters that are + * already part of a valid percent-encoded sequence changes the semantics of a + * URL, and hence we need to preserve them. Note that this may allow + * non-encoded '%' characters to remain in the URL (i.e., occurrences of '%' + * that are not part of a valid percent-encoded sequence, for example, + * 'ab%xy'). + * + * @const {!RegExp} + * @private */ -goog.dom.DomHelper.prototype.isElement = goog.dom.isElement; +goog.html.SafeUrl.NORMALIZE_MATCHER_ = /[()']|%5B|%5D|%25/g; /** - * Returns true if the specified value is a Window object. This includes the - * global window for HTML pages, and iframe windows. - * @param {?} obj Variable to test. - * @return {boolean} Whether the variable is a window. + * Map of replacements to be done in string generated by encodeURI. + * @const {!Object<string, string>} + * @private */ -goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow; +goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_ = { + '\'': '%27', + '(': '%28', + ')': '%29', + '%5B': '[', + '%5D': ']', + '%25': '%' +}; /** - * Returns an element's parent, if it's an Element. - * @param {Element} element The DOM element. - * @return {Element} The parent, or null if not an Element. + * Type marker for the SafeUrl type, used to implement additional run-time + * type checking. + * @const + * @private */ -goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement; +goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; /** - * Whether a node contains another node. - * @param {Node} parent The node that should contain the other node. - * @param {Node} descendant The node to test presence of. - * @return {boolean} Whether the parent node contains the descendent node. + * Package-internal utility method to create SafeUrl instances. + * + * @param {string} url The string to initialize the SafeUrl object with. + * @return {!goog.html.SafeUrl} The initialized SafeUrl object. + * @package */ -goog.dom.DomHelper.prototype.contains = goog.dom.contains; +goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function( + url) { + var safeUrl = new goog.html.SafeUrl(); + safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url; + return safeUrl; +}; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Compares the document order of two nodes, returning 0 if they are the same - * node, a negative number if node1 is before node2, and a positive number if - * node2 is before node1. Note that we compare the order the tags appear in the - * document so in the tree <b><i>text</i></b> the B node is considered to be - * before the I node. + * @fileoverview The TrustedResourceUrl type and its builders. * - * @param {Node} node1 The first node to compare. - * @param {Node} node2 The second node to compare. - * @return {number} 0 if the nodes are the same node, a negative number if node1 - * is before node2, and a positive number if node2 is before node1. + * TODO(user): Link to document stating type contract. */ -goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder; +goog.provide('goog.html.TrustedResourceUrl'); + +goog.require('goog.asserts'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.DirectionalString'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); -/** - * Find the deepest common ancestor of the given nodes. - * @param {...Node} var_args The nodes to find a common ancestor of. - * @return {Node} The common ancestor of the nodes, or null if there is none. - * null will only be returned if two or more of the nodes are from different - * documents. - */ -goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor; /** - * Returns the owner document for a node. - * @param {Node} node The node to get the document for. - * @return {!Document} The document owning the node. + * A URL which is under application control and from which script, CSS, and + * other resources that represent executable code, can be fetched. + * + * Given that the URL can only be constructed from strings under application + * control and is used to load resources, bugs resulting in a malformed URL + * should not have a security impact and are likely to be easily detectable + * during testing. Given the wide number of non-RFC compliant URLs in use, + * stricter validation could prevent some applications from being able to use + * this type. + * + * Instances of this type must be created via the factory method, + * ({@code goog.html.TrustedResourceUrl.fromConstant}), and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty + * string can be obtained via constructor invocation. + * + * @see goog.html.TrustedResourceUrl#fromConstant + * @constructor + * @final + * @struct + * @implements {goog.i18n.bidi.DirectionalString} + * @implements {goog.string.TypedString} */ -goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument; +goog.html.TrustedResourceUrl = function() { + /** + * The contained value of this TrustedResourceUrl. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.TrustedResourceUrl#unwrap + * @const + * @private + */ + this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +}; /** - * Cross browser function for getting the document element of an iframe. - * @param {Element} iframe Iframe element. - * @return {!Document} The frame content document. + * @override + * @const */ -goog.dom.DomHelper.prototype.getFrameContentDocument = - goog.dom.getFrameContentDocument; +goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true; /** - * Cross browser function for getting the window of a frame or iframe. - * @param {Element} frame Frame element. - * @return {Window} The window associated with the given frame. - */ -goog.dom.DomHelper.prototype.getFrameContentWindow = - goog.dom.getFrameContentWindow; + * Returns this TrustedResourceUrl's value as a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code TrustedResourceUrl}, use + * {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in + * doubt, assume that it's security relevant. In particular, note that + * goog.html functions which return a goog.html type do not guarantee that + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof + * // goog.html.SafeHtml. + * </pre> + * + * @see goog.html.TrustedResourceUrl#unwrap + * @override + */ +goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; +}; /** - * Sets the text content of a node, with cross-browser support. - * @param {Node} node The node to change the text content of. - * @param {string|number} text The value that should replace the node's content. + * @override + * @const */ -goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent; +goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString = + true; /** - * Gets the outerHTML of a node, which islike innerHTML, except that it - * actually contains the HTML of the node itself. - * @param {Element} element The element to get the HTML of. - * @return {string} The outerHTML of the given element. + * Returns this URLs directionality, which is always {@code LTR}. + * @override */ -goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml; +goog.html.TrustedResourceUrl.prototype.getDirection = function() { + return goog.i18n.bidi.Dir.LTR; +}; -/** - * Finds the first descendant node that matches the filter function. This does - * a depth first search. - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {Node|undefined} The found node or undefined if none is found. - */ -goog.dom.DomHelper.prototype.findNode = goog.dom.findNode; +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a TrustedResourceUrl, use + * {@code goog.html.TrustedResourceUrl.unwrap}. + * + * @see goog.html.TrustedResourceUrl#unwrap + * @override + */ + goog.html.TrustedResourceUrl.prototype.toString = function() { + return 'TrustedResourceUrl{' + + this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}'; + }; +} /** - * Finds all the descendant nodes that matches the filter function. This does a - * depth first search. - * @param {Node} root The root of the tree to search. - * @param {function(Node) : boolean} p The filter function. - * @return {Array<Node>} The found nodes or an empty array if none are found. + * Performs a runtime check that the provided object is indeed a + * TrustedResourceUrl object, and returns its value. + * + * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to + * extract from. + * @return {string} The trustedResourceUrl object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. */ -goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes; +goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) { + // Perform additional Run-time type-checking to ensure that + // trustedResourceUrl is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl && + trustedResourceUrl.constructor === goog.html.TrustedResourceUrl && + trustedResourceUrl + .TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.TrustedResourceUrl + .TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return trustedResourceUrl + .privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_; + } else { + goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' + + trustedResourceUrl + '\''); + return 'type_error:TrustedResourceUrl'; + + } +}; /** - * Returns true if the element has a tab index that allows it to receive - * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements - * natively support keyboard focus, even if they have no tab index. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element has a tab index that allows keyboard - * focus. + * Creates a TrustedResourceUrl object from a compile-time constant string. + * + * Compile-time constant strings are inherently program-controlled and hence + * trusted. + * + * @param {!goog.string.Const} url A compile-time-constant string from which to + * create a TrustedResourceUrl. + * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object + * initialized to {@code url}. */ -goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex; +goog.html.TrustedResourceUrl.fromConstant = function(url) { + return goog.html.TrustedResourceUrl + .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse( + goog.string.Const.unwrap(url)); +}; /** - * Enables or disables keyboard focus support on the element via its tab index. - * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true - * (or elements that natively support keyboard focus, like form elements) can - * receive keyboard focus. See http://go/tabindex for more info. - * @param {Element} element Element whose tab index is to be changed. - * @param {boolean} enable Whether to set or remove a tab index on the element - * that supports keyboard focus. + * Type marker for the TrustedResourceUrl type, used to implement additional + * run-time type checking. + * @const + * @private */ -goog.dom.DomHelper.prototype.setFocusableTabIndex = - goog.dom.setFocusableTabIndex; +goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; /** - * Returns true if the element can be focused, i.e. it has a tab index that - * allows it to receive keyboard focus (tabIndex >= 0), or it is an element - * that natively supports keyboard focus. - * @param {!Element} element Element to check. - * @return {boolean} Whether the element allows keyboard focus. + * Package-internal utility method to create TrustedResourceUrl instances. + * + * @param {string} url The string to initialize the TrustedResourceUrl object + * with. + * @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl + * object. + * @package */ -goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable; +goog.html.TrustedResourceUrl. + createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) { + var trustedResourceUrl = new goog.html.TrustedResourceUrl(); + trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = + url; + return trustedResourceUrl; +}; + +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Returns the text contents of the current node, without markup. New lines are - * stripped and whitespace is collapsed, such that each character would be - * visible. - * - * In browsers that support it, innerText is used. Other browsers attempt to - * simulate it via node traversal. Line breaks are canonicalized in IE. + * @fileoverview The SafeHtml type and its builders. * - * @param {Node} node The node from which we are getting content. - * @return {string} The text content. + * TODO(user): Link to document stating type contract. */ -goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent; + +goog.provide('goog.html.SafeHtml'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.tags'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.i18n.bidi.Dir'); +goog.require('goog.i18n.bidi.DirectionalString'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); + /** - * Returns the text length of the text contained in a node, without markup. This - * is equivalent to the selection length if the node was selected, or the number - * of cursor movements to traverse the node. Images & BRs take one space. New - * lines are ignored. + * A string that is safe to use in HTML context in DOM APIs and HTML documents. * - * @param {Node} node The node whose text content length is being calculated. - * @return {number} The length of {@code node}'s text content. + * A SafeHtml is a string-like object that carries the security type contract + * that its value as a string will not cause untrusted script execution when + * evaluated as HTML in a browser. + * + * Values of this type are guaranteed to be safe to use in HTML contexts, + * such as, assignment to the innerHTML DOM property, or interpolation into + * a HTML template in HTML PC_DATA context, in the sense that the use will not + * result in a Cross-Site-Scripting vulnerability. + * + * Instances of this type must be created via the factory methods + * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}), + * etc and not by invoking its constructor. The constructor intentionally + * takes no parameters and the type is immutable; hence only a default instance + * corresponding to the empty string can be obtained via constructor invocation. + * + * @see goog.html.SafeHtml#create + * @see goog.html.SafeHtml#htmlEscape + * @constructor + * @final + * @struct + * @implements {goog.i18n.bidi.DirectionalString} + * @implements {goog.string.TypedString} */ -goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength; +goog.html.SafeHtml = function() { + /** + * The contained value of this SafeHtml. The field has a purposely ugly + * name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} + */ + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = ''; + + /** + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeHtml#unwrap + * @const + * @private + */ + this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + + /** + * This SafeHtml's directionality, or null if unknown. + * @private {?goog.i18n.bidi.Dir} + */ + this.dir_ = null; +}; /** - * Returns the text offset of a node relative to one of its ancestors. The text - * length is the same as the length calculated by - * {@code goog.dom.getNodeTextLength}. - * - * @param {Node} node The node whose offset is being calculated. - * @param {Node=} opt_offsetParent Defaults to the node's owner document's body. - * @return {number} The text offset. + * @override + * @const */ -goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset; +goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true; + + +/** @override */ +goog.html.SafeHtml.prototype.getDirection = function() { + return this.dir_; +}; /** - * Returns the node at a given offset in a parent node. If an object is - * provided for the optional third parameter, the node and the remainder of the - * offset will stored as properties of this object. - * @param {Node} parent The parent node. - * @param {number} offset The offset into the parent node. - * @param {Object=} opt_result Object to be used to store the return value. The - * return value will be stored in the form {node: Node, remainder: number} - * if this object is provided. - * @return {Node} The node at the given offset. + * @override + * @const */ -goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset; +goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true; /** - * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, - * the object must have a numeric length property and an item function (which - * has type 'string' on IE for some reason). - * @param {Object} val Object to test. - * @return {boolean} Whether the object is a NodeList. + * Returns this SafeHtml's value a string. + * + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of + * this method. If in doubt, assume that it's security relevant. In particular, + * note that goog.html functions which return a goog.html type do not guarantee + * that the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeHtml#unwrap + * @override */ -goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList; +goog.html.SafeHtml.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_; +}; + + +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeHtml, use + * {@code goog.html.SafeHtml.unwrap}. + * + * @see goog.html.SafeHtml#unwrap + * @override + */ + goog.html.SafeHtml.prototype.toString = function() { + return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ + + '}'; + }; +} /** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * tag name and/or class name. If the passed element matches the specified - * criteria, the element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or - * null/undefined to match only based on class name). - * @param {?string=} opt_class The class name to match (or null/undefined to - * match only based on tag name). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if no match is found. + * Performs a runtime check that the provided object is indeed a SafeHtml + * object, and returns its value. + * @param {!goog.html.SafeHtml} safeHtml The object to extract from. + * @return {string} The SafeHtml object's contained string, unless the run-time + * type check fails. In that case, {@code unwrap} returns an innocuous + * string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. */ -goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass = - goog.dom.getAncestorByTagNameAndClass; +goog.html.SafeHtml.unwrap = function(safeHtml) { + // Perform additional run-time type-checking to ensure that safeHtml is indeed + // an instance of the expected type. This provides some additional protection + // against security bugs due to application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeHtml instanceof goog.html.SafeHtml && + safeHtml.constructor === goog.html.SafeHtml && + safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_; + } else { + goog.asserts.fail('expected object of type SafeHtml, got \'' + + safeHtml + '\''); + return 'type_error:SafeHtml'; + } +}; /** - * Walks up the DOM hierarchy returning the first ancestor that has the passed - * class name. If the passed element matches the specified criteria, the - * element itself is returned. - * @param {Node} element The DOM node to start with. - * @param {string} class The class name to match. - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Element} The first ancestor that matches the passed criteria, or - * null if none match. + * Shorthand for union of types that can sensibly be converted to strings + * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString). + * @private + * @typedef {string|number|boolean|!goog.string.TypedString| + * !goog.i18n.bidi.DirectionalString} */ -goog.dom.DomHelper.prototype.getAncestorByClass = - goog.dom.getAncestorByClass; +goog.html.SafeHtml.TextOrHtml_; /** - * Walks up the DOM hierarchy returning the first ancestor that passes the - * matcher function. - * @param {Node} element The DOM node to start with. - * @param {function(Node) : boolean} matcher A function that returns true if the - * passed node matches the desired criteria. - * @param {boolean=} opt_includeNode If true, the node itself is included in - * the search (the first call to the matcher will pass startElement as - * the node to test). - * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the - * dom. - * @return {Node} DOM node that matched the matcher, or null if there was - * no match. + * Returns HTML-escaped text as a SafeHtml object. + * + * If text is of a type that implements + * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new + * {@code SafeHtml} object is set to {@code text}'s directionality, if known. + * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e., + * {@code null}). + * + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If + * the parameter is of type SafeHtml it is returned directly (no escaping + * is done). + * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. */ -goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor; - -// FIXME add tests for browser features (Modernizr?) +goog.html.SafeHtml.htmlEscape = function(textOrHtml) { + if (textOrHtml instanceof goog.html.SafeHtml) { + return textOrHtml; + } + var dir = null; + if (textOrHtml.implementsGoogI18nBidiDirectionalString) { + dir = textOrHtml.getDirection(); + } + var textAsString; + if (textOrHtml.implementsGoogStringTypedString) { + textAsString = textOrHtml.getTypedStringValue(); + } else { + textAsString = String(textOrHtml); + } + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + goog.string.htmlEscape(textAsString), dir); +}; -goog.provide('ol.dom'); -goog.provide('ol.dom.BrowserFeature'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.userAgent'); -goog.require('goog.vec.Mat4'); -goog.require('ol'); +/** + * Returns HTML-escaped text as a SafeHtml object, with newlines changed to + * <br>. + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If + * the parameter is of type SafeHtml it is returned directly (no escaping + * is done). + * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. + */ +goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) { + if (textOrHtml instanceof goog.html.SafeHtml) { + return textOrHtml; + } + var html = goog.html.SafeHtml.htmlEscape(textOrHtml); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)), + html.getDirection()); +}; /** - * Create an html canvas element and returns its 2d context. - * @param {number=} opt_width Canvas width. - * @param {number=} opt_height Canvas height. - * @return {CanvasRenderingContext2D} + * Returns HTML-escaped text as a SafeHtml object, with newlines changed to + * <br> and escaping whitespace to preserve spatial formatting. Character + * entity #160 is used to make it safer for XML. + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If + * the parameter is of type SafeHtml it is returned directly (no escaping + * is done). + * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml. */ -ol.dom.createCanvasContext2D = function(opt_width, opt_height) { - var canvas = goog.dom.createElement(goog.dom.TagName.CANVAS); - if (goog.isDef(opt_width)) { - canvas.width = opt_width; - } - if (goog.isDef(opt_height)) { - canvas.height = opt_height; +goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function( + textOrHtml) { + if (textOrHtml instanceof goog.html.SafeHtml) { + return textOrHtml; } - return canvas.getContext('2d'); + var html = goog.html.SafeHtml.htmlEscape(textOrHtml); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)), + html.getDirection()); }; /** - * @enum {boolean} + * Coerces an arbitrary object into a SafeHtml object. + * + * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same + * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and + * HTML-escaped. If {@code textOrHtml} is of a type that implements + * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is + * preserved. + * + * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to + * coerce. + * @return {!goog.html.SafeHtml} The resulting SafeHtml object. + * @deprecated Use goog.html.SafeHtml.htmlEscape. */ -ol.dom.BrowserFeature = { - USE_MS_MATRIX_TRANSFORM: ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE, - USE_MS_ALPHA_FILTER: ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE -}; +goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape; /** - * Detect 2d transform. - * Adapted from http://stackoverflow.com/q/5661671/130442 - * http://caniuse.com/#feat=transforms2d - * @return {boolean} + * @const + * @private */ -ol.dom.canUseCssTransform = (function() { - var canUseCssTransform; - return function() { - if (!goog.isDef(canUseCssTransform)) { - goog.asserts.assert(!goog.isNull(document.body)); - if (!goog.global.getComputedStyle) { - // this browser is ancient - canUseCssTransform = false; - } else { - var el = goog.dom.createElement(goog.dom.TagName.P), - has2d, - transforms = { - 'webkitTransform': '-webkit-transform', - 'OTransform': '-o-transform', - 'msTransform': '-ms-transform', - 'MozTransform': '-moz-transform', - 'transform': 'transform' - }; - goog.dom.appendChild(document.body, el); - for (var t in transforms) { - if (t in el.style) { - el.style[t] = 'translate(1px,1px)'; - has2d = goog.global.getComputedStyle(el).getPropertyValue( - transforms[t]); - } - } - goog.dom.removeNode(el); - - canUseCssTransform = (has2d && has2d !== 'none'); - } - } - return canUseCssTransform; - }; -}()); +goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/; /** - * Detect 3d transform. - * Adapted from http://stackoverflow.com/q/5661671/130442 - * http://caniuse.com/#feat=transforms3d - * @return {boolean} + * Set of attributes containing URL as defined at + * http://www.w3.org/TR/html5/index.html#attributes-1. + * @private @const {!Object<string,boolean>} */ -ol.dom.canUseCssTransform3D = (function() { - var canUseCssTransform3D; - return function() { - if (!goog.isDef(canUseCssTransform3D)) { - goog.asserts.assert(!goog.isNull(document.body)); - if (!goog.global.getComputedStyle) { - // this browser is ancient - canUseCssTransform3D = false; - } else { - var el = goog.dom.createElement(goog.dom.TagName.P), - has3d, - transforms = { - 'webkitTransform': '-webkit-transform', - 'OTransform': '-o-transform', - 'msTransform': '-ms-transform', - 'MozTransform': '-moz-transform', - 'transform': 'transform' - }; - goog.dom.appendChild(document.body, el); - for (var t in transforms) { - if (t in el.style) { - el.style[t] = 'translate3d(1px,1px,1px)'; - has3d = goog.global.getComputedStyle(el).getPropertyValue( - transforms[t]); - } - } - goog.dom.removeNode(el); - - canUseCssTransform3D = (has3d && has3d !== 'none'); - } - } - return canUseCssTransform3D; - }; -}()); +goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet('action', 'cite', + 'data', 'formaction', 'href', 'manifest', 'poster', 'src'); /** - * @param {Element} element Element. - * @param {string} value Value. + * Tags which are unsupported via create(). They might be supported via a + * tag-specific create method. These are tags which might require a + * TrustedResourceUrl in one of their attributes or a restricted type for + * their content. + * @private @const {!Object<string,boolean>} */ -ol.dom.setTransform = function(element, value) { - var style = element.style; - style.WebkitTransform = value; - style.MozTransform = value; - style.OTransform = value; - style.msTransform = value; - style.transform = value; - - // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason - if (goog.userAgent.IE && !ol.IS_LEGACY_IE) { - element.style.transformOrigin = '0 0'; - } -}; +goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet( + 'embed', 'iframe', 'link', 'object', 'script', 'style', 'template'); /** - * Sets the opacity of an element, in an IE-compatible way - * @param {!Element} element Element - * @param {number} value Opacity, [0..1] + * @typedef {string|number|goog.string.TypedString| + * goog.html.SafeStyle.PropertyMap} + * @private */ -ol.dom.setOpacity = function(element, value) { - if (ol.dom.BrowserFeature.USE_MS_ALPHA_FILTER) { - /** @type {string} */ - var filter = element.currentStyle.filter; - - /** @type {RegExp} */ - var regex; - - /** @type {string} */ - var alpha; - - if (goog.userAgent.VERSION == '8.0') { - regex = /progid:DXImageTransform\.Microsoft\.Alpha\(.*?\)/i, - alpha = 'progid:DXImageTransform.Microsoft.Alpha(Opacity=' + - (value * 100) + ')'; - } else { - regex = /alpha\(.*?\)/i; - alpha = 'alpha(opacity=' + (value * 100) + ')'; - } - - var newFilter = filter.replace(regex, alpha); - if (newFilter === filter) { - // no replace was made? just append the new alpha filter instead - newFilter += ' ' + alpha; - } +goog.html.SafeHtml.AttributeValue_; - element.style.filter = newFilter; - // Fix to apply filter to absolutely-positioned children element - if (element.currentStyle.zIndex === 'auto') { - element.style.zIndex = 0; - } - } else { - element.style.opacity = value; +/** + * Creates a SafeHtml content consisting of a tag with optional attributes and + * optional content. + * + * For convenience tag names and attribute names are accepted as regular + * strings, instead of goog.string.Const. Nevertheless, you should not pass + * user-controlled values to these parameters. Note that these parameters are + * syntactically validated at runtime, and invalid values will result in + * an exception. + * + * Example usage: + * + * goog.html.SafeHtml.create('br'); + * goog.html.SafeHtml.create('div', {'class': 'a'}); + * goog.html.SafeHtml.create('p', {}, 'a'); + * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br')); + * + * goog.html.SafeHtml.create('span', { + * 'style': {'margin': '0'} + * }); + * + * To guarantee SafeHtml's type contract is upheld there are restrictions on + * attribute values and tag names. + * + * - For attributes which contain script code (on*), a goog.string.Const is + * required. + * - For attributes which contain style (style), a goog.html.SafeStyle or a + * goog.html.SafeStyle.PropertyMap is required. + * - For attributes which are interpreted as URLs (e.g. src, href) a + * goog.html.SafeUrl or goog.string.Const is required. + * - For tags which can load code, more specific goog.html.SafeHtml.create*() + * functions must be used. Tags which can load code and are not supported by + * this function are embed, iframe, link, object, script, style, and template. + * + * @param {string} tagName The name of the tag. Only tag names consisting of + * [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed. + * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} + * opt_attributes Mapping from attribute names to their values. Only + * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or + * undefined causes the attribute to be omitted. + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to + * HTML-escape and put inside the tag. This must be empty for void tags + * like <br>. Array elements are concatenated. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid tag name, attribute name, or attribute value is + * provided. + * @throws {goog.asserts.AssertionError} If content for void tag is provided. + */ +goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) { + if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) { + throw Error('Invalid tag name <' + tagName + '>.'); } + if (tagName.toLowerCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) { + throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.'); + } + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + tagName, opt_attributes, opt_content); }; /** - * Sets the IE matrix transform without replacing other filters - * @private - * @param {!Element} element Element - * @param {string} value The new progid string + * Creates a SafeHtml representing an iframe tag. + * + * By default the sandbox attribute is set to an empty value, which is the most + * secure option, as it confers the iframe the least privileges. If this + * is too restrictive then granting individual privileges is the preferable + * option. Unsetting the attribute entirely is the least secure option and + * should never be done unless it's stricly necessary. + * + * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src + * attribute. If null or undefined src will not be set. + * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute. + * If null or undefined srcdoc will not be set. + * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} + * opt_attributes Mapping from attribute names to their values. Only + * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or + * undefined causes the attribute to be omitted. + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to + * HTML-escape and put inside the tag. Array elements are concatenated. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid tag name, attribute name, or attribute value is + * provided. If opt_attributes contains the src or srcdoc attributes. */ -ol.dom.setIEMatrix_ = function(element, value) { - var filter = element.currentStyle.filter; - var newFilter = - filter.replace(/progid:DXImageTransform.Microsoft.Matrix\(.*?\)/i, value); +goog.html.SafeHtml.createIframe = function( + opt_src, opt_srcdoc, opt_attributes, opt_content) { + var fixedAttributes = {}; + fixedAttributes['src'] = opt_src || null; + fixedAttributes['srcdoc'] = opt_srcdoc || null; + var defaultAttributes = {'sandbox': ''}; + var attributes = goog.html.SafeHtml.combineAttributes( + fixedAttributes, defaultAttributes, opt_attributes); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'iframe', attributes, opt_content); +}; - if (newFilter === filter) { - newFilter = ' ' + value; - } - element.style.filter = newFilter; +/** + * Creates a SafeHtml representing a style tag. The type attribute is set + * to "text/css". + * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>} + * styleSheet Content to put inside the tag. Array elements are + * concatenated. + * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} + * opt_attributes Mapping from attribute names to their values. Only + * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or + * undefined causes the attribute to be omitted. + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. + * @throws {Error} If invalid attribute name or attribute value is provided. If + * opt_attributes contains the type attribute. + */ +goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) { + var fixedAttributes = {'type': 'text/css'}; + var defaultAttributes = {}; + var attributes = goog.html.SafeHtml.combineAttributes( + fixedAttributes, defaultAttributes, opt_attributes); - // Fix to apply filter to absolutely-positioned children element - if (element.currentStyle.zIndex === 'auto') { - element.style.zIndex = 0; - } + var content = ''; + styleSheet = goog.array.concat(styleSheet); + for (var i = 0; i < styleSheet.length; i++) { + content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]); + } + // Convert to SafeHtml so that it's not HTML-escaped. + var htmlContent = goog.html.SafeHtml + .createSafeHtmlSecurityPrivateDoNotAccessOrElse( + content, goog.i18n.bidi.Dir.NEUTRAL); + return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse( + 'style', attributes, htmlContent); }; /** - * @param {!Element} element Element. - * @param {goog.vec.Mat4.Number} transform Matrix. - * @param {number=} opt_precision Precision. - * @param {Element=} opt_translationElement Required for IE7-8 + * @param {string} tagName The tag name. + * @param {string} name The attribute name. + * @param {!goog.html.SafeHtml.AttributeValue_} value The attribute value. + * @return {string} A "name=value" string. + * @throws {Error} If attribute value is unsafe for the given tag and attribute. + * @private */ -ol.dom.transformElement2D = - function(element, transform, opt_precision, opt_translationElement) { - // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer - // matrix3d() - var i; - if (ol.dom.canUseCssTransform3D()) { - var value3D; - - if (goog.isDef(opt_precision)) { - /** @type {Array.<string>} */ - var strings3D = new Array(16); - for (i = 0; i < 16; ++i) { - strings3D[i] = transform[i].toFixed(opt_precision); - } - value3D = strings3D.join(','); - } else { - value3D = transform.join(','); - } - ol.dom.setTransform(element, 'matrix3d(' + value3D + ')'); - } else if (ol.dom.canUseCssTransform()) { - /** @type {Array.<number>} */ - var transform2D = [ - goog.vec.Mat4.getElement(transform, 0, 0), - goog.vec.Mat4.getElement(transform, 1, 0), - goog.vec.Mat4.getElement(transform, 0, 1), - goog.vec.Mat4.getElement(transform, 1, 1), - goog.vec.Mat4.getElement(transform, 0, 3), - goog.vec.Mat4.getElement(transform, 1, 3) - ]; - var value2D; - if (goog.isDef(opt_precision)) { - /** @type {Array.<string>} */ - var strings2D = new Array(6); - for (i = 0; i < 6; ++i) { - strings2D[i] = transform2D[i].toFixed(opt_precision); - } - value2D = strings2D.join(','); +goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) { + // If it's goog.string.Const, allow any valid attribute name. + if (value instanceof goog.string.Const) { + value = goog.string.Const.unwrap(value); + } else if (name.toLowerCase() == 'style') { + value = goog.html.SafeHtml.getStyleValue_(value); + } else if (/^on/i.test(name)) { + // TODO(jakubvrana): Disallow more attributes with a special meaning. + throw Error('Attribute "' + name + + '" requires goog.string.Const value, "' + value + '" given.'); + // URL attributes handled differently accroding to tag. + } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) { + if (value instanceof goog.html.TrustedResourceUrl) { + value = goog.html.TrustedResourceUrl.unwrap(value); + } else if (value instanceof goog.html.SafeUrl) { + value = goog.html.SafeUrl.unwrap(value); } else { - value2D = transform2D.join(','); + // TODO(user): Allow strings and sanitize them automatically, + // so that it's consistent with accepting a map directly for "style". + throw Error('Attribute "' + name + '" on tag "' + tagName + + '" requires goog.html.SafeUrl or goog.string.Const value, "' + + value + '" given.'); } - ol.dom.setTransform(element, 'matrix(' + value2D + ')'); - } else if (ol.dom.BrowserFeature.USE_MS_MATRIX_TRANSFORM) { - var m11 = goog.vec.Mat4.getElement(transform, 0, 0), - m12 = goog.vec.Mat4.getElement(transform, 0, 1), - m21 = goog.vec.Mat4.getElement(transform, 1, 0), - m22 = goog.vec.Mat4.getElement(transform, 1, 1), - dx = goog.vec.Mat4.getElement(transform, 0, 3), - dy = goog.vec.Mat4.getElement(transform, 1, 3); - - // See: http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx - // and: http://extremelysatisfactorytotalitarianism.com/blog/?p=1002 - // @TODO: fix terrible IE bbox rotation issue. - var s = 'progid:DXImageTransform.Microsoft.Matrix('; - s += 'sizingMethod="auto expand"'; - s += ',M11=' + m11.toFixed(opt_precision || 20); - s += ',M12=' + m12.toFixed(opt_precision || 20); - s += ',M21=' + m21.toFixed(opt_precision || 20); - s += ',M22=' + m22.toFixed(opt_precision || 20); - s += ')'; - ol.dom.setIEMatrix_(element, s); - - // scale = m11 = m22 = target resolution [m/px] / current res [m/px] - // dx = (viewport width [px] / 2) * scale - // + (layer.x [m] - view.x [m]) / target resolution [m / px] - // except that we're positioning the child element relative to the - // viewport, not the map. - // dividing by the scale factor isn't the exact correction, but it's - // close enough that you can barely tell unless you're looking for it - dx /= m11; - dy /= m22; - - opt_translationElement.style.left = Math.round(dx) + 'px'; - opt_translationElement.style.top = Math.round(dy) + 'px'; - } else { - element.style.left = - Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px'; - element.style.top = - Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px'; + } - // TODO: Add scaling here. This isn't quite as simple as multiplying - // width/height, because that only changes the container size, not the - // content size. + // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require + // HTML-escaping. + if (value.implementsGoogStringTypedString) { + // Ok to call getTypedStringValue() since there's no reliance on the type + // contract for security here. + value = value.getTypedStringValue(); } -}; -goog.provide('ol.webgl'); -goog.provide('ol.webgl.WebGLContextEventType'); + goog.asserts.assert(goog.isString(value) || goog.isNumber(value), + 'String or number value expected, got ' + + (typeof value) + ' with value: ' + value); + return name + '="' + goog.string.htmlEscape(String(value)) + '"'; +}; /** - * @const + * Gets value allowed in "style" attribute. + * @param {goog.html.SafeHtml.AttributeValue_} value It could be SafeStyle or a + * map which will be passed to goog.html.SafeStyle.create. + * @return {string} Unwrapped value. + * @throws {Error} If string value is given. * @private - * @type {Array.<string>} */ -ol.webgl.CONTEXT_IDS_ = [ - 'experimental-webgl', - 'webgl', - 'webkit-3d', - 'moz-webgl' -]; +goog.html.SafeHtml.getStyleValue_ = function(value) { + if (!goog.isObject(value)) { + throw Error('The "style" attribute requires goog.html.SafeStyle or map ' + + 'of style properties, ' + (typeof value) + ' given: ' + value); + } + if (!(value instanceof goog.html.SafeStyle)) { + // Process the property bag into a style object. + value = goog.html.SafeStyle.create(value); + } + return goog.html.SafeStyle.unwrap(value); +}; /** - * @enum {string} + * Creates a SafeHtml content with known directionality consisting of a tag with + * optional attributes and optional content. + * @param {!goog.i18n.bidi.Dir} dir Directionality. + * @param {string} tagName + * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content + * @return {!goog.html.SafeHtml} The SafeHtml content with the tag. */ -ol.webgl.WebGLContextEventType = { - LOST: 'webglcontextlost', - RESTORED: 'webglcontextrestored' +goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes, + opt_content) { + var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content); + html.dir_ = dir; + return html; }; /** - * @param {HTMLCanvasElement} canvas Canvas. - * @param {Object=} opt_attributes Attributes. - * @return {WebGLRenderingContext} WebGL rendering context. + * Creates a new SafeHtml object by concatenating values. + * @param {...(!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate. + * @return {!goog.html.SafeHtml} */ -ol.webgl.getContext = function(canvas, opt_attributes) { - var context, i, ii = ol.webgl.CONTEXT_IDS_.length; - for (i = 0; i < ii; ++i) { - try { - context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); - if (!goog.isNull(context)) { - return /** @type {!WebGLRenderingContext} */ (context); +goog.html.SafeHtml.concat = function(var_args) { + var dir = goog.i18n.bidi.Dir.NEUTRAL; + var content = ''; + + /** + * @param {!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument + */ + var addArgument = function(argument) { + if (goog.isArray(argument)) { + goog.array.forEach(argument, addArgument); + } else { + var html = goog.html.SafeHtml.htmlEscape(argument); + content += goog.html.SafeHtml.unwrap(html); + var htmlDir = html.getDirection(); + if (dir == goog.i18n.bidi.Dir.NEUTRAL) { + dir = htmlDir; + } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) { + dir = null; } - } catch (e) { } - } - return null; -}; - -goog.provide('ol.has'); + }; -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('ol'); -goog.require('ol.dom'); -goog.require('ol.webgl'); + goog.array.forEach(arguments, addArgument); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + content, dir); +}; /** - * The ratio between physical pixels and device-independent pixels - * (dips) on the device (`window.devicePixelRatio`). - * @const - * @type {number} - * @api stable + * Creates a new SafeHtml object with known directionality by concatenating the + * values. + * @param {!goog.i18n.bidi.Dir} dir Directionality. + * @param {...(!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array + * arguments would be processed recursively. + * @return {!goog.html.SafeHtml} */ -ol.has.DEVICE_PIXEL_RATIO = goog.global.devicePixelRatio || 1; +goog.html.SafeHtml.concatWithDir = function(dir, var_args) { + var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1)); + html.dir_ = dir; + return html; +}; /** - * True if the browser supports ArrayBuffers. + * Type marker for the SafeHtml type, used to implement additional run-time + * type checking. * @const - * @type {boolean} + * @private */ -ol.has.ARRAY_BUFFER = 'ArrayBuffer' in goog.global; +goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; /** - * True if the browser's Canvas implementation implements {get,set}LineDash. - * @type {boolean} + * Package-internal utility method to create SafeHtml instances. + * + * @param {string} html The string to initialize the SafeHtml object with. + * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be + * constructed, or null if unknown. + * @return {!goog.html.SafeHtml} The initialized SafeHtml object. + * @package */ -ol.has.CANVAS_LINE_DASH = false; +goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function( + html, dir) { + var safeHtml = new goog.html.SafeHtml(); + safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html; + safeHtml.dir_ = dir; + return safeHtml; +}; /** - * True if browser supports Canvas. - * @const - * @type {boolean} - * @api stable - */ -ol.has.CANVAS = ol.ENABLE_CANVAS && ( - /** - * @return {boolean} Canvas supported. - */ - function() { - if (!('HTMLCanvasElement' in goog.global)) { - return false; + * Like create() but does not restrict which tags can be constructed. + * + * @param {string} tagName Tag name. Set or validated by caller. + * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes + * @param {(!goog.html.SafeHtml.TextOrHtml_| + * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content + * @return {!goog.html.SafeHtml} + * @throws {Error} If invalid or unsafe attribute name or value is provided. + * @throws {goog.asserts.AssertionError} If content for void tag is provided. + * @package + */ +goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = + function(tagName, opt_attributes, opt_content) { + var dir = null; + var result = '<' + tagName; + + if (opt_attributes) { + for (var name in opt_attributes) { + if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) { + throw Error('Invalid attribute name "' + name + '".'); } - try { - var context = ol.dom.createCanvasContext2D(); - if (goog.isNull(context)) { - return false; - } else { - if (goog.isDef(context.setLineDash)) { - ol.has.CANVAS_LINE_DASH = true; - } - return true; - } - } catch (e) { - return false; + var value = opt_attributes[name]; + if (!goog.isDefAndNotNull(value)) { + continue; } - })(); + result += ' ' + + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value); + } + } + var content = opt_content; + if (!goog.isDef(content)) { + content = []; + } else if (!goog.isArray(content)) { + content = [content]; + } -/** - * Indicates if DeviceOrientation is supported in the user's browser. - * @const - * @type {boolean} - * @api stable - */ -ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in goog.global; + if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) { + goog.asserts.assert(!content.length, + 'Void tag <' + tagName + '> does not allow content.'); + result += '>'; + } else { + var html = goog.html.SafeHtml.concat(content); + result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>'; + dir = html.getDirection(); + } + var dirAttribute = opt_attributes && opt_attributes['dir']; + if (dirAttribute) { + if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) { + // If the tag has the "dir" attribute specified then its direction is + // neutral because it can be safely used in any context. + dir = goog.i18n.bidi.Dir.NEUTRAL; + } else { + dir = null; + } + } -/** - * True if browser supports DOM. - * @const - * @type {boolean} - */ -ol.has.DOM = ol.ENABLE_DOM; + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + result, dir); +}; /** - * Is HTML5 geolocation supported in the current browser? - * @const - * @type {boolean} - * @api stable + * @param {!Object<string, string>} fixedAttributes + * @param {!Object<string, string>} defaultAttributes + * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} + * opt_attributes Optional attributes passed to create*(). + * @return {!Object<string, goog.html.SafeHtml.AttributeValue_>} + * @throws {Error} If opt_attributes contains an attribute with the same name + * as an attribute in fixedAttributes. + * @package */ -ol.has.GEOLOCATION = 'geolocation' in goog.global.navigator; - +goog.html.SafeHtml.combineAttributes = function( + fixedAttributes, defaultAttributes, opt_attributes) { + var combinedAttributes = {}; + var name; -/** - * True if browser supports touch events. - * @const - * @type {boolean} - * @api stable - */ -ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in goog.global; + for (name in fixedAttributes) { + goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); + combinedAttributes[name] = fixedAttributes[name]; + } + for (name in defaultAttributes) { + goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case'); + combinedAttributes[name] = defaultAttributes[name]; + } + for (name in opt_attributes) { + var nameLower = name.toLowerCase(); + if (nameLower in fixedAttributes) { + throw Error('Cannot override "' + nameLower + '" attribute, got "' + + name + '" with value "' + opt_attributes[name] + '"'); + } + if (nameLower in defaultAttributes) { + delete combinedAttributes[nameLower]; + } + combinedAttributes[name] = opt_attributes[name]; + } -/** - * True if browser supports pointer events. - * @const - * @type {boolean} - */ -ol.has.POINTER = 'PointerEvent' in goog.global; + return combinedAttributes; +}; /** - * True if browser supports ms pointer events (IE 10). - * @const - * @type {boolean} + * A SafeHtml instance corresponding to the empty string. + * @const {!goog.html.SafeHtml} */ -ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled); +goog.html.SafeHtml.EMPTY = + goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + '', goog.i18n.bidi.Dir.NEUTRAL); +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * True if browser supports WebGL. - * @const - * @type {boolean} - * @api stable + * @fileoverview Type-safe wrappers for unsafe DOM APIs. + * + * This file provides type-safe wrappers for DOM APIs that can result in + * cross-site scripting (XSS) vulnerabilities, if the API is supplied with + * untrusted (attacker-controlled) input. Instead of plain strings, the type + * safe wrappers consume values of types from the goog.html package whose + * contract promises that values are safe to use in the corresponding context. + * + * Hence, a program that exclusively uses the wrappers in this file (i.e., whose + * only reference to security-sensitive raw DOM APIs are in this file) is + * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo + * correctness of code that produces values of the respective goog.html types, + * and absent code that violates type safety). + * + * For example, assigning to an element's .innerHTML property a string that is + * derived (even partially) from untrusted input typically results in an XSS + * vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value + * of type goog.html.SafeHtml, whose contract states that using its values in a + * HTML context will not result in XSS. Hence a program that is free of direct + * assignments to any element's innerHTML property (with the exception of the + * assignment to .innerHTML in this file) is guaranteed to be free of XSS due to + * assignment of untrusted strings to the innerHTML property. */ -ol.has.WEBGL; - - -(function() { - if (ol.ENABLE_WEBGL) { - var hasWebGL = false; - var textureSize; - var /** @type {Array.<string>} */ extensions = []; - - if ('WebGLRenderingContext' in goog.global) { - try { - var canvas = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement(goog.dom.TagName.CANVAS)); - var gl = ol.webgl.getContext(canvas, { - failIfMajorPerformanceCaveat: true - }); - if (!goog.isNull(gl)) { - hasWebGL = true; - textureSize = /** @type {number} */ - (gl.getParameter(gl.MAX_TEXTURE_SIZE)); - extensions = gl.getSupportedExtensions(); - } - } catch (e) {} - } - ol.has.WEBGL = hasWebGL; - ol.WEBGL_EXTENSIONS = extensions; - ol.WEBGL_MAX_TEXTURE_SIZE = textureSize; - } -})(); -// FIXME Test on Internet Explorer with VBArray +goog.provide('goog.dom.safe'); -goog.provide('ol.binary.Buffer'); -goog.provide('ol.binary.IReader'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeUrl'); -goog.require('goog.asserts'); -goog.require('goog.userAgent'); -goog.require('ol.has'); +/** + * Assigns known-safe HTML to an element's innerHTML property. + * @param {!Element} elem The element whose innerHTML is to be assigned to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.setInnerHtml = function(elem, html) { + elem.innerHTML = goog.html.SafeHtml.unwrap(html); +}; /** - * @constructor - * @param {ArrayBuffer|string} data Data. + * Assigns known-safe HTML to an element's outerHTML property. + * @param {!Element} elem The element whose outerHTML is to be assigned to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. */ -ol.binary.Buffer = function(data) { +goog.dom.safe.setOuterHtml = function(elem, html) { + elem.outerHTML = goog.html.SafeHtml.unwrap(html); +}; - /** - * @private - * @type {ArrayBuffer|string} - */ - this.data_ = data; +/** + * Writes known-safe HTML to a document. + * @param {!Document} doc The document to be written to. + * @param {!goog.html.SafeHtml} html The known-safe HTML to assign. + */ +goog.dom.safe.documentWrite = function(doc, html) { + doc.write(goog.html.SafeHtml.unwrap(html)); }; /** - * @return {ol.binary.IReader} Reader. + * Safely assigns a URL to an anchor element's href property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * anchor's href property. If url is of type string however, it is first + * sanitized using goog.html.SafeUrl.sanitize. + * + * Example usage: + * goog.dom.safe.setAnchorHref(anchorEl, url); + * which is a safe alternative to + * anchorEl.href = url; + * The latter can result in XSS vulnerabilities if url is a + * user-/attacker-controlled value. + * + * @param {!HTMLAnchorElement} anchor The anchor element whose href property + * is to be assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize */ -ol.binary.Buffer.prototype.getReader = function() { - var data = this.data_; - if (ol.has.ARRAY_BUFFER) { - var arrayBuffer; - if (data instanceof ArrayBuffer) { - arrayBuffer = data; - } else if (goog.isString(data)) { - // FIXME check what happens with Unicode - arrayBuffer = new ArrayBuffer(data.length); - var uint8View = new Uint8Array(arrayBuffer); - var i, ii; - for (i = 0, ii = data.length; i < ii; ++i) { - uint8View[i] = data.charCodeAt(i); - } - } else { - goog.asserts.fail(); - return null; - } - return new ol.binary.ArrayBufferReader(arrayBuffer); +goog.dom.safe.setAnchorHref = function(anchor, url) { + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; } else { - goog.asserts.assert(goog.isString(data)); - goog.asserts.assert( - goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10.0')); - return new ol.binary.ArrayReader(new VBArray(data).toArray()); + safeUrl = goog.html.SafeUrl.sanitize(url); } + anchor.href = goog.html.SafeUrl.unwrap(safeUrl); }; - /** - * @interface + * Safely assigns a URL to a Location object's href property. + * + * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to + * loc's href property. If url is of type string however, it is first sanitized + * using goog.html.SafeUrl.sanitize. + * + * Example usage: + * goog.dom.safe.setLocationHref(document.location, redirectUrl); + * which is a safe alternative to + * document.location.href = redirectUrl; + * The latter can result in XSS vulnerabilities if redirectUrl is a + * user-/attacker-controlled value. + * + * @param {!Location} loc The Location object whose href property is to be + * assigned to. + * @param {string|!goog.html.SafeUrl} url The URL to assign. + * @see goog.html.SafeUrl#sanitize */ -ol.binary.IReader = function() {}; +goog.dom.safe.setLocationHref = function(loc, url) { + /** @type {!goog.html.SafeUrl} */ + var safeUrl; + if (url instanceof goog.html.SafeUrl) { + safeUrl = url; + } else { + safeUrl = goog.html.SafeUrl.sanitize(url); + } + loc.href = goog.html.SafeUrl.unwrap(safeUrl); +}; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @return {boolean} At EOF. + * @fileoverview A utility class for representing two-dimensional positions. */ -ol.binary.IReader.prototype.atEOF = function() {}; -/** - * @return {number} Byte. - */ -ol.binary.IReader.prototype.readByte = function() {}; +goog.provide('goog.math.Coordinate'); + +goog.require('goog.math'); /** + * Class for representing coordinates and positions. + * @param {number=} opt_x Left, defaults to 0. + * @param {number=} opt_y Top, defaults to 0. + * @struct * @constructor - * @param {ArrayBuffer} arrayBuffer Array buffer. - * @implements {ol.binary.IReader} */ -ol.binary.ArrayBufferReader = function(arrayBuffer) { - - /** - * @private - * @type {Uint8Array} - */ - this.uint8View_ = new Uint8Array(arrayBuffer); - +goog.math.Coordinate = function(opt_x, opt_y) { /** - * @private + * X-value * @type {number} */ - this.length_ = this.uint8View_.length; + this.x = goog.isDef(opt_x) ? opt_x : 0; /** - * @private + * Y-value * @type {number} */ - this.offset_ = 0; - + this.y = goog.isDef(opt_y) ? opt_y : 0; }; /** - * @inheritDoc + * Returns a new copy of the coordinate. + * @return {!goog.math.Coordinate} A clone of this coordinate. */ -ol.binary.ArrayBufferReader.prototype.atEOF = function() { - return this.offset_ == this.length_; +goog.math.Coordinate.prototype.clone = function() { + return new goog.math.Coordinate(this.x, this.y); }; +if (goog.DEBUG) { + /** + * Returns a nice string representing the coordinate. + * @return {string} In the form (50, 73). + * @override + */ + goog.math.Coordinate.prototype.toString = function() { + return '(' + this.x + ', ' + this.y + ')'; + }; +} + + /** - * @inheritDoc + * Compares coordinates for equality. + * @param {goog.math.Coordinate} a A Coordinate. + * @param {goog.math.Coordinate} b A Coordinate. + * @return {boolean} True iff the coordinates are equal, or if both are null. */ -ol.binary.ArrayBufferReader.prototype.readByte = function() { - if (this.offset_ < this.length_) { - return this.uint8View_[this.offset_++]; - } else { - goog.asserts.fail(); - return 0; +goog.math.Coordinate.equals = function(a, b) { + if (a == b) { + return true; } + if (!a || !b) { + return false; + } + return a.x == b.x && a.y == b.y; }; - /** - * @constructor - * @implements {ol.binary.IReader} - * @param {Array.<number>} array Array. + * Returns the distance between two coordinates. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The distance between {@code a} and {@code b}. */ -ol.binary.ArrayReader = function(array) { +goog.math.Coordinate.distance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return Math.sqrt(dx * dx + dy * dy); +}; - /** - * @private - * @type {Array.<number>} - */ - this.array_ = array; - /** - * @private - * @type {number} - */ - this.length_ = array.length; +/** + * Returns the magnitude of a coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @return {number} The distance between the origin and {@code a}. + */ +goog.math.Coordinate.magnitude = function(a) { + return Math.sqrt(a.x * a.x + a.y * a.y); +}; - /** - * @private - * @type {number} - */ - this.offset_ = 0; +/** + * Returns the angle from the origin to a coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @return {number} The angle, in degrees, clockwise from the positive X + * axis to {@code a}. + */ +goog.math.Coordinate.azimuth = function(a) { + return goog.math.angle(0, 0, a.x, a.y); }; /** - * @inheritDoc + * Returns the squared distance between two coordinates. Squared distances can + * be used for comparisons when the actual value is not required. + * + * Performance note: eliminating the square root is an optimization often used + * in lower-level languages, but the speed difference is not nearly as + * pronounced in JavaScript (only a few percent.) + * + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The squared distance between {@code a} and {@code b}. */ -ol.binary.ArrayReader.prototype.atEOF = function() { - return this.offset_ == this.length_; +goog.math.Coordinate.squaredDistance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return dx * dx + dy * dy; }; /** - * @inheritDoc + * Returns the difference between two coordinates as a new + * goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the difference + * between {@code a} and {@code b}. */ -ol.binary.ArrayReader.prototype.readByte = function() { - if (this.offset_ < this.length_) { - return this.array_[this.offset_++]; - } else { - goog.asserts.fail(); - return 0; - } +goog.math.Coordinate.difference = function(a, b) { + return new goog.math.Coordinate(a.x - b.x, a.y - b.y); }; -goog.provide('ol.CanvasFunctionType'); - /** - * A function returning the canvas element (`{HTMLCanvasElement}`) - * used by the source as an image. The arguments passed to the function are: - * {@link ol.Extent} the image extent, `{number}` the image resolution, - * `{number}` the device pixel ratio, {@link ol.Size} the image size, and - * {@link ol.proj.Projection} the image projection. The canvas returned by - * this function is cached by the source. The this keyword inside the function - * references the {@link ol.source.ImageCanvas}. - * - * @typedef {function(this:ol.source.ImageCanvas, ol.Extent, number, - * number, ol.Size, ol.proj.Projection): HTMLCanvasElement} - * @api + * Returns the sum of two coordinates as a new goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two + * coordinates. */ -ol.CanvasFunctionType; +goog.math.Coordinate.sum = function(a, b) { + return new goog.math.Coordinate(a.x + b.x, a.y + b.y); +}; + /** - * An implementation of Google Maps' MVCArray. - * @see https://developers.google.com/maps/documentation/javascript/reference + * Rounds the x and y fields to the next larger integer values. + * @return {!goog.math.Coordinate} This coordinate with ceil'd fields. */ +goog.math.Coordinate.prototype.ceil = function() { + this.x = Math.ceil(this.x); + this.y = Math.ceil(this.y); + return this; +}; -goog.provide('ol.Collection'); -goog.provide('ol.CollectionEvent'); -goog.provide('ol.CollectionEventType'); -goog.require('goog.array'); -goog.require('goog.events.Event'); -goog.require('ol.Object'); +/** + * Rounds the x and y fields to the next smaller integer values. + * @return {!goog.math.Coordinate} This coordinate with floored fields. + */ +goog.math.Coordinate.prototype.floor = function() { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + return this; +}; /** - * @enum {string} + * Rounds the x and y fields to the nearest integer values. + * @return {!goog.math.Coordinate} This coordinate with rounded fields. */ -ol.CollectionEventType = { - /** - * Triggered when an item is added to the collection. - * @event ol.CollectionEvent#add - * @api stable - */ - ADD: 'add', - /** - * Triggered when an item is removed from the collection. - * @event ol.CollectionEvent#remove - * @api stable - */ - REMOVE: 'remove' +goog.math.Coordinate.prototype.round = function() { + this.x = Math.round(this.x); + this.y = Math.round(this.y); + return this; }; - /** - * @classdesc - * Events emitted by {@link ol.Collection} instances are instances of this - * type. - * - * @constructor - * @extends {goog.events.Event} - * @implements {oli.CollectionEvent} - * @param {ol.CollectionEventType} type Type. - * @param {*=} opt_element Element. - * @param {Object=} opt_target Target. + * Translates this box by the given offsets. If a {@code goog.math.Coordinate} + * is given, then the x and y values are translated by the coordinate's x and y. + * Otherwise, x and y are translated by {@code tx} and {@code opt_ty} + * respectively. + * @param {number|goog.math.Coordinate} tx The value to translate x by or the + * the coordinate to translate this coordinate by. + * @param {number=} opt_ty The value to translate y by. + * @return {!goog.math.Coordinate} This coordinate after translating. */ -ol.CollectionEvent = function(type, opt_element, opt_target) { +goog.math.Coordinate.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.x += tx.x; + this.y += tx.y; + } else { + this.x += tx; + if (goog.isNumber(opt_ty)) { + this.y += opt_ty; + } + } + return this; +}; - goog.base(this, type, opt_target); - /** - * The element that is added to or removed from the collection. - * @type {*} - * @api stable - */ - this.element = opt_element; +/** + * Scales this coordinate by the given scale factors. The x and y values are + * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} + * is not given, then {@code sx} is used for both x and y. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Coordinate} This coordinate after scaling. + */ +goog.math.Coordinate.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.x *= sx; + this.y *= sy; + return this; +}; + + +/** + * Rotates this coordinate clockwise about the origin (or, optionally, the given + * center) by the given angle, in radians. + * @param {number} radians The angle by which to rotate this coordinate + * clockwise about the given center, in radians. + * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults + * to (0, 0) if not given. + */ +goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) { + var center = opt_center || new goog.math.Coordinate(0, 0); + + var x = this.x; + var y = this.y; + var cos = Math.cos(radians); + var sin = Math.sin(radians); + this.x = (x - center.x) * cos - (y - center.y) * sin + center.x; + this.y = (x - center.x) * sin + (y - center.y) * cos + center.y; }; -goog.inherits(ol.CollectionEvent, goog.events.Event); /** - * @enum {string} + * Rotates this coordinate clockwise about the origin (or, optionally, the given + * center) by the given angle, in degrees. + * @param {number} degrees The angle by which to rotate this coordinate + * clockwise about the given center, in degrees. + * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults + * to (0, 0) if not given. */ -ol.CollectionProperty = { - LENGTH: 'length' +goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) { + this.rotateRadians(goog.math.toRadians(degrees), opt_center); }; +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility class for representing two-dimensional sizes. + * @author brenneman@google.com (Shawn Brenneman) + */ + + +goog.provide('goog.math.Size'); + /** - * @classdesc - * An expanded version of standard JS Array, adding convenience methods for - * manipulation. Add and remove changes to the Collection trigger a Collection - * event. Note that this does not cover changes to the objects _within_ the - * Collection; they trigger events on the appropriate object, not on the - * Collection as a whole. - * - * Because a Collection is itself an {@link ol.Object}, it can be bound to any - * other Object or Collection such that a change in one will automatically be - * reflected in the other. - * + * Class for representing sizes consisting of a width and height. Undefined + * width and height support is deprecated and results in compiler warning. + * @param {number} width Width. + * @param {number} height Height. + * @struct * @constructor - * @extends {ol.Object} - * @fires ol.CollectionEvent - * @param {Array.<T>=} opt_array Array. - * @template T - * @api stable */ -ol.Collection = function(opt_array) { - - goog.base(this); +goog.math.Size = function(width, height) { + /** + * Width + * @type {number} + */ + this.width = width; /** - * @private - * @type {Array.<T>} + * Height + * @type {number} */ - this.array_ = opt_array || []; + this.height = height; +}; - this.updateLength_(); +/** + * Compares sizes for equality. + * @param {goog.math.Size} a A Size. + * @param {goog.math.Size} b A Size. + * @return {boolean} True iff the sizes have equal widths and equal + * heights, or if both are null. + */ +goog.math.Size.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.width == b.width && a.height == b.height; }; -goog.inherits(ol.Collection, ol.Object); /** - * Remove all elements from the collection. - * @api stable + * @return {!goog.math.Size} A new copy of the Size. */ -ol.Collection.prototype.clear = function() { - while (this.getLength() > 0) { - this.pop(); - } +goog.math.Size.prototype.clone = function() { + return new goog.math.Size(this.width, this.height); }; +if (goog.DEBUG) { + /** + * Returns a nice string representing size. + * @return {string} In the form (50 x 73). + * @override + */ + goog.math.Size.prototype.toString = function() { + return '(' + this.width + ' x ' + this.height + ')'; + }; +} + + /** - * @param {Array.<T>} arr Array. - * @return {ol.Collection.<T>} This collection. - * @api stable + * @return {number} The longer of the two dimensions in the size. */ -ol.Collection.prototype.extend = function(arr) { - var i, ii; - for (i = 0, ii = arr.length; i < ii; ++i) { - this.push(arr[i]); - } - return this; +goog.math.Size.prototype.getLongest = function() { + return Math.max(this.width, this.height); }; /** - * Iterate over each element, calling the provided callback. - * @param {function(this: S, T, number, Array.<T>): *} f The function to call - * for every element. This function takes 3 arguments (the element, the - * index and the array). The return value is ignored. - * @param {S=} opt_this The object to use as `this` in `f`. - * @template S - * @api stable + * @return {number} The shorter of the two dimensions in the size. */ -ol.Collection.prototype.forEach = function(f, opt_this) { - goog.array.forEach(this.array_, f, opt_this); +goog.math.Size.prototype.getShortest = function() { + return Math.min(this.width, this.height); }; /** - * Get a reference to the underlying Array object. Warning: if the array - * is mutated, no events will be dispatched by the collection, and the - * collection's "length" property won't be in sync with the actual length - * of the array. - * @return {Array.<T>} Array. - * @api stable + * @return {number} The area of the size (width * height). */ -ol.Collection.prototype.getArray = function() { - return this.array_; +goog.math.Size.prototype.area = function() { + return this.width * this.height; }; /** - * Get the element at the provided index. - * @param {number} index Index. - * @return {T} Element. - * @api stable + * @return {number} The perimeter of the size (width + height) * 2. */ -ol.Collection.prototype.item = function(index) { - return this.array_[index]; +goog.math.Size.prototype.perimeter = function() { + return (this.width + this.height) * 2; }; /** - * Get the length of this collection. - * @return {number} The length of the array. - * @observable - * @api stable + * @return {number} The ratio of the size's width to its height. */ -ol.Collection.prototype.getLength = function() { - return /** @type {number} */ (this.get(ol.CollectionProperty.LENGTH)); +goog.math.Size.prototype.aspectRatio = function() { + return this.width / this.height; }; /** - * Insert an element at the provided index. - * @param {number} index Index. - * @param {T} elem Element. - * @api stable + * @return {boolean} True if the size has zero area, false if both dimensions + * are non-zero numbers. */ -ol.Collection.prototype.insertAt = function(index, elem) { - goog.array.insertAt(this.array_, elem, index); - this.updateLength_(); - this.dispatchEvent( - new ol.CollectionEvent(ol.CollectionEventType.ADD, elem, this)); +goog.math.Size.prototype.isEmpty = function() { + return !this.area(); }; /** - * Remove the last element of the collection and return it. - * Return `undefined` if the collection is empty. - * @return {T|undefined} Element. - * @api stable + * Clamps the width and height parameters upward to integer values. + * @return {!goog.math.Size} This size with ceil'd components. */ -ol.Collection.prototype.pop = function() { - return this.removeAt(this.getLength() - 1); +goog.math.Size.prototype.ceil = function() { + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this; }; /** - * Insert the provided element at the end of the collection. - * @param {T} elem Element. - * @return {number} Length. - * @api stable + * @param {!goog.math.Size} target The target size. + * @return {boolean} True if this Size is the same size or smaller than the + * target size in both dimensions. */ -ol.Collection.prototype.push = function(elem) { - var n = this.array_.length; - this.insertAt(n, elem); - return n; +goog.math.Size.prototype.fitsInside = function(target) { + return this.width <= target.width && this.height <= target.height; }; /** - * Removes the first occurence of elem from the collection. - * @param {T} elem Element. - * @return {T|undefined} The removed element or undefined if elem was not found. - * @api stable + * Clamps the width and height parameters downward to integer values. + * @return {!goog.math.Size} This size with floored components. */ -ol.Collection.prototype.remove = function(elem) { - var arr = this.array_; - var i, ii; - for (i = 0, ii = arr.length; i < ii; ++i) { - if (arr[i] === elem) { - return this.removeAt(i); - } - } - return undefined; +goog.math.Size.prototype.floor = function() { + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this; }; /** - * Remove the element at the provided index and return it. - * Return `undefined` if the collection does not contain this index. - * @param {number} index Index. - * @return {T|undefined} Value. - * @api stable + * Rounds the width and height parameters to integer values. + * @return {!goog.math.Size} This size with rounded components. */ -ol.Collection.prototype.removeAt = function(index) { - var prev = this.array_[index]; - goog.array.removeAt(this.array_, index); - this.updateLength_(); - this.dispatchEvent( - new ol.CollectionEvent(ol.CollectionEventType.REMOVE, prev, this)); - return prev; +goog.math.Size.prototype.round = function() { + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this; }; /** - * Set the element at the provided index. - * @param {number} index Index. - * @param {T} elem Element. - * @api stable + * Scales this size by the given scale factors. The width and height are scaled + * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not + * given, then {@code sx} is used for both the width and height. + * @param {number} sx The scale factor to use for the width. + * @param {number=} opt_sy The scale factor to use for the height. + * @return {!goog.math.Size} This Size object after scaling. */ -ol.Collection.prototype.setAt = function(index, elem) { - var n = this.getLength(); - if (index < n) { - var prev = this.array_[index]; - this.array_[index] = elem; - this.dispatchEvent( - new ol.CollectionEvent(ol.CollectionEventType.REMOVE, prev, this)); - this.dispatchEvent( - new ol.CollectionEvent(ol.CollectionEventType.ADD, elem, this)); - } else { - var j; - for (j = n; j < index; ++j) { - this.insertAt(j, undefined); - } - this.insertAt(index, elem); - } +goog.math.Size.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.width *= sx; + this.height *= sy; + return this; }; /** - * @private + * Uniformly scales the size to fit inside the dimensions of a given size. The + * original aspect ratio will be preserved. + * + * This function assumes that both Sizes contain strictly positive dimensions. + * @param {!goog.math.Size} target The target size. + * @return {!goog.math.Size} This Size object, after optional scaling. */ -ol.Collection.prototype.updateLength_ = function() { - this.set(ol.CollectionProperty.LENGTH, this.array_.length); +goog.math.Size.prototype.scaleToFit = function(target) { + var s = this.aspectRatio() > target.aspectRatio() ? + target.width / this.width : + target.height / this.height; + + return this.scale(s); }; // Copyright 2006 The Closure Library Authors. All Rights Reserved. @@ -28454,11104 +29543,10673 @@ ol.Collection.prototype.updateLength_ = function() { // limitations under the License. /** - * @fileoverview Names of standard colors with their associated hex values. + * @fileoverview Utilities for manipulating the browser's Document Object Model + * Inspiration taken *heavily* from mochikit (http://mochikit.com/). + * + * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer + * to a different document object. This is useful if you are working with + * frames or multiple windows. + * + * @author arv@google.com (Erik Arvidsson) */ -goog.provide('goog.color.names'); + +// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem +// is that getTextContent should mimic the DOM3 textContent. We should add a +// getInnerText (or getText) which tries to return the visible text, innerText. + + +goog.provide('goog.dom'); +goog.provide('goog.dom.Appendable'); +goog.provide('goog.dom.DomHelper'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.BrowserFeature'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.safe'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Size'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.string.Unicode'); +goog.require('goog.userAgent'); /** - * A map that contains a lot of colors that are recognised by various browsers. - * This list is way larger than the minimal one dictated by W3C. - * The keys of this map are the lowercase "readable" names of the colors, while - * the values are the "hex" values. + * @define {boolean} Whether we know at compile time that the browser is in + * quirks mode. */ -goog.color.names = { - 'aliceblue': '#f0f8ff', - 'antiquewhite': '#faebd7', - 'aqua': '#00ffff', - 'aquamarine': '#7fffd4', - 'azure': '#f0ffff', - 'beige': '#f5f5dc', - 'bisque': '#ffe4c4', - 'black': '#000000', - 'blanchedalmond': '#ffebcd', - 'blue': '#0000ff', - 'blueviolet': '#8a2be2', - 'brown': '#a52a2a', - 'burlywood': '#deb887', - 'cadetblue': '#5f9ea0', - 'chartreuse': '#7fff00', - 'chocolate': '#d2691e', - 'coral': '#ff7f50', - 'cornflowerblue': '#6495ed', - 'cornsilk': '#fff8dc', - 'crimson': '#dc143c', - 'cyan': '#00ffff', - 'darkblue': '#00008b', - 'darkcyan': '#008b8b', - 'darkgoldenrod': '#b8860b', - 'darkgray': '#a9a9a9', - 'darkgreen': '#006400', - 'darkgrey': '#a9a9a9', - 'darkkhaki': '#bdb76b', - 'darkmagenta': '#8b008b', - 'darkolivegreen': '#556b2f', - 'darkorange': '#ff8c00', - 'darkorchid': '#9932cc', - 'darkred': '#8b0000', - 'darksalmon': '#e9967a', - 'darkseagreen': '#8fbc8f', - 'darkslateblue': '#483d8b', - 'darkslategray': '#2f4f4f', - 'darkslategrey': '#2f4f4f', - 'darkturquoise': '#00ced1', - 'darkviolet': '#9400d3', - 'deeppink': '#ff1493', - 'deepskyblue': '#00bfff', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1e90ff', - 'firebrick': '#b22222', - 'floralwhite': '#fffaf0', - 'forestgreen': '#228b22', - 'fuchsia': '#ff00ff', - 'gainsboro': '#dcdcdc', - 'ghostwhite': '#f8f8ff', - 'gold': '#ffd700', - 'goldenrod': '#daa520', - 'gray': '#808080', - 'green': '#008000', - 'greenyellow': '#adff2f', - 'grey': '#808080', - 'honeydew': '#f0fff0', - 'hotpink': '#ff69b4', - 'indianred': '#cd5c5c', - 'indigo': '#4b0082', - 'ivory': '#fffff0', - 'khaki': '#f0e68c', - 'lavender': '#e6e6fa', - 'lavenderblush': '#fff0f5', - 'lawngreen': '#7cfc00', - 'lemonchiffon': '#fffacd', - 'lightblue': '#add8e6', - 'lightcoral': '#f08080', - 'lightcyan': '#e0ffff', - 'lightgoldenrodyellow': '#fafad2', - 'lightgray': '#d3d3d3', - 'lightgreen': '#90ee90', - 'lightgrey': '#d3d3d3', - 'lightpink': '#ffb6c1', - 'lightsalmon': '#ffa07a', - 'lightseagreen': '#20b2aa', - 'lightskyblue': '#87cefa', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#b0c4de', - 'lightyellow': '#ffffe0', - 'lime': '#00ff00', - 'limegreen': '#32cd32', - 'linen': '#faf0e6', - 'magenta': '#ff00ff', - 'maroon': '#800000', - 'mediumaquamarine': '#66cdaa', - 'mediumblue': '#0000cd', - 'mediumorchid': '#ba55d3', - 'mediumpurple': '#9370db', - 'mediumseagreen': '#3cb371', - 'mediumslateblue': '#7b68ee', - 'mediumspringgreen': '#00fa9a', - 'mediumturquoise': '#48d1cc', - 'mediumvioletred': '#c71585', - 'midnightblue': '#191970', - 'mintcream': '#f5fffa', - 'mistyrose': '#ffe4e1', - 'moccasin': '#ffe4b5', - 'navajowhite': '#ffdead', - 'navy': '#000080', - 'oldlace': '#fdf5e6', - 'olive': '#808000', - 'olivedrab': '#6b8e23', - 'orange': '#ffa500', - 'orangered': '#ff4500', - 'orchid': '#da70d6', - 'palegoldenrod': '#eee8aa', - 'palegreen': '#98fb98', - 'paleturquoise': '#afeeee', - 'palevioletred': '#db7093', - 'papayawhip': '#ffefd5', - 'peachpuff': '#ffdab9', - 'peru': '#cd853f', - 'pink': '#ffc0cb', - 'plum': '#dda0dd', - 'powderblue': '#b0e0e6', - 'purple': '#800080', - 'red': '#ff0000', - 'rosybrown': '#bc8f8f', - 'royalblue': '#4169e1', - 'saddlebrown': '#8b4513', - 'salmon': '#fa8072', - 'sandybrown': '#f4a460', - 'seagreen': '#2e8b57', - 'seashell': '#fff5ee', - 'sienna': '#a0522d', - 'silver': '#c0c0c0', - 'skyblue': '#87ceeb', - 'slateblue': '#6a5acd', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#fffafa', - 'springgreen': '#00ff7f', - 'steelblue': '#4682b4', - 'tan': '#d2b48c', - 'teal': '#008080', - 'thistle': '#d8bfd8', - 'tomato': '#ff6347', - 'turquoise': '#40e0d0', - 'violet': '#ee82ee', - 'wheat': '#f5deb3', - 'white': '#ffffff', - 'whitesmoke': '#f5f5f5', - 'yellow': '#ffff00', - 'yellowgreen': '#9acd32' -}; +goog.define('goog.dom.ASSUME_QUIRKS_MODE', false); -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities related to color and color conversion. + * @define {boolean} Whether we know at compile time that the browser is in + * standards compliance mode. */ - -goog.provide('goog.color'); -goog.provide('goog.color.Hsl'); -goog.provide('goog.color.Hsv'); -goog.provide('goog.color.Rgb'); - -goog.require('goog.color.names'); -goog.require('goog.math'); +goog.define('goog.dom.ASSUME_STANDARDS_MODE', false); /** - * RGB color representation. An array containing three elements [r, g, b], - * each an integer in [0, 255], representing the red, green, and blue components - * of the color respectively. - * @typedef {Array<number>} + * Whether we know the compatibility mode at compile time. + * @type {boolean} + * @private */ -goog.color.Rgb; +goog.dom.COMPAT_MODE_KNOWN_ = + goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE; /** - * HSV color representation. An array containing three elements [h, s, v]: - * h (hue) must be an integer in [0, 360], cyclic. - * s (saturation) must be a number in [0, 1]. - * v (value/brightness) must be an integer in [0, 255]. - * @typedef {Array<number>} + * Gets the DomHelper object for the document where the element resides. + * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this + * element. + * @return {!goog.dom.DomHelper} The DomHelper. */ -goog.color.Hsv; +goog.dom.getDomHelper = function(opt_element) { + return opt_element ? + new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : + (goog.dom.defaultDomHelper_ || + (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper())); +}; /** - * HSL color representation. An array containing three elements [h, s, l]: - * h (hue) must be an integer in [0, 360], cyclic. - * s (saturation) must be a number in [0, 1]. - * l (lightness) must be a number in [0, 1]. - * @typedef {Array<number>} + * Cached default DOM helper. + * @type {goog.dom.DomHelper} + * @private */ -goog.color.Hsl; +goog.dom.defaultDomHelper_; /** - * Parses a color out of a string. - * @param {string} str Color in some format. - * @return {{hex: string, type: string}} 'hex' is a string containing a hex - * representation of the color, 'type' is a string containing the type - * of color format passed in ('hex', 'rgb', 'named'). + * Gets the document object being used by the dom library. + * @return {!Document} Document object. */ -goog.color.parse = function(str) { - var result = {}; - str = String(str); - - var maybeHex = goog.color.prependHashIfNecessaryHelper(str); - if (goog.color.isValidHexColor_(maybeHex)) { - result.hex = goog.color.normalizeHex(maybeHex); - result.type = 'hex'; - return result; - } else { - var rgb = goog.color.isValidRgbColor_(str); - if (rgb.length) { - result.hex = goog.color.rgbArrayToHex(rgb); - result.type = 'rgb'; - return result; - } else if (goog.color.names) { - var hex = goog.color.names[str.toLowerCase()]; - if (hex) { - result.hex = hex; - result.type = 'named'; - return result; - } - } - } - throw Error(str + ' is not a valid color string'); +goog.dom.getDocument = function() { + return document; }; /** - * Determines if the given string can be parsed as a color. - * {@see goog.color.parse}. - * @param {string} str Potential color string. - * @return {boolean} True if str is in a format that can be parsed to a color. + * Gets an element from the current document by element id. + * + * If an Element is passed in, it is returned. + * + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. */ -goog.color.isValidColor = function(str) { - var maybeHex = goog.color.prependHashIfNecessaryHelper(str); - return !!(goog.color.isValidHexColor_(maybeHex) || - goog.color.isValidRgbColor_(str).length || - goog.color.names && goog.color.names[str.toLowerCase()]); +goog.dom.getElement = function(element) { + return goog.dom.getElementHelper_(document, element); }; /** - * Parses red, green, blue components out of a valid rgb color string. - * Throws Error if the color string is invalid. - * @param {string} str RGB representation of a color. - * {@see goog.color.isValidRgbColor_}. - * @return {!goog.color.Rgb} rgb representation of the color. + * Gets an element by id from the given document (if present). + * If an element is given, it is returned. + * @param {!Document} doc + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The resulting element. + * @private */ -goog.color.parseRgb = function(str) { - var rgb = goog.color.isValidRgbColor_(str); - if (!rgb.length) { - throw Error(str + ' is not a valid RGB color'); - } - return rgb; +goog.dom.getElementHelper_ = function(doc, element) { + return goog.isString(element) ? + doc.getElementById(element) : + element; }; /** - * Converts a hex representation of a color to RGB. - * @param {string} hexColor Color to convert. - * @return {string} string of the form 'rgb(R,G,B)' which can be used in - * styles. + * Gets an element by id, asserting that the element is found. + * + * This is used when an element is expected to exist, and should fail with + * an assertion error if it does not (if assertions are enabled). + * + * @param {string} id Element ID. + * @return {!Element} The element with the given ID, if it exists. */ -goog.color.hexToRgbStyle = function(hexColor) { - return goog.color.rgbStyle_(goog.color.hexToRgb(hexColor)); +goog.dom.getRequiredElement = function(id) { + return goog.dom.getRequiredElementHelper_(document, id); }; /** - * Regular expression for extracting the digits in a hex color triplet. - * @type {RegExp} + * Helper function for getRequiredElementHelper functions, both static and + * on DomHelper. Asserts the element with the given id exists. + * @param {!Document} doc + * @param {string} id + * @return {!Element} The element with the given ID, if it exists. * @private */ -goog.color.hexTripletRe_ = /#(.)(.)(.)/; +goog.dom.getRequiredElementHelper_ = function(doc, id) { + // To prevent users passing in Elements as is permitted in getElement(). + goog.asserts.assertString(id); + var element = goog.dom.getElementHelper_(doc, id); + element = goog.asserts.assertElement(element, + 'No element found with id: ' + id); + return element; +}; /** - * Normalize an hex representation of a color - * @param {string} hexColor an hex color string. - * @return {string} hex color in the format '#rrggbb' with all lowercase - * literals. + * Alias for getElement. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + * @deprecated Use {@link goog.dom.getElement} instead. */ -goog.color.normalizeHex = function(hexColor) { - if (!goog.color.isValidHexColor_(hexColor)) { - throw Error("'" + hexColor + "' is not a valid hex color"); - } - if (hexColor.length == 4) { // of the form #RGB - hexColor = hexColor.replace(goog.color.hexTripletRe_, '#$1$1$2$2$3$3'); - } - return hexColor.toLowerCase(); -}; +goog.dom.$ = goog.dom.getElement; /** - * Converts a hex representation of a color to RGB. - * @param {string} hexColor Color to convert. - * @return {!goog.color.Rgb} rgb representation of the color. + * Looks up elements by both tag and class name, using browser native functions + * ({@code querySelectorAll}, {@code getElementsByTagName} or + * {@code getElementsByClassName}) where possible. This function + * is a useful, if limited, way of collecting a list of DOM elements + * with certain characteristics. {@code goog.dom.query} offers a + * more powerful and general solution which allows matching on CSS3 + * selector expressions, but at increased cost in code size. If all you + * need is particular tags belonging to a single class, this function + * is fast and sleek. + * + * Note that tag names are case sensitive in the SVG namespace, and this + * function converts opt_tag to uppercase for comparisons. For queries in the + * SVG namespace you should use querySelector or querySelectorAll instead. + * https://bugzilla.mozilla.org/show_bug.cgi?id=963870 + * https://bugs.webkit.org/show_bug.cgi?id=83438 + * + * @see {goog.dom.query} + * + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). */ -goog.color.hexToRgb = function(hexColor) { - hexColor = goog.color.normalizeHex(hexColor); - var r = parseInt(hexColor.substr(1, 2), 16); - var g = parseInt(hexColor.substr(3, 2), 16); - var b = parseInt(hexColor.substr(5, 2), 16); - - return [r, g, b]; +goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { + return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class, + opt_el); }; /** - * Converts a color from RGB to hex representation. - * @param {number} r Amount of red, int between 0 and 255. - * @param {number} g Amount of green, int between 0 and 255. - * @param {number} b Amount of blue, int between 0 and 255. - * @return {string} hex representation of the color. + * Returns a static, array-like list of the elements with the provided + * className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } The items found with the class name provided. */ -goog.color.rgbToHex = function(r, g, b) { - r = Number(r); - g = Number(g); - b = Number(b); - if (isNaN(r) || r < 0 || r > 255 || - isNaN(g) || g < 0 || g > 255 || - isNaN(b) || b < 0 || b > 255) { - throw Error('"(' + r + ',' + g + ',' + b + '") is not a valid RGB color'); +goog.dom.getElementsByClass = function(className, opt_el) { + var parent = opt_el || document; + if (goog.dom.canUseQuerySelector_(parent)) { + return parent.querySelectorAll('.' + className); } - var hexR = goog.color.prependZeroIfNecessaryHelper(r.toString(16)); - var hexG = goog.color.prependZeroIfNecessaryHelper(g.toString(16)); - var hexB = goog.color.prependZeroIfNecessaryHelper(b.toString(16)); - return '#' + hexR + hexG + hexB; + return goog.dom.getElementsByTagNameAndClass_( + document, '*', className, opt_el); }; /** - * Converts a color from RGB to hex representation. - * @param {goog.color.Rgb} rgb rgb representation of the color. - * @return {string} hex representation of the color. + * Returns the first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {Element|Document=} opt_el Optional element to look in. + * @return {Element} The first item with the class name provided. */ -goog.color.rgbArrayToHex = function(rgb) { - return goog.color.rgbToHex(rgb[0], rgb[1], rgb[2]); +goog.dom.getElementByClass = function(className, opt_el) { + var parent = opt_el || document; + var retVal = null; + if (goog.dom.canUseQuerySelector_(parent)) { + retVal = parent.querySelector('.' + className); + } else { + retVal = goog.dom.getElementsByTagNameAndClass_( + document, '*', className, opt_el)[0]; + } + return retVal || null; }; /** - * Converts a color from RGB color space to HSL color space. - * Modified from {@link http://en.wikipedia.org/wiki/HLS_color_space}. - * @param {number} r Value of red, in [0, 255]. - * @param {number} g Value of green, in [0, 255]. - * @param {number} b Value of blue, in [0, 255]. - * @return {!goog.color.Hsl} hsl representation of the color. + * Ensures an element with the given className exists, and then returns the + * first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {!Element|!Document=} opt_root Optional element or document to look + * in. + * @return {!Element} The first item with the class name provided. + * @throws {goog.asserts.AssertionError} Thrown if no element is found. */ -goog.color.rgbToHsl = function(r, g, b) { - // First must normalize r, g, b to be between 0 and 1. - var normR = r / 255; - var normG = g / 255; - var normB = b / 255; - var max = Math.max(normR, normG, normB); - var min = Math.min(normR, normG, normB); - var h = 0; - var s = 0; - - // Luminosity is the average of the max and min rgb color intensities. - var l = 0.5 * (max + min); - - // The hue and saturation are dependent on which color intensity is the max. - // If max and min are equal, the color is gray and h and s should be 0. - if (max != min) { - if (max == normR) { - h = 60 * (normG - normB) / (max - min); - } else if (max == normG) { - h = 60 * (normB - normR) / (max - min) + 120; - } else if (max == normB) { - h = 60 * (normR - normG) / (max - min) + 240; - } - - if (0 < l && l <= 0.5) { - s = (max - min) / (2 * l); - } else { - s = (max - min) / (2 - 2 * l); - } - } - - // Make sure the hue falls between 0 and 360. - return [Math.round(h + 360) % 360, s, l]; +goog.dom.getRequiredElementByClass = function(className, opt_root) { + var retValue = goog.dom.getElementByClass(className, opt_root); + return goog.asserts.assert(retValue, + 'No element found with className: ' + className); }; /** - * Converts a color from RGB color space to HSL color space. - * @param {goog.color.Rgb} rgb rgb representation of the color. - * @return {!goog.color.Hsl} hsl representation of the color. + * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and + * fast W3C Selectors API. + * @param {!(Element|Document)} parent The parent document object. + * @return {boolean} whether or not we can use parent.querySelector* APIs. + * @private */ -goog.color.rgbArrayToHsl = function(rgb) { - return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]); +goog.dom.canUseQuerySelector_ = function(parent) { + return !!(parent.querySelectorAll && parent.querySelector); }; /** - * Helper for hslToRgb. - * @param {number} v1 Helper variable 1. - * @param {number} v2 Helper variable 2. - * @param {number} vH Helper variable 3. - * @return {number} Appropriate RGB value, given the above. + * Helper for {@code getElementsByTagNameAndClass}. + * @param {!Document} doc The document to get the elements in. + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). * @private */ -goog.color.hueToRgb_ = function(v1, v2, vH) { - if (vH < 0) { - vH += 1; - } else if (vH > 1) { - vH -= 1; - } - if ((6 * vH) < 1) { - return (v1 + (v2 - v1) * 6 * vH); - } else if (2 * vH < 1) { - return v2; - } else if (3 * vH < 2) { - return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6); +goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class, + opt_el) { + var parent = opt_el || doc; + var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : ''; + + if (goog.dom.canUseQuerySelector_(parent) && + (tagName || opt_class)) { + var query = tagName + (opt_class ? '.' + opt_class : ''); + return parent.querySelectorAll(query); } - return v1; -}; + // Use the native getElementsByClassName if available, under the assumption + // that even when the tag name is specified, there will be fewer elements to + // filter through when going by class than by tag name + if (opt_class && parent.getElementsByClassName) { + var els = parent.getElementsByClassName(opt_class); -/** - * Converts a color from HSL color space to RGB color space. - * Modified from {@link http://www.easyrgb.com/math.html} - * @param {number} h Hue, in [0, 360]. - * @param {number} s Saturation, in [0, 1]. - * @param {number} l Luminosity, in [0, 1]. - * @return {!goog.color.Rgb} rgb representation of the color. - */ -goog.color.hslToRgb = function(h, s, l) { - var r = 0; - var g = 0; - var b = 0; - var normH = h / 360; // normalize h to fall in [0, 1] + if (tagName) { + var arrayLike = {}; + var len = 0; - if (s == 0) { - r = g = b = l * 255; - } else { - var temp1 = 0; - var temp2 = 0; - if (l < 0.5) { - temp2 = l * (1 + s); + // Filter for specific tags if requested. + for (var i = 0, el; el = els[i]; i++) { + if (tagName == el.nodeName) { + arrayLike[len++] = el; + } + } + arrayLike.length = len; + + return arrayLike; } else { - temp2 = l + s - (s * l); + return els; } - temp1 = 2 * l - temp2; - r = 255 * goog.color.hueToRgb_(temp1, temp2, normH + (1 / 3)); - g = 255 * goog.color.hueToRgb_(temp1, temp2, normH); - b = 255 * goog.color.hueToRgb_(temp1, temp2, normH - (1 / 3)); } - return [Math.round(r), Math.round(g), Math.round(b)]; -}; - + var els = parent.getElementsByTagName(tagName || '*'); -/** - * Converts a color from HSL color space to RGB color space. - * @param {goog.color.Hsl} hsl hsl representation of the color. - * @return {!goog.color.Rgb} rgb representation of the color. - */ -goog.color.hslArrayToRgb = function(hsl) { - return goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]); + if (opt_class) { + var arrayLike = {}; + var len = 0; + for (var i = 0, el; el = els[i]; i++) { + var className = el.className; + // Check if className has a split function since SVG className does not. + if (typeof className.split == 'function' && + goog.array.contains(className.split(/\s+/), opt_class)) { + arrayLike[len++] = el; + } + } + arrayLike.length = len; + return arrayLike; + } else { + return els; + } }; /** - * Helper for isValidHexColor_. - * @type {RegExp} - * @private + * Alias for {@code getElementsByTagNameAndClass}. + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {Element=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). + * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead. */ -goog.color.validHexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i; +goog.dom.$$ = goog.dom.getElementsByTagNameAndClass; /** - * Checks if a string is a valid hex color. We expect strings of the format - * #RRGGBB (ex: #1b3d5f) or #RGB (ex: #3CA == #33CCAA). - * @param {string} str String to check. - * @return {boolean} Whether the string is a valid hex color. - * @private + * Sets multiple properties on a node. + * @param {Element} element DOM node to set properties on. + * @param {Object} properties Hash of property:value pairs. */ -goog.color.isValidHexColor_ = function(str) { - return goog.color.validHexColorRe_.test(str); +goog.dom.setProperties = function(element, properties) { + goog.object.forEach(properties, function(val, key) { + if (key == 'style') { + element.style.cssText = val; + } else if (key == 'class') { + element.className = val; + } else if (key == 'for') { + element.htmlFor = val; + } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) { + element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val); + } else if (goog.string.startsWith(key, 'aria-') || + goog.string.startsWith(key, 'data-')) { + element.setAttribute(key, val); + } else { + element[key] = val; + } + }); }; /** - * Helper for isNormalizedHexColor_. - * @type {RegExp} - * @private + * Map of attributes that should be set using + * element.setAttribute(key, val) instead of element[key] = val. Used + * by goog.dom.setProperties. + * + * @private {!Object<string, string>} + * @const */ -goog.color.normalizedHexColorRe_ = /^#[0-9a-f]{6}$/; - - -/** - * Checks if a string is a normalized hex color. - * We expect strings of the format #RRGGBB (ex: #1b3d5f) - * using only lowercase letters. - * @param {string} str String to check. - * @return {boolean} Whether the string is a normalized hex color. - * @private - */ -goog.color.isNormalizedHexColor_ = function(str) { - return goog.color.normalizedHexColorRe_.test(str); +goog.dom.DIRECT_ATTRIBUTE_MAP_ = { + 'cellpadding': 'cellPadding', + 'cellspacing': 'cellSpacing', + 'colspan': 'colSpan', + 'frameborder': 'frameBorder', + 'height': 'height', + 'maxlength': 'maxLength', + 'role': 'role', + 'rowspan': 'rowSpan', + 'type': 'type', + 'usemap': 'useMap', + 'valign': 'vAlign', + 'width': 'width' }; /** - * Regular expression for matching and capturing RGB style strings. Helper for - * isValidRgbColor_. - * @type {RegExp} - * @private - */ -goog.color.rgbColorRe_ = - /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i; - - -/** - * Checks if a string is a valid rgb color. We expect strings of the format - * '(r, g, b)', or 'rgb(r, g, b)', where each color component is an int in - * [0, 255]. - * @param {string} str String to check. - * @return {!goog.color.Rgb} the rgb representation of the color if it is - * a valid color, or the empty array otherwise. - * @private + * Gets the dimensions of the viewport. + * + * Gecko Standards mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of body element. + * + * docEl.clientHeight Height of viewport excluding scrollbar. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of document. + * + * Gecko Backwards compatible mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight Height of document. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of viewport excluding scrollbar. + * + * IE6/7 Standards mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Undefined. + * body.clientWidth Width of body element. + * + * docEl.clientHeight Height of viewport excluding scrollbar. + * win.innerHeight Undefined. + * body.clientHeight Height of document element. + * + * IE5 + IE6/7 Backwards compatible mode: + * docEl.clientWidth 0. + * win.innerWidth Undefined. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight 0. + * win.innerHeight Undefined. + * body.clientHeight Height of viewport excluding scrollbar. + * + * Opera 9 Standards and backwards compatible mode: + * docEl.clientWidth Width of viewport excluding scrollbar. + * win.innerWidth Width of viewport including scrollbar. + * body.clientWidth Width of viewport excluding scrollbar. + * + * docEl.clientHeight Height of document. + * win.innerHeight Height of viewport including scrollbar. + * body.clientHeight Height of viewport excluding scrollbar. + * + * WebKit: + * Safari 2 + * docEl.clientHeight Same as scrollHeight. + * docEl.clientWidth Same as innerWidth. + * win.innerWidth Width of viewport excluding scrollbar. + * win.innerHeight Height of the viewport including scrollbar. + * frame.innerHeight Height of the viewport exluding scrollbar. + * + * Safari 3 (tested in 522) + * + * docEl.clientWidth Width of viewport excluding scrollbar. + * docEl.clientHeight Height of viewport excluding scrollbar in strict mode. + * body.clientHeight Height of viewport excluding scrollbar in quirks mode. + * + * @param {Window=} opt_window Optional window element to test. + * @return {!goog.math.Size} Object with values 'width' and 'height'. */ -goog.color.isValidRgbColor_ = function(str) { - // Each component is separate (rather than using a repeater) so we can - // capture the match. Also, we explicitly set each component to be either 0, - // or start with a non-zero, to prevent octal numbers from slipping through. - var regExpResultArray = str.match(goog.color.rgbColorRe_); - if (regExpResultArray) { - var r = Number(regExpResultArray[1]); - var g = Number(regExpResultArray[2]); - var b = Number(regExpResultArray[3]); - if (r >= 0 && r <= 255 && - g >= 0 && g <= 255 && - b >= 0 && b <= 255) { - return [r, g, b]; - } - } - return []; +goog.dom.getViewportSize = function(opt_window) { + // TODO(arv): This should not take an argument + return goog.dom.getViewportSize_(opt_window || window); }; /** - * Takes a hex value and prepends a zero if it's a single digit. - * Small helper method for use by goog.color and friends. - * @param {string} hex Hex value to prepend if single digit. - * @return {string} hex value prepended with zero if it was single digit, - * otherwise the same value that was passed in. + * Helper for {@code getViewportSize}. + * @param {Window} win The window to get the view port size for. + * @return {!goog.math.Size} Object with values 'width' and 'height'. + * @private */ -goog.color.prependZeroIfNecessaryHelper = function(hex) { - return hex.length == 1 ? '0' + hex : hex; +goog.dom.getViewportSize_ = function(win) { + var doc = win.document; + var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body; + return new goog.math.Size(el.clientWidth, el.clientHeight); }; /** - * Takes a string a prepends a '#' sign if one doesn't exist. - * Small helper method for use by goog.color and friends. - * @param {string} str String to check. - * @return {string} The value passed in, prepended with a '#' if it didn't - * already have one. + * Calculates the height of the document. + * + * @return {number} The height of the current document. */ -goog.color.prependHashIfNecessaryHelper = function(str) { - return str.charAt(0) == '#' ? str : '#' + str; +goog.dom.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(window); }; /** - * Takes an array of [r, g, b] and converts it into a string appropriate for - * CSS styles. - * @param {goog.color.Rgb} rgb rgb representation of the color. - * @return {string} string of the form 'rgb(r,g,b)'. + * Calculates the height of the document of the given window. + * + * Function code copied from the opensocial gadget api: + * gadgets.window.adjustHeight(opt_height) + * * @private + * @param {!Window} win The window whose document height to retrieve. + * @return {number} The height of the document of the given window. */ -goog.color.rgbStyle_ = function(rgb) { - return 'rgb(' + rgb.join(',') + ')'; -}; +goog.dom.getDocumentHeight_ = function(win) { + // NOTE(eae): This method will return the window size rather than the document + // size in webkit quirks mode. + var doc = win.document; + var height = 0; + if (doc) { + // Calculating inner content height is hard and different between + // browsers rendering in Strict vs. Quirks mode. We use a combination of + // three properties within document.body and document.documentElement: + // - scrollHeight + // - offsetHeight + // - clientHeight + // These values differ significantly between browsers and rendering modes. + // But there are patterns. It just takes a lot of time and persistence + // to figure out. -/** - * Converts an HSV triplet to an RGB array. V is brightness because b is - * reserved for blue in RGB. - * @param {number} h Hue value in [0, 360]. - * @param {number} s Saturation value in [0, 1]. - * @param {number} brightness brightness in [0, 255]. - * @return {!goog.color.Rgb} rgb representation of the color. - */ -goog.color.hsvToRgb = function(h, s, brightness) { - var red = 0; - var green = 0; - var blue = 0; - if (s == 0) { - red = brightness; - green = brightness; - blue = brightness; - } else { - var sextant = Math.floor(h / 60); - var remainder = (h / 60) - sextant; - var val1 = brightness * (1 - s); - var val2 = brightness * (1 - (s * remainder)); - var val3 = brightness * (1 - (s * (1 - remainder))); - switch (sextant) { - case 1: - red = val2; - green = brightness; - blue = val1; - break; - case 2: - red = val1; - green = brightness; - blue = val3; - break; - case 3: - red = val1; - green = val2; - blue = brightness; - break; - case 4: - red = val3; - green = val1; - blue = brightness; - break; - case 5: - red = brightness; - green = val1; - blue = val2; - break; - case 6: - case 0: - red = brightness; - green = val3; - blue = val1; - break; + var body = doc.body; + var docEl = doc.documentElement; + if (!(docEl && body)) { + return 0; } - } - - return [Math.floor(red), Math.floor(green), Math.floor(blue)]; -}; - - -/** - * Converts from RGB values to an array of HSV values. - * @param {number} red Red value in [0, 255]. - * @param {number} green Green value in [0, 255]. - * @param {number} blue Blue value in [0, 255]. - * @return {!goog.color.Hsv} hsv representation of the color. - */ -goog.color.rgbToHsv = function(red, green, blue) { - - var max = Math.max(Math.max(red, green), blue); - var min = Math.min(Math.min(red, green), blue); - var hue; - var saturation; - var value = max; - if (min == max) { - hue = 0; - saturation = 0; - } else { - var delta = (max - min); - saturation = delta / max; - if (red == max) { - hue = (green - blue) / delta; - } else if (green == max) { - hue = 2 + ((blue - red) / delta); + // Get the height of the viewport + var vh = goog.dom.getViewportSize_(win).height; + if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) { + // In Strict mode: + // The inner content height is contained in either: + // document.documentElement.scrollHeight + // document.documentElement.offsetHeight + // Based on studying the values output by different browsers, + // use the value that's NOT equal to the viewport height found above. + height = docEl.scrollHeight != vh ? + docEl.scrollHeight : docEl.offsetHeight; } else { - hue = 4 + ((red - green) / delta); - } - hue *= 60; - if (hue < 0) { - hue += 360; - } - if (hue > 360) { - hue -= 360; + // In Quirks mode: + // documentElement.clientHeight is equal to documentElement.offsetHeight + // except in IE. In most browsers, document.documentElement can be used + // to calculate the inner content height. + // However, in other browsers (e.g. IE), document.body must be used + // instead. How do we know which one to use? + // If document.documentElement.clientHeight does NOT equal + // document.documentElement.offsetHeight, then use document.body. + var sh = docEl.scrollHeight; + var oh = docEl.offsetHeight; + if (docEl.clientHeight != oh) { + sh = body.scrollHeight; + oh = body.offsetHeight; + } + + // Detect whether the inner content height is bigger or smaller + // than the bounding box (viewport). If bigger, take the larger + // value. If smaller, take the smaller value. + if (sh > vh) { + // Content is larger + height = sh > oh ? sh : oh; + } else { + // Content is smaller + height = sh < oh ? sh : oh; + } } } - return [hue, saturation, value]; + return height; }; /** - * Converts from an array of RGB values to an array of HSV values. - * @param {goog.color.Rgb} rgb rgb representation of the color. - * @return {!goog.color.Hsv} hsv representation of the color. + * Gets the page scroll distance as a coordinate object. + * + * @param {Window=} opt_window Optional window element to test. + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @deprecated Use {@link goog.dom.getDocumentScroll} instead. */ -goog.color.rgbArrayToHsv = function(rgb) { - return goog.color.rgbToHsv(rgb[0], rgb[1], rgb[2]); +goog.dom.getPageScroll = function(opt_window) { + var win = opt_window || goog.global || window; + return goog.dom.getDomHelper(win.document).getDocumentScroll(); }; /** - * Converts an HSV triplet to an RGB array. - * @param {goog.color.Hsv} hsv hsv representation of the color. - * @return {!goog.color.Rgb} rgb representation of the color. + * Gets the document scroll distance as a coordinate object. + * + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. */ -goog.color.hsvArrayToRgb = function(hsv) { - return goog.color.hsvToRgb(hsv[0], hsv[1], hsv[2]); +goog.dom.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(document); }; /** - * Converts a hex representation of a color to HSL. - * @param {string} hex Color to convert. - * @return {!goog.color.Hsv} hsv representation of the color. + * Helper for {@code getDocumentScroll}. + * + * @param {!Document} doc The document to get the scroll for. + * @return {!goog.math.Coordinate} Object with values 'x' and 'y'. + * @private */ -goog.color.hexToHsl = function(hex) { - var rgb = goog.color.hexToRgb(hex); - return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]); +goog.dom.getDocumentScroll_ = function(doc) { + var el = goog.dom.getDocumentScrollElement_(doc); + var win = goog.dom.getWindow_(doc); + if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') && + win.pageYOffset != el.scrollTop) { + // The keyboard on IE10 touch devices shifts the page using the pageYOffset + // without modifying scrollTop. For this case, we want the body scroll + // offsets. + return new goog.math.Coordinate(el.scrollLeft, el.scrollTop); + } + return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft, + win.pageYOffset || el.scrollTop); }; /** - * Converts from h,s,l values to a hex string - * @param {number} h Hue, in [0, 360]. - * @param {number} s Saturation, in [0, 1]. - * @param {number} l Luminosity, in [0, 1]. - * @return {string} hex representation of the color. + * Gets the document scroll element. + * @return {!Element} Scrolling element. */ -goog.color.hslToHex = function(h, s, l) { - return goog.color.rgbArrayToHex(goog.color.hslToRgb(h, s, l)); +goog.dom.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(document); }; /** - * Converts from an hsl array to a hex string - * @param {goog.color.Hsl} hsl hsl representation of the color. - * @return {string} hex representation of the color. + * Helper for {@code getDocumentScrollElement}. + * @param {!Document} doc The document to get the scroll element for. + * @return {!Element} Scrolling element. + * @private */ -goog.color.hslArrayToHex = function(hsl) { - return goog.color.rgbArrayToHex(goog.color.hslToRgb(hsl[0], hsl[1], hsl[2])); +goog.dom.getDocumentScrollElement_ = function(doc) { + // WebKit needs body.scrollLeft in both quirks mode and strict mode. We also + // default to the documentElement if the document does not have a body (e.g. + // a SVG document). + if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) { + return doc.documentElement; + } + return doc.body || doc.documentElement; }; /** - * Converts a hex representation of a color to HSV - * @param {string} hex Color to convert. - * @return {!goog.color.Hsv} hsv representation of the color. + * Gets the window object associated with the given document. + * + * @param {Document=} opt_doc Document object to get window for. + * @return {!Window} The window associated with the given document. */ -goog.color.hexToHsv = function(hex) { - return goog.color.rgbArrayToHsv(goog.color.hexToRgb(hex)); +goog.dom.getWindow = function(opt_doc) { + // TODO(arv): This should not take an argument. + return opt_doc ? goog.dom.getWindow_(opt_doc) : window; }; /** - * Converts from h,s,v values to a hex string - * @param {number} h Hue, in [0, 360]. - * @param {number} s Saturation, in [0, 1]. - * @param {number} v Value, in [0, 255]. - * @return {string} hex representation of the color. + * Helper for {@code getWindow}. + * + * @param {!Document} doc Document object to get window for. + * @return {!Window} The window associated with the given document. + * @private */ -goog.color.hsvToHex = function(h, s, v) { - return goog.color.rgbArrayToHex(goog.color.hsvToRgb(h, s, v)); +goog.dom.getWindow_ = function(doc) { + return doc.parentWindow || doc.defaultView; }; /** - * Converts from an HSV array to a hex string - * @param {goog.color.Hsv} hsv hsv representation of the color. - * @return {string} hex representation of the color. + * Returns a dom node with a set of attributes. This function accepts varargs + * for subsequent nodes to be added. Subsequent nodes will be added to the + * first node as childNodes. + * + * So: + * <code>createDom('div', null, createDom('p'), createDom('p'));</code> + * would return a div with two child paragraphs + * + * @param {string} tagName Tag to create. + * @param {(Object|Array<string>|string)=} opt_attributes If object, then a map + * of name-value pairs for attributes. If a string, then this is the + * className of the new element. If an array, the elements will be joined + * together as the className of the new element. + * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array or NodeList,i + * its elements will be added as childNodes instead. + * @return {!Element} Reference to a DOM node. */ -goog.color.hsvArrayToHex = function(hsv) { - return goog.color.hsvToHex(hsv[0], hsv[1], hsv[2]); +goog.dom.createDom = function(tagName, opt_attributes, var_args) { + return goog.dom.createDom_(document, arguments); }; /** - * Calculates the Euclidean distance between two color vectors on an HSL sphere. - * A demo of the sphere can be found at: - * http://en.wikipedia.org/wiki/HSL_color_space - * In short, a vector for color (H, S, L) in this system can be expressed as - * (S*L'*cos(2*PI*H), S*L'*sin(2*PI*H), L), where L' = abs(L - 0.5), and we - * simply calculate the 1-2 distance using these coordinates - * @param {goog.color.Hsl} hsl1 First color in hsl representation. - * @param {goog.color.Hsl} hsl2 Second color in hsl representation. - * @return {number} Distance between the two colors, in the range [0, 1]. + * Helper for {@code createDom}. + * @param {!Document} doc The document to create the DOM in. + * @param {!Arguments} args Argument object passed from the callers. See + * {@code goog.dom.createDom} for details. + * @return {!Element} Reference to a DOM node. + * @private */ -goog.color.hslDistance = function(hsl1, hsl2) { - var sl1, sl2; - if (hsl1[2] <= 0.5) { - sl1 = hsl1[1] * hsl1[2]; - } else { - sl1 = hsl1[1] * (1.0 - hsl1[2]); +goog.dom.createDom_ = function(doc, args) { + var tagName = args[0]; + var attributes = args[1]; + + // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/ + // dhtml/reference/properties/name_2.asp + // Also does not allow setting of 'type' attribute on 'input' or 'button'. + if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes && + (attributes.name || attributes.type)) { + var tagNameArr = ['<', tagName]; + if (attributes.name) { + tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), + '"'); + } + if (attributes.type) { + tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), + '"'); + + // Clone attributes map to remove 'type' without mutating the input. + var clone = {}; + goog.object.extend(clone, attributes); + + // JSCompiler can't see how goog.object.extend added this property, + // because it was essentially added by reflection. + // So it needs to be quoted. + delete clone['type']; + + attributes = clone; + } + tagNameArr.push('>'); + tagName = tagNameArr.join(''); } - if (hsl2[2] <= 0.5) { - sl2 = hsl2[1] * hsl2[2]; - } else { - sl2 = hsl2[1] * (1.0 - hsl2[2]); + var element = doc.createElement(tagName); + + if (attributes) { + if (goog.isString(attributes)) { + element.className = attributes; + } else if (goog.isArray(attributes)) { + element.className = attributes.join(' '); + } else { + goog.dom.setProperties(element, attributes); + } } - var h1 = hsl1[0] / 360.0; - var h2 = hsl2[0] / 360.0; - var dh = (h1 - h2) * 2.0 * Math.PI; - return (hsl1[2] - hsl2[2]) * (hsl1[2] - hsl2[2]) + - sl1 * sl1 + sl2 * sl2 - 2 * sl1 * sl2 * Math.cos(dh); + if (args.length > 2) { + goog.dom.append_(doc, element, args, 2); + } + + return element; }; /** - * Blend two colors together, using the specified factor to indicate the weight - * given to the first color - * @param {goog.color.Rgb} rgb1 First color represented in rgb. - * @param {goog.color.Rgb} rgb2 Second color represented in rgb. - * @param {number} factor The weight to be given to rgb1 over rgb2. Values - * should be in the range [0, 1]. If less than 0, factor will be set to 0. - * If greater than 1, factor will be set to 1. - * @return {!goog.color.Rgb} Combined color represented in rgb. + * Appends a node with text or other nodes. + * @param {!Document} doc The document to create new nodes in. + * @param {!Node} parent The node to append nodes to. + * @param {!Arguments} args The values to add. See {@code goog.dom.append}. + * @param {number} startIndex The index of the array to start from. + * @private */ -goog.color.blend = function(rgb1, rgb2, factor) { - factor = goog.math.clamp(factor, 0, 1); +goog.dom.append_ = function(doc, parent, args, startIndex) { + function childHandler(child) { + // TODO(user): More coercion, ala MochiKit? + if (child) { + parent.appendChild(goog.isString(child) ? + doc.createTextNode(child) : child); + } + } - return [ - Math.round(factor * rgb1[0] + (1.0 - factor) * rgb2[0]), - Math.round(factor * rgb1[1] + (1.0 - factor) * rgb2[1]), - Math.round(factor * rgb1[2] + (1.0 - factor) * rgb2[2]) - ]; + for (var i = startIndex; i < args.length; i++) { + var arg = args[i]; + // TODO(attila): Fix isArrayLike to return false for a text node. + if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) { + // If the argument is a node list, not a real array, use a clone, + // because forEach can't be used to mutate a NodeList. + goog.array.forEach(goog.dom.isNodeList(arg) ? + goog.array.toArray(arg) : arg, + childHandler); + } else { + childHandler(arg); + } + } }; /** - * Adds black to the specified color, darkening it - * @param {goog.color.Rgb} rgb rgb representation of the color. - * @param {number} factor Number in the range [0, 1]. 0 will do nothing, while - * 1 will return black. If less than 0, factor will be set to 0. If greater - * than 1, factor will be set to 1. - * @return {!goog.color.Rgb} Combined rgb color. + * Alias for {@code createDom}. + * @param {string} tagName Tag to create. + * @param {(string|Object)=} opt_attributes If object, then a map of name-value + * pairs for attributes. If a string, then this is the className of the new + * element. + * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array, its + * children will be added as childNodes instead. + * @return {!Element} Reference to a DOM node. + * @deprecated Use {@link goog.dom.createDom} instead. */ -goog.color.darken = function(rgb, factor) { - var black = [0, 0, 0]; - return goog.color.blend(black, rgb, factor); -}; +goog.dom.$dom = goog.dom.createDom; /** - * Adds white to the specified color, lightening it - * @param {goog.color.Rgb} rgb rgb representation of the color. - * @param {number} factor Number in the range [0, 1]. 0 will do nothing, while - * 1 will return white. If less than 0, factor will be set to 0. If greater - * than 1, factor will be set to 1. - * @return {!goog.color.Rgb} Combined rgb color. + * Creates a new element. + * @param {string} name Tag name. + * @return {!Element} The new element. */ -goog.color.lighten = function(rgb, factor) { - var white = [255, 255, 255]; - return goog.color.blend(white, rgb, factor); +goog.dom.createElement = function(name) { + return document.createElement(name); }; /** - * Find the "best" (highest-contrast) of the suggested colors for the prime - * color. Uses W3C formula for judging readability and visual accessibility: - * http://www.w3.org/TR/AERT#color-contrast - * @param {goog.color.Rgb} prime Color represented as a rgb array. - * @param {Array<goog.color.Rgb>} suggestions Array of colors, - * each representing a rgb array. - * @return {!goog.color.Rgb} Highest-contrast color represented by an array.. + * Creates a new text node. + * @param {number|string} content Content. + * @return {!Text} The new text node. */ -goog.color.highContrast = function(prime, suggestions) { - var suggestionsWithDiff = []; - for (var i = 0; i < suggestions.length; i++) { - suggestionsWithDiff.push({ - color: suggestions[i], - diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) + - goog.color.colorDiff_(suggestions[i], prime) - }); - } - suggestionsWithDiff.sort(function(a, b) { - return b.diff - a.diff; - }); - return suggestionsWithDiff[0].color; +goog.dom.createTextNode = function(content) { + return document.createTextNode(String(content)); }; /** - * Calculate brightness of a color according to YIQ formula (brightness is Y). - * More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for - * goog.color.highContrast() - * @param {goog.color.Rgb} rgb Color represented by a rgb array. - * @return {number} brightness (Y). - * @private + * Create a table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean=} opt_fillWithNbsp If true, fills table entries with + * {@code goog.string.Unicode.NBSP} characters. + * @return {!Element} The created table. */ -goog.color.yiqBrightness_ = function(rgb) { - return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000); +goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) { + // TODO(user): Return HTMLTableElement, also in prototype function. + // Callers need to be updated to e.g. not assign numbers to table.cellSpacing. + return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp); }; /** - * Calculate difference in brightness of two colors. Helper method for - * goog.color.highContrast() - * @param {goog.color.Rgb} rgb1 Color represented by a rgb array. - * @param {goog.color.Rgb} rgb2 Color represented by a rgb array. - * @return {number} Brightness difference. + * Create a table. + * @param {!Document} doc Document object to use to create the table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean} fillWithNbsp If true, fills table entries with + * {@code goog.string.Unicode.NBSP} characters. + * @return {!HTMLTableElement} The created table. * @private */ -goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) { - return Math.abs(goog.color.yiqBrightness_(rgb1) - - goog.color.yiqBrightness_(rgb2)); +goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) { + var table = /** @type {!HTMLTableElement} */ + (doc.createElement(goog.dom.TagName.TABLE)); + var tbody = table.appendChild(doc.createElement(goog.dom.TagName.TBODY)); + for (var i = 0; i < rows; i++) { + var tr = doc.createElement(goog.dom.TagName.TR); + for (var j = 0; j < columns; j++) { + var td = doc.createElement(goog.dom.TagName.TD); + // IE <= 9 will create a text node if we set text content to the empty + // string, so we avoid doing it unless necessary. This ensures that the + // same DOM tree is returned on all browsers. + if (fillWithNbsp) { + goog.dom.setTextContent(td, goog.string.Unicode.NBSP); + } + tr.appendChild(td); + } + tbody.appendChild(tr); + } + return table; }; /** - * Calculate color difference between two colors. Helper method for - * goog.color.highContrast() - * @param {goog.color.Rgb} rgb1 Color represented by a rgb array. - * @param {goog.color.Rgb} rgb2 Color represented by a rgb array. - * @return {number} Color difference. - * @private + * Converts HTML markup into a node. + * @param {!goog.html.SafeHtml} html The HTML markup to convert. + * @return {!Node} The resulting node. */ -goog.color.colorDiff_ = function(rgb1, rgb2) { - return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) + - Math.abs(rgb1[2] - rgb2[2]); +goog.dom.safeHtmlToNode = function(html) { + return goog.dom.safeHtmlToNode_(document, html); }; -// We can't use goog.color or goog.color.alpha because they interally use a hex -// string representation that encodes each channel in a single byte. This -// causes occasional loss of precision and rounding errors, especially in the -// alpha channel. - -goog.provide('ol.color'); -goog.require('goog.asserts'); -goog.require('goog.color'); -goog.require('goog.color.names'); -goog.require('goog.math'); -goog.require('goog.vec.Mat4'); -goog.require('ol'); +/** + * Helper for {@code safeHtmlToNode}. + * @param {!Document} doc The document. + * @param {!goog.html.SafeHtml} html The HTML markup to convert. + * @return {!Node} The resulting node. + * @private + */ +goog.dom.safeHtmlToNode_ = function(doc, html) { + var tempDiv = doc.createElement('div'); + if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { + goog.dom.safe.setInnerHtml(tempDiv, + goog.html.SafeHtml.concat(goog.html.SafeHtml.create('br'), html)); + tempDiv.removeChild(tempDiv.firstChild); + } else { + goog.dom.safe.setInnerHtml(tempDiv, html); + } + return goog.dom.childrenToNode_(doc, tempDiv); +}; /** - * A color represented as a short array [red, green, blue, alpha]. - * red, green, and blue should be integers in the range 0..255 inclusive. - * alpha should be a float in the range 0..1 inclusive. - * @typedef {Array.<number>} - * @api + * Converts an HTML string into a document fragment. The string must be + * sanitized in order to avoid cross-site scripting. For example + * {@code goog.dom.htmlToDocumentFragment('<img src=x onerror=alert(0)>')} + * triggers an alert in all browsers, even if the returned document fragment + * is thrown away immediately. + * + * @param {string} htmlString The HTML string to convert. + * @return {!Node} The resulting document fragment. */ -ol.Color; +goog.dom.htmlToDocumentFragment = function(htmlString) { + return goog.dom.htmlToDocumentFragment_(document, htmlString); +}; +// TODO(jakubvrana): Merge with {@code safeHtmlToNode_}. /** - * This RegExp matches # followed by 3 or 6 hex digits. - * @const - * @type {RegExp} + * Helper for {@code htmlToDocumentFragment}. + * + * @param {!Document} doc The document. + * @param {string} htmlString The HTML string to convert. + * @return {!Node} The resulting document fragment. * @private */ -ol.color.hexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i; +goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) { + var tempDiv = doc.createElement('div'); + if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) { + tempDiv.innerHTML = '<br>' + htmlString; + tempDiv.removeChild(tempDiv.firstChild); + } else { + tempDiv.innerHTML = htmlString; + } + return goog.dom.childrenToNode_(doc, tempDiv); +}; /** - * @see goog.color.rgbColorRe_ - * @const - * @type {RegExp} + * Helper for {@code htmlToDocumentFragment_}. + * @param {!Document} doc The document. + * @param {!Node} tempDiv The input node. + * @return {!Node} The resulting node. * @private */ -ol.color.rgbColorRe_ = - /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i; +goog.dom.childrenToNode_ = function(doc, tempDiv) { + if (tempDiv.childNodes.length == 1) { + return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild)); + } else { + var fragment = doc.createDocumentFragment(); + while (tempDiv.firstChild) { + fragment.appendChild(tempDiv.firstChild); + } + return fragment; + } +}; /** - * @see goog.color.alpha.rgbaColorRe_ - * @const - * @type {RegExp} - * @private + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @return {boolean} True if in CSS1-compatible mode. */ -ol.color.rgbaColorRe_ = - /^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i; +goog.dom.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(document); +}; /** - * @param {ol.Color} dst Destination. - * @param {ol.Color} src Source. - * @param {ol.Color=} opt_color Color. - * @return {ol.Color} Color. + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @param {!Document} doc The document to check. + * @return {boolean} True if in CSS1-compatible mode. + * @private */ -ol.color.blend = function(dst, src, opt_color) { - // http://en.wikipedia.org/wiki/Alpha_compositing - // FIXME do we need to scale by 255? - var out = goog.isDef(opt_color) ? opt_color : []; - var dstA = dst[3]; - var srcA = dst[3]; - if (dstA == 1) { - out[0] = (src[0] * srcA + dst[0] * (1 - srcA) + 0.5) | 0; - out[1] = (src[1] * srcA + dst[1] * (1 - srcA) + 0.5) | 0; - out[2] = (src[2] * srcA + dst[2] * (1 - srcA) + 0.5) | 0; - out[4] = 1; - } else if (srcA === 0) { - out[0] = dst[0]; - out[1] = dst[1]; - out[2] = dst[2]; - out[3] = dstA; - } else { - var outA = srcA + dstA * (1 - srcA); - if (outA === 0) { - out[0] = 0; - out[1] = 0; - out[2] = 0; - out[3] = 0; - } else { - out[0] = ((src[0] * srcA + dst[0] * dstA * (1 - srcA)) / outA + 0.5) | 0; - out[1] = ((src[1] * srcA + dst[1] * dstA * (1 - srcA)) / outA + 0.5) | 0; - out[2] = ((src[2] * srcA + dst[2] * dstA * (1 - srcA)) / outA + 0.5) | 0; - out[3] = outA; - } +goog.dom.isCss1CompatMode_ = function(doc) { + if (goog.dom.COMPAT_MODE_KNOWN_) { + return goog.dom.ASSUME_STANDARDS_MODE; } - goog.asserts.assert(ol.color.isValid(out)); - return out; + + return doc.compatMode == 'CSS1Compat'; }; /** - * Return the color as an array. This function maintains a cache of calculated - * arrays which means the result should not be modified. - * @param {ol.Color|string} color Color. - * @return {ol.Color} Color. - * @api + * Determines if the given node can contain children, intended to be used for + * HTML generation. + * + * IE natively supports node.canHaveChildren but has inconsistent behavior. + * Prior to IE8 the base tag allows children and in IE9 all nodes return true + * for canHaveChildren. + * + * In practice all non-IE browsers allow you to add children to any node, but + * the behavior is inconsistent: + * + * <pre> + * var a = document.createElement('br'); + * a.appendChild(document.createTextNode('foo')); + * a.appendChild(document.createTextNode('bar')); + * console.log(a.childNodes.length); // 2 + * console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar" + * </pre> + * + * For more information, see: + * http://dev.w3.org/html5/markup/syntax.html#syntax-elements + * + * TODO(user): Rename shouldAllowChildren() ? + * + * @param {Node} node The node to check. + * @return {boolean} Whether the node can contain children. */ -ol.color.asArray = function(color) { - if (goog.isArray(color)) { - return color; - } else { - goog.asserts.assert(goog.isString(color)); - return ol.color.fromString(color); +goog.dom.canHaveChildren = function(node) { + if (node.nodeType != goog.dom.NodeType.ELEMENT) { + return false; + } + switch (node.tagName) { + case goog.dom.TagName.APPLET: + case goog.dom.TagName.AREA: + case goog.dom.TagName.BASE: + case goog.dom.TagName.BR: + case goog.dom.TagName.COL: + case goog.dom.TagName.COMMAND: + case goog.dom.TagName.EMBED: + case goog.dom.TagName.FRAME: + case goog.dom.TagName.HR: + case goog.dom.TagName.IMG: + case goog.dom.TagName.INPUT: + case goog.dom.TagName.IFRAME: + case goog.dom.TagName.ISINDEX: + case goog.dom.TagName.KEYGEN: + case goog.dom.TagName.LINK: + case goog.dom.TagName.NOFRAMES: + case goog.dom.TagName.NOSCRIPT: + case goog.dom.TagName.META: + case goog.dom.TagName.OBJECT: + case goog.dom.TagName.PARAM: + case goog.dom.TagName.SCRIPT: + case goog.dom.TagName.SOURCE: + case goog.dom.TagName.STYLE: + case goog.dom.TagName.TRACK: + case goog.dom.TagName.WBR: + return false; } + return true; }; /** - * Return the color as an rgba string. - * @param {ol.Color|string} color Color. - * @return {string} Rgba string. - * @api + * Appends a child to a node. + * @param {Node} parent Parent. + * @param {Node} child Child. */ -ol.color.asString = function(color) { - if (goog.isString(color)) { - return color; - } else { - goog.asserts.assert(goog.isArray(color)); - return ol.color.toString(color); - } +goog.dom.appendChild = function(parent, child) { + parent.appendChild(child); }; /** - * @param {ol.Color} color1 Color1. - * @param {ol.Color} color2 Color2. - * @return {boolean} Equals. + * Appends a node with text or other nodes. + * @param {!Node} parent The node to append nodes to. + * @param {...goog.dom.Appendable} var_args The things to append to the node. + * If this is a Node it is appended as is. + * If this is a string then a text node is appended. + * If this is an array like object then fields 0 to length - 1 are appended. */ -ol.color.equals = function(color1, color2) { - return color1 === color2 || ( - color1[0] == color2[0] && color1[1] == color2[1] && - color1[2] == color2[2] && color1[3] == color2[3]); +goog.dom.append = function(parent, var_args) { + goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1); }; /** - * @param {string} s String. - * @return {ol.Color} Color. + * Removes all the child nodes on a DOM node. + * @param {Node} node Node to remove children from. */ -ol.color.fromString = ( - /** - * @return {function(string): ol.Color} - */ - function() { +goog.dom.removeChildren = function(node) { + // Note: Iterations over live collections can be slow, this is the fastest + // we could find. The double parenthesis are used to prevent JsCompiler and + // strict warnings. + var child; + while ((child = node.firstChild)) { + node.removeChild(child); + } +}; - // We maintain a small cache of parsed strings. To provide cheap LRU-like - // semantics, whenever the cache grows too large we simply delete an - // arbitrary 25% of the entries. - /** - * @const - * @type {number} - */ - var MAX_CACHE_SIZE = 1024; +/** + * Inserts a new node before an existing reference node (i.e. as the previous + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert before. + */ +goog.dom.insertSiblingBefore = function(newNode, refNode) { + if (refNode.parentNode) { + refNode.parentNode.insertBefore(newNode, refNode); + } +}; - /** - * @type {Object.<string, ol.Color>} - */ - var cache = {}; - /** - * @type {number} - */ - var cacheSize = 0; +/** + * Inserts a new node after an existing reference node (i.e. as the next + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert after. + */ +goog.dom.insertSiblingAfter = function(newNode, refNode) { + if (refNode.parentNode) { + refNode.parentNode.insertBefore(newNode, refNode.nextSibling); + } +}; - return ( - /** - * @param {string} s String. - * @return {ol.Color} Color. - */ - function(s) { - var color; - if (cache.hasOwnProperty(s)) { - color = cache[s]; - } else { - if (cacheSize >= MAX_CACHE_SIZE) { - var i = 0; - var key; - for (key in cache) { - if ((i++ & 3) === 0) { - delete cache[key]; - --cacheSize; - } - } - } - color = ol.color.fromStringInternal_(s); - cache[s] = color; - ++cacheSize; - } - return color; - }); - })(); +/** + * Insert a child at a given index. If index is larger than the number of child + * nodes that the parent currently has, the node is inserted as the last child + * node. + * @param {Element} parent The element into which to insert the child. + * @param {Node} child The element to insert. + * @param {number} index The index at which to insert the new child node. Must + * not be negative. + */ +goog.dom.insertChildAt = function(parent, child, index) { + // Note that if the second argument is null, insertBefore + // will append the child at the end of the list of children. + parent.insertBefore(child, parent.childNodes[index] || null); +}; /** - * @param {string} s String. - * @private - * @return {ol.Color} Color. + * Removes a node from its parent. + * @param {Node} node The node to remove. + * @return {Node} The node removed if removed; else, null. */ -ol.color.fromStringInternal_ = function(s) { +goog.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null; +}; - var isHex = false; - if (ol.ENABLE_NAMED_COLORS && goog.color.names.hasOwnProperty(s)) { - // goog.color.names does not have a type declaration, so add a typecast - s = /** @type {string} */ (goog.color.names[s]); - isHex = true; + +/** + * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no + * parent. + * @param {Node} newNode Node to insert. + * @param {Node} oldNode Node to replace. + */ +goog.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + if (parent) { + parent.replaceChild(newNode, oldNode); } +}; - var r, g, b, a, color, match; - if (isHex || (match = ol.color.hexColorRe_.exec(s))) { // hex - var n = s.length - 1; // number of hex digits - goog.asserts.assert(n == 3 || n == 6); - var d = n == 3 ? 1 : 2; // number of digits per channel - r = parseInt(s.substr(1 + 0 * d, d), 16); - g = parseInt(s.substr(1 + 1 * d, d), 16); - b = parseInt(s.substr(1 + 2 * d, d), 16); - if (d == 1) { - r = (r << 4) + r; - g = (g << 4) + g; - b = (b << 4) + b; + +/** + * Flattens an element. That is, removes it and replace it with its children. + * Does nothing if the element is not in the document. + * @param {Element} element The element to flatten. + * @return {Element|undefined} The original element, detached from the document + * tree, sans children; or undefined, if the element was not in the document + * to begin with. + */ +goog.dom.flattenElement = function(element) { + var child, parent = element.parentNode; + if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) { + // Use IE DOM method (supported by Opera too) if available + if (element.removeNode) { + return /** @type {Element} */ (element.removeNode(false)); + } else { + // Move all children of the original node up one level. + while ((child = element.firstChild)) { + parent.insertBefore(child, element); + } + + // Detach the original element. + return /** @type {Element} */ (goog.dom.removeNode(element)); } - a = 1; - color = [r, g, b, a]; - goog.asserts.assert(ol.color.isValid(color)); - return color; - } else if ((match = ol.color.rgbaColorRe_.exec(s))) { // rgba() - r = Number(match[1]); - g = Number(match[2]); - b = Number(match[3]); - a = Number(match[4]); - color = [r, g, b, a]; - return ol.color.normalize(color, color); - } else if ((match = ol.color.rgbColorRe_.exec(s))) { // rgb() - r = Number(match[1]); - g = Number(match[2]); - b = Number(match[3]); - color = [r, g, b, 1]; - return ol.color.normalize(color, color); - } else { - goog.asserts.fail(s + ' is not a valid color'); } - }; /** - * @param {ol.Color} color Color. - * @return {boolean} Is valid. + * Returns an array containing just the element children of the given element. + * @param {Element} element The element whose element children we want. + * @return {!(Array|NodeList)} An array or array-like list of just the element + * children of the given element. */ -ol.color.isValid = function(color) { - return 0 <= color[0] && color[0] < 256 && - 0 <= color[1] && color[1] < 256 && - 0 <= color[2] && color[2] < 256 && - 0 <= color[3] && color[3] <= 1; +goog.dom.getChildren = function(element) { + // We check if the children attribute is supported for child elements + // since IE8 misuses the attribute by also including comments. + if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE && + element.children != undefined) { + return element.children; + } + // Fall back to manually filtering the element's child nodes. + return goog.array.filter(element.childNodes, function(node) { + return node.nodeType == goog.dom.NodeType.ELEMENT; + }); }; /** - * @param {ol.Color} color Color. - * @param {ol.Color=} opt_color Color. - * @return {ol.Color} Clamped color. + * Returns the first child node that is an element. + * @param {Node} node The node to get the first child element of. + * @return {Element} The first child node of {@code node} that is an element. */ -ol.color.normalize = function(color, opt_color) { - var result = goog.isDef(opt_color) ? opt_color : []; - result[0] = goog.math.clamp((color[0] + 0.5) | 0, 0, 255); - result[1] = goog.math.clamp((color[1] + 0.5) | 0, 0, 255); - result[2] = goog.math.clamp((color[2] + 0.5) | 0, 0, 255); - result[3] = goog.math.clamp(color[3], 0, 1); - return result; +goog.dom.getFirstElementChild = function(node) { + if (node.firstElementChild != undefined) { + return /** @type {!Element} */(node).firstElementChild; + } + return goog.dom.getNextElementNode_(node.firstChild, true); }; /** - * @param {ol.Color} color Color. - * @return {string} String. + * Returns the last child node that is an element. + * @param {Node} node The node to get the last child element of. + * @return {Element} The last child node of {@code node} that is an element. */ -ol.color.toString = function(color) { - var r = color[0]; - if (r != (r | 0)) { - r = (r + 0.5) | 0; - } - var g = color[1]; - if (g != (g | 0)) { - g = (g + 0.5) | 0; - } - var b = color[2]; - if (b != (b | 0)) { - b = (b + 0.5) | 0; +goog.dom.getLastElementChild = function(node) { + if (node.lastElementChild != undefined) { + return /** @type {!Element} */(node).lastElementChild; } - var a = color[3]; - return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; + return goog.dom.getNextElementNode_(node.lastChild, false); }; /** - * @param {!ol.Color} color Color. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {!ol.Color=} opt_color Color. - * @return {ol.Color} Transformed color. + * Returns the first next sibling that is an element. + * @param {Node} node The node to get the next sibling element of. + * @return {Element} The next sibling of {@code node} that is an element. */ -ol.color.transform = function(color, transform, opt_color) { - var result = goog.isDef(opt_color) ? opt_color : []; - result = goog.vec.Mat4.multVec3(transform, color, result); - goog.asserts.assert(goog.isArray(result)); - result[3] = color[3]; - return ol.color.normalize(result, result); +goog.dom.getNextElementSibling = function(node) { + if (node.nextElementSibling != undefined) { + return /** @type {!Element} */(node).nextElementSibling; + } + return goog.dom.getNextElementNode_(node.nextSibling, true); }; /** - * @param {ol.Color|string} color1 Color2. - * @param {ol.Color|string} color2 Color2. - * @return {boolean} Equals. + * Returns the first previous sibling that is an element. + * @param {Node} node The node to get the previous sibling element of. + * @return {Element} The first previous sibling of {@code node} that is + * an element. */ -ol.color.stringOrColorEquals = function(color1, color2) { - if (color1 === color2 || color1 == color2) { - return true; - } - if (goog.isString(color1)) { - color1 = ol.color.fromString(color1); - } - if (goog.isString(color2)) { - color2 = ol.color.fromString(color2); +goog.dom.getPreviousElementSibling = function(node) { + if (node.previousElementSibling != undefined) { + return /** @type {!Element} */(node).previousElementSibling; } - return ol.color.equals(color1, color2); + return goog.dom.getNextElementNode_(node.previousSibling, false); }; -goog.provide('ol.color.Matrix'); -goog.require('goog.vec.Mat4'); +/** + * Returns the first node that is an element in the specified direction, + * starting with {@code node}. + * @param {Node} node The node to get the next element from. + * @param {boolean} forward Whether to look forwards or backwards. + * @return {Element} The first element. + * @private + */ +goog.dom.getNextElementNode_ = function(node, forward) { + while (node && node.nodeType != goog.dom.NodeType.ELEMENT) { + node = forward ? node.nextSibling : node.previousSibling; + } + return /** @type {Element} */ (node); +}; /** - * @constructor + * Returns the next node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The next node in the DOM tree, or null if this was the last + * node. */ -ol.color.Matrix = function() { - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.colorMatrix_ = goog.vec.Mat4.createNumber(); - - /** - * @private - * @type {number|undefined} - */ - this.brightness_ = undefined; +goog.dom.getNextNode = function(node) { + if (!node) { + return null; + } - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.brightnessMatrix_ = goog.vec.Mat4.createNumber(); + if (node.firstChild) { + return node.firstChild; + } - /** - * @private - * @type {number|undefined} - */ - this.contrast_ = undefined; + while (node && !node.nextSibling) { + node = node.parentNode; + } - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.contrastMatrix_ = goog.vec.Mat4.createNumber(); + return node ? node.nextSibling : null; +}; - /** - * @private - * @type {number|undefined} - */ - this.hue_ = undefined; - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.hueMatrix_ = goog.vec.Mat4.createNumber(); +/** + * Returns the previous node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The previous node in the DOM tree, or null if this was the + * first node. + */ +goog.dom.getPreviousNode = function(node) { + if (!node) { + return null; + } - /** - * @private - * @type {number|undefined} - */ - this.saturation_ = undefined; + if (!node.previousSibling) { + return node.parentNode; + } - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.saturationMatrix_ = goog.vec.Mat4.createNumber(); + node = node.previousSibling; + while (node && node.lastChild) { + node = node.lastChild; + } + return node; }; /** - * @param {!goog.vec.Mat4.Number} matrix Matrix. - * @param {number} value Brightness value. - * @return {!goog.vec.Mat4.Number} Matrix. + * Whether the object looks like a DOM node. + * @param {?} obj The object being tested for node likeness. + * @return {boolean} Whether the object looks like a DOM node. */ -ol.color.Matrix.makeBrightness = function(matrix, value) { - goog.vec.Mat4.makeTranslate(matrix, value, value, value); - return matrix; +goog.dom.isNodeLike = function(obj) { + return goog.isObject(obj) && obj.nodeType > 0; }; /** - * @param {!goog.vec.Mat4.Number} matrix Matrix. - * @param {number} value Contrast value. - * @return {!goog.vec.Mat4.Number} Matrix. + * Whether the object looks like an Element. + * @param {?} obj The object being tested for Element likeness. + * @return {boolean} Whether the object looks like an Element. */ -ol.color.Matrix.makeContrast = function(matrix, value) { - goog.vec.Mat4.makeScale(matrix, value, value, value); - var translateValue = (-0.5 * value + 0.5); - goog.vec.Mat4.setColumnValues(matrix, 3, - translateValue, translateValue, translateValue, 1); - return matrix; +goog.dom.isElement = function(obj) { + return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT; }; /** - * @param {!goog.vec.Mat4.Number} matrix Matrix. - * @param {number} value Hue value. - * @return {!goog.vec.Mat4.Number} Matrix. + * Returns true if the specified value is a Window object. This includes the + * global window for HTML pages, and iframe windows. + * @param {?} obj Variable to test. + * @return {boolean} Whether the variable is a window. */ -ol.color.Matrix.makeHue = function(matrix, value) { - var cosHue = Math.cos(value); - var sinHue = Math.sin(value); - var v00 = 0.213 + cosHue * 0.787 - sinHue * 0.213; - var v01 = 0.715 - cosHue * 0.715 - sinHue * 0.715; - var v02 = 0.072 - cosHue * 0.072 + sinHue * 0.928; - var v03 = 0; - var v10 = 0.213 - cosHue * 0.213 + sinHue * 0.143; - var v11 = 0.715 + cosHue * 0.285 + sinHue * 0.140; - var v12 = 0.072 - cosHue * 0.072 - sinHue * 0.283; - var v13 = 0; - var v20 = 0.213 - cosHue * 0.213 - sinHue * 0.787; - var v21 = 0.715 - cosHue * 0.715 + sinHue * 0.715; - var v22 = 0.072 + cosHue * 0.928 + sinHue * 0.072; - var v23 = 0; - var v30 = 0; - var v31 = 0; - var v32 = 0; - var v33 = 1; - goog.vec.Mat4.setFromValues(matrix, - v00, v10, v20, v30, - v01, v11, v21, v31, - v02, v12, v22, v32, - v03, v13, v23, v33); - return matrix; +goog.dom.isWindow = function(obj) { + return goog.isObject(obj) && obj['window'] == obj; }; /** - * @param {!goog.vec.Mat4.Number} matrix Matrix. - * @param {number} value Saturation value. - * @return {!goog.vec.Mat4.Number} Matrix. + * Returns an element's parent, if it's an Element. + * @param {Element} element The DOM element. + * @return {Element} The parent, or null if not an Element. */ -ol.color.Matrix.makeSaturation = function(matrix, value) { - var v00 = 0.213 + 0.787 * value; - var v01 = 0.715 - 0.715 * value; - var v02 = 0.072 - 0.072 * value; - var v03 = 0; - var v10 = 0.213 - 0.213 * value; - var v11 = 0.715 + 0.285 * value; - var v12 = 0.072 - 0.072 * value; - var v13 = 0; - var v20 = 0.213 - 0.213 * value; - var v21 = 0.715 - 0.715 * value; - var v22 = 0.072 + 0.928 * value; - var v23 = 0; - var v30 = 0; - var v31 = 0; - var v32 = 0; - var v33 = 1; - goog.vec.Mat4.setFromValues(matrix, - v00, v10, v20, v30, - v01, v11, v21, v31, - v02, v12, v22, v32, - v03, v13, v23, v33); - return matrix; +goog.dom.getParentElement = function(element) { + var parent; + if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) { + var isIe9 = goog.userAgent.IE && + goog.userAgent.isVersionOrHigher('9') && + !goog.userAgent.isVersionOrHigher('10'); + // SVG elements in IE9 can't use the parentElement property. + // goog.global['SVGElement'] is not defined in IE9 quirks mode. + if (!(isIe9 && goog.global['SVGElement'] && + element instanceof goog.global['SVGElement'])) { + parent = element.parentElement; + if (parent) { + return parent; + } + } + } + parent = element.parentNode; + return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null; }; /** - * @param {number|undefined} brightness Brightness. - * @param {number|undefined} contrast Contrast. - * @param {number|undefined} hue Hue. - * @param {number|undefined} saturation Saturation. - * @return {!goog.vec.Mat4.Number} Matrix. + * Whether a node contains another node. + * @param {Node} parent The node that should contain the other node. + * @param {Node} descendant The node to test presence of. + * @return {boolean} Whether the parent node contains the descendent node. */ -ol.color.Matrix.prototype.getMatrix = function( - brightness, contrast, hue, saturation) { - var colorMatrixDirty = false; - if (goog.isDef(brightness) && brightness !== this.brightness_) { - ol.color.Matrix.makeBrightness(this.brightnessMatrix_, brightness); - this.brightness_ = brightness; - colorMatrixDirty = true; - } - if (goog.isDef(contrast) && contrast !== this.contrast_) { - ol.color.Matrix.makeContrast(this.contrastMatrix_, contrast); - this.contrast_ = contrast; - colorMatrixDirty = true; - } - if (goog.isDef(hue) && hue !== this.hue_) { - ol.color.Matrix.makeHue(this.hueMatrix_, hue); - this.hue_ = hue; - colorMatrixDirty = true; +goog.dom.contains = function(parent, descendant) { + // We use browser specific methods for this if available since it is faster + // that way. + + // IE DOM + if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) { + return parent == descendant || parent.contains(descendant); } - if (goog.isDef(saturation) && saturation !== this.saturation_) { - ol.color.Matrix.makeSaturation(this.saturationMatrix_, saturation); - this.saturation_ = saturation; - colorMatrixDirty = true; + + // W3C DOM Level 3 + if (typeof parent.compareDocumentPosition != 'undefined') { + return parent == descendant || + Boolean(parent.compareDocumentPosition(descendant) & 16); } - if (colorMatrixDirty) { - var colorMatrix = this.colorMatrix_; - goog.vec.Mat4.makeIdentity(colorMatrix); - if (goog.isDef(contrast)) { - goog.vec.Mat4.multMat(colorMatrix, this.contrastMatrix_, colorMatrix); - } - if (goog.isDef(brightness)) { - goog.vec.Mat4.multMat(colorMatrix, this.brightnessMatrix_, colorMatrix); - } - if (goog.isDef(saturation)) { - goog.vec.Mat4.multMat(colorMatrix, this.saturationMatrix_, colorMatrix); - } - if (goog.isDef(hue)) { - goog.vec.Mat4.multMat(colorMatrix, this.hueMatrix_, colorMatrix); - } + + // W3C DOM Level 1 + while (descendant && parent != descendant) { + descendant = descendant.parentNode; } - return this.colorMatrix_; + return descendant == parent; }; -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for detecting, adding and removing classes. Prefer - * this over goog.dom.classes for new code since it attempts to use classList - * (DOMTokenList: http://dom.spec.whatwg.org/#domtokenlist) which is faster - * and requires less code. + * Compares the document order of two nodes, returning 0 if they are the same + * node, a negative number if node1 is before node2, and a positive number if + * node2 is before node1. Note that we compare the order the tags appear in the + * document so in the tree <b><i>text</i></b> the B node is considered to be + * before the I node. * - * Note: these utilities are meant to operate on HTMLElements - * and may have unexpected behavior on elements with differing interfaces - * (such as SVGElements). + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} 0 if the nodes are the same node, a negative number if node1 + * is before node2, and a positive number if node2 is before node1. */ +goog.dom.compareNodeOrder = function(node1, node2) { + // Fall out quickly for equality. + if (node1 == node2) { + return 0; + } + // Use compareDocumentPosition where available + if (node1.compareDocumentPosition) { + // 4 is the bitmask for FOLLOWS. + return node1.compareDocumentPosition(node2) & 2 ? 1 : -1; + } -goog.provide('goog.dom.classlist'); + // Special case for document nodes on IE 7 and 8. + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + if (node1.nodeType == goog.dom.NodeType.DOCUMENT) { + return -1; + } + if (node2.nodeType == goog.dom.NodeType.DOCUMENT) { + return 1; + } + } -goog.require('goog.array'); + // Process in IE using sourceIndex - we check to see if the first node has + // a source index or if its parent has one. + if ('sourceIndex' in node1 || + (node1.parentNode && 'sourceIndex' in node1.parentNode)) { + var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT; + var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT; + if (isElement1 && isElement2) { + return node1.sourceIndex - node2.sourceIndex; + } else { + var parent1 = node1.parentNode; + var parent2 = node2.parentNode; -/** - * Override this define at build-time if you know your target supports it. - * @define {boolean} Whether to use the classList property (DOMTokenList). - */ -goog.define('goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST', false); + if (parent1 == parent2) { + return goog.dom.compareSiblingOrder_(node1, node2); + } + if (!isElement1 && goog.dom.contains(parent1, node2)) { + return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2); + } -/** - * Gets an array-like object of class names on an element. - * @param {Element} element DOM node to get the classes of. - * @return {!goog.array.ArrayLike} Class names on {@code element}. - */ -goog.dom.classlist.get = function(element) { - if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { - return element.classList; + + if (!isElement2 && goog.dom.contains(parent2, node1)) { + return goog.dom.compareParentsDescendantNodeIe_(node2, node1); + } + + return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) - + (isElement2 ? node2.sourceIndex : parent2.sourceIndex); + } } - var className = element.className; - // Some types of elements don't have a className in IE (e.g. iframes). - // Furthermore, in Firefox, className is not a string when the element is - // an SVG element. - return goog.isString(className) && className.match(/\S+/g) || []; + // For Safari, we compare ranges. + var doc = goog.dom.getOwnerDocument(node1); + + var range1, range2; + range1 = doc.createRange(); + range1.selectNode(node1); + range1.collapse(true); + + range2 = doc.createRange(); + range2.selectNode(node2); + range2.collapse(true); + + return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END, + range2); }; /** - * Sets the entire class name of an element. - * @param {Element} element DOM node to set class of. - * @param {string} className Class name(s) to apply to element. + * Utility function to compare the position of two nodes, when + * {@code textNode}'s parent is an ancestor of {@code node}. If this entry + * condition is not met, this function will attempt to reference a null object. + * @param {!Node} textNode The textNode to compare. + * @param {Node} node The node to compare. + * @return {number} -1 if node is before textNode, +1 otherwise. + * @private */ -goog.dom.classlist.set = function(element, className) { - element.className = className; +goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) { + var parent = textNode.parentNode; + if (parent == node) { + // If textNode is a child of node, then node comes first. + return -1; + } + var sibling = node; + while (sibling.parentNode != parent) { + sibling = sibling.parentNode; + } + return goog.dom.compareSiblingOrder_(sibling, textNode); }; /** - * Returns true if an element has a class. This method may throw a DOM - * exception for an invalid or empty class name if DOMTokenList is used. - * @param {Element} element DOM node to test. - * @param {string} className Class name to test for. - * @return {boolean} Whether element has the class. + * Utility function to compare the position of two nodes known to be non-equal + * siblings. + * @param {Node} node1 The first node to compare. + * @param {!Node} node2 The second node to compare. + * @return {number} -1 if node1 is before node2, +1 otherwise. + * @private */ -goog.dom.classlist.contains = function(element, className) { - if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { - return element.classList.contains(className); +goog.dom.compareSiblingOrder_ = function(node1, node2) { + var s = node2; + while ((s = s.previousSibling)) { + if (s == node1) { + // We just found node1 before node2. + return -1; + } } - return goog.array.contains(goog.dom.classlist.get(element), className); + + // Since we didn't find it, node1 must be after node2. + return 1; }; /** - * Adds a class to an element. Does not add multiples of class names. This - * method may throw a DOM exception for an invalid or empty class name if - * DOMTokenList is used. - * @param {Element} element DOM node to add class to. - * @param {string} className Class name to add. + * Find the deepest common ancestor of the given nodes. + * @param {...Node} var_args The nodes to find a common ancestor of. + * @return {Node} The common ancestor of the nodes, or null if there is none. + * null will only be returned if two or more of the nodes are from different + * documents. */ -goog.dom.classlist.add = function(element, className) { - if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { - element.classList.add(className); - return; +goog.dom.findCommonAncestor = function(var_args) { + var i, count = arguments.length; + if (!count) { + return null; + } else if (count == 1) { + return arguments[0]; } - if (!goog.dom.classlist.contains(element, className)) { - // Ensure we add a space if this is not the first class name added. - element.className += element.className.length > 0 ? - (' ' + className) : className; + var paths = []; + var minLength = Infinity; + for (i = 0; i < count; i++) { + // Compute the list of ancestors. + var ancestors = []; + var node = arguments[i]; + while (node) { + ancestors.unshift(node); + node = node.parentNode; + } + + // Save the list for comparison. + paths.push(ancestors); + minLength = Math.min(minLength, ancestors.length); + } + var output = null; + for (i = 0; i < minLength; i++) { + var first = paths[0][i]; + for (var j = 1; j < count; j++) { + if (first != paths[j][i]) { + return output; + } + } + output = first; } + return output; }; /** - * Convenience method to add a number of class names at once. - * @param {Element} element The element to which to add classes. - * @param {goog.array.ArrayLike<string>} classesToAdd An array-like object - * containing a collection of class names to add to the element. - * This method may throw a DOM exception if classesToAdd contains invalid - * or empty class names. + * Returns the owner document for a node. + * @param {Node|Window} node The node to get the document for. + * @return {!Document} The document owning the node. */ -goog.dom.classlist.addAll = function(element, classesToAdd) { - if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { - goog.array.forEach(classesToAdd, function(className) { - goog.dom.classlist.add(element, className); - }); - return; - } +goog.dom.getOwnerDocument = function(node) { + // TODO(nnaze): Update param signature to be non-nullable. + goog.asserts.assert(node, 'Node cannot be null or undefined.'); + return /** @type {!Document} */ ( + node.nodeType == goog.dom.NodeType.DOCUMENT ? node : + node.ownerDocument || node.document); +}; - var classMap = {}; - // Get all current class names into a map. - goog.array.forEach(goog.dom.classlist.get(element), - function(className) { - classMap[className] = true; - }); +/** + * Cross-browser function for getting the document element of a frame or iframe. + * @param {Element} frame Frame element. + * @return {!Document} The frame content document. + */ +goog.dom.getFrameContentDocument = function(frame) { + var doc = frame.contentDocument || frame.contentWindow.document; + return doc; +}; - // Add new class names to the map. - goog.array.forEach(classesToAdd, - function(className) { - classMap[className] = true; - }); - // Flatten the keys of the map into the className. - element.className = ''; - for (var className in classMap) { - element.className += element.className.length > 0 ? - (' ' + className) : className; - } +/** + * Cross-browser function for getting the window of a frame or iframe. + * @param {Element} frame Frame element. + * @return {Window} The window associated with the given frame. + */ +goog.dom.getFrameContentWindow = function(frame) { + return frame.contentWindow || + goog.dom.getWindow(goog.dom.getFrameContentDocument(frame)); }; /** - * Removes a class from an element. This method may throw a DOM exception - * for an invalid or empty class name if DOMTokenList is used. - * @param {Element} element DOM node to remove class from. - * @param {string} className Class name to remove. + * Sets the text content of a node, with cross-browser support. + * @param {Node} node The node to change the text content of. + * @param {string|number} text The value that should replace the node's content. */ -goog.dom.classlist.remove = function(element, className) { - if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { - element.classList.remove(className); - return; - } +goog.dom.setTextContent = function(node, text) { + goog.asserts.assert(node != null, + 'goog.dom.setTextContent expects a non-null value for node'); - if (goog.dom.classlist.contains(element, className)) { - // Filter out the class name. - element.className = goog.array.filter( - goog.dom.classlist.get(element), - function(c) { - return c != className; - }).join(' '); + if ('textContent' in node) { + node.textContent = text; + } else if (node.nodeType == goog.dom.NodeType.TEXT) { + node.data = text; + } else if (node.firstChild && + node.firstChild.nodeType == goog.dom.NodeType.TEXT) { + // If the first child is a text node we just change its data and remove the + // rest of the children. + while (node.lastChild != node.firstChild) { + node.removeChild(node.lastChild); + } + node.firstChild.data = text; + } else { + goog.dom.removeChildren(node); + var doc = goog.dom.getOwnerDocument(node); + node.appendChild(doc.createTextNode(String(text))); } }; /** - * Removes a set of classes from an element. Prefer this call to - * repeatedly calling {@code goog.dom.classlist.remove} if you want to remove - * a large set of class names at once. - * @param {Element} element The element from which to remove classes. - * @param {goog.array.ArrayLike<string>} classesToRemove An array-like object - * containing a collection of class names to remove from the element. - * This method may throw a DOM exception if classesToRemove contains invalid - * or empty class names. + * Gets the outerHTML of a node, which islike innerHTML, except that it + * actually contains the HTML of the node itself. + * @param {Element} element The element to get the HTML of. + * @return {string} The outerHTML of the given element. */ -goog.dom.classlist.removeAll = function(element, classesToRemove) { - if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { - goog.array.forEach(classesToRemove, function(className) { - goog.dom.classlist.remove(element, className); - }); - return; +goog.dom.getOuterHtml = function(element) { + // IE, Opera and WebKit all have outerHTML. + if ('outerHTML' in element) { + return element.outerHTML; + } else { + var doc = goog.dom.getOwnerDocument(element); + var div = doc.createElement('div'); + div.appendChild(element.cloneNode(true)); + return div.innerHTML; } - // Filter out those classes in classesToRemove. - element.className = goog.array.filter( - goog.dom.classlist.get(element), - function(className) { - // If this class is not one we are trying to remove, - // add it to the array of new class names. - return !goog.array.contains(classesToRemove, className); - }).join(' '); }; /** - * Adds or removes a class depending on the enabled argument. This method - * may throw a DOM exception for an invalid or empty class name if DOMTokenList - * is used. - * @param {Element} element DOM node to add or remove the class on. - * @param {string} className Class name to add or remove. - * @param {boolean} enabled Whether to add or remove the class (true adds, - * false removes). + * Finds the first descendant node that matches the filter function, using + * a depth first search. This function offers the most general purpose way + * of finding a matching element. You may also wish to consider + * {@code goog.dom.query} which can express many matching criteria using + * CSS selector expressions. These expressions often result in a more + * compact representation of the desired result. + * @see goog.dom.query + * + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Node|undefined} The found node or undefined if none is found. */ -goog.dom.classlist.enable = function(element, className, enabled) { - if (enabled) { - goog.dom.classlist.add(element, className); - } else { - goog.dom.classlist.remove(element, className); - } +goog.dom.findNode = function(root, p) { + var rv = []; + var found = goog.dom.findNodes_(root, p, rv, true); + return found ? rv[0] : undefined; }; /** - * Adds or removes a set of classes depending on the enabled argument. This - * method may throw a DOM exception for an invalid or empty class name if - * DOMTokenList is used. - * @param {!Element} element DOM node to add or remove the class on. - * @param {goog.array.ArrayLike<string>} classesToEnable An array-like object - * containing a collection of class names to add or remove from the element. - * @param {boolean} enabled Whether to add or remove the classes (true adds, - * false removes). + * Finds all the descendant nodes that match the filter function, using a + * a depth first search. This function offers the most general-purpose way + * of finding a set of matching elements. You may also wish to consider + * {@code goog.dom.query} which can express many matching criteria using + * CSS selector expressions. These expressions often result in a more + * compact representation of the desired result. + + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {!Array<!Node>} The found nodes or an empty array if none are found. */ -goog.dom.classlist.enableAll = function(element, classesToEnable, enabled) { - var f = enabled ? goog.dom.classlist.addAll : - goog.dom.classlist.removeAll; - f(element, classesToEnable); +goog.dom.findNodes = function(root, p) { + var rv = []; + goog.dom.findNodes_(root, p, rv, false); + return rv; }; /** - * Switches a class on an element from one to another without disturbing other - * classes. If the fromClass isn't removed, the toClass won't be added. This - * method may throw a DOM exception if the class names are empty or invalid. - * @param {Element} element DOM node to swap classes on. - * @param {string} fromClass Class to remove. - * @param {string} toClass Class to add. - * @return {boolean} Whether classes were switched. + * Finds the first or all the descendant nodes that match the filter function, + * using a depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @param {!Array<!Node>} rv The found nodes are added to this array. + * @param {boolean} findOne If true we exit after the first found node. + * @return {boolean} Whether the search is complete or not. True in case findOne + * is true and the node is found. False otherwise. + * @private */ -goog.dom.classlist.swap = function(element, fromClass, toClass) { - if (goog.dom.classlist.contains(element, fromClass)) { - goog.dom.classlist.remove(element, fromClass); - goog.dom.classlist.add(element, toClass); - return true; +goog.dom.findNodes_ = function(root, p, rv, findOne) { + if (root != null) { + var child = root.firstChild; + while (child) { + if (p(child)) { + rv.push(child); + if (findOne) { + return true; + } + } + if (goog.dom.findNodes_(child, p, rv, findOne)) { + return true; + } + child = child.nextSibling; + } } return false; }; /** - * Removes a class if an element has it, and adds it the element doesn't have - * it. Won't affect other classes on the node. This method may throw a DOM - * exception if the class name is empty or invalid. - * @param {Element} element DOM node to toggle class on. - * @param {string} className Class to toggle. - * @return {boolean} True if class was added, false if it was removed - * (in other words, whether element has the class after this function has - * been called). + * Map of tags whose content to ignore when calculating text length. + * @private {!Object<string, number>} + * @const */ -goog.dom.classlist.toggle = function(element, className) { - var add = !goog.dom.classlist.contains(element, className); - goog.dom.classlist.enable(element, className, add); - return add; +goog.dom.TAGS_TO_IGNORE_ = { + 'SCRIPT': 1, + 'STYLE': 1, + 'HEAD': 1, + 'IFRAME': 1, + 'OBJECT': 1 }; /** - * Adds and removes a class of an element. Unlike - * {@link goog.dom.classlist.swap}, this method adds the classToAdd regardless - * of whether the classToRemove was present and had been removed. This method - * may throw a DOM exception if the class names are empty or invalid. - * - * @param {Element} element DOM node to swap classes on. - * @param {string} classToRemove Class to remove. - * @param {string} classToAdd Class to add. + * Map of tags which have predefined values with regard to whitespace. + * @private {!Object<string, string>} + * @const */ -goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) { - goog.dom.classlist.remove(element, classToRemove); - goog.dom.classlist.add(element, classToAdd); -}; +goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'}; -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Vendor prefix getters. + * Returns true if the element has a tab index that allows it to receive + * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + * natively support keyboard focus, even if they have no tab index. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a tab index that allows keyboard + * focus. + * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ */ - -goog.provide('goog.dom.vendor'); - -goog.require('goog.string'); -goog.require('goog.userAgent'); +goog.dom.isFocusableTabIndex = function(element) { + return goog.dom.hasSpecifiedTabIndex_(element) && + goog.dom.isTabIndexFocusable_(element); +}; /** - * Returns the JS vendor prefix used in CSS properties. Different vendors - * use different methods of changing the case of the property names. - * - * @return {?string} The JS vendor prefix or null if there is none. + * Enables or disables keyboard focus support on the element via its tab index. + * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true + * (or elements that natively support keyboard focus, like form elements) can + * receive keyboard focus. See http://go/tabindex for more info. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to set or remove a tab index on the element + * that supports keyboard focus. */ -goog.dom.vendor.getVendorJsPrefix = function() { - if (goog.userAgent.WEBKIT) { - return 'Webkit'; - } else if (goog.userAgent.GECKO) { - return 'Moz'; - } else if (goog.userAgent.IE) { - return 'ms'; - } else if (goog.userAgent.OPERA) { - return 'O'; +goog.dom.setFocusableTabIndex = function(element, enable) { + if (enable) { + element.tabIndex = 0; + } else { + // Set tabIndex to -1 first, then remove it. This is a workaround for + // Safari (confirmed in version 4 on Windows). When removing the attribute + // without setting it to -1 first, the element remains keyboard focusable + // despite not having a tabIndex attribute anymore. + element.tabIndex = -1; + element.removeAttribute('tabIndex'); // Must be camelCase! } - - return null; }; /** - * Returns the vendor prefix used in CSS properties. - * - * @return {?string} The vendor prefix or null if there is none. + * Returns true if the element can be focused, i.e. it has a tab index that + * allows it to receive keyboard focus (tabIndex >= 0), or it is an element + * that natively supports keyboard focus. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element allows keyboard focus. */ -goog.dom.vendor.getVendorPrefix = function() { - if (goog.userAgent.WEBKIT) { - return '-webkit'; - } else if (goog.userAgent.GECKO) { - return '-moz'; - } else if (goog.userAgent.IE) { - return '-ms'; - } else if (goog.userAgent.OPERA) { - return '-o'; +goog.dom.isFocusable = function(element) { + var focusable; + // Some elements can have unspecified tab index and still receive focus. + if (goog.dom.nativelySupportsFocus_(element)) { + // Make sure the element is not disabled ... + focusable = !element.disabled && + // ... and if a tab index is specified, it allows focus. + (!goog.dom.hasSpecifiedTabIndex_(element) || + goog.dom.isTabIndexFocusable_(element)); + } else { + focusable = goog.dom.isFocusableTabIndex(element); } - return null; + // IE requires elements to be visible in order to focus them. + return focusable && goog.userAgent.IE ? + goog.dom.hasNonZeroBoundingRect_(element) : focusable; }; /** - * @param {string} propertyName A property name. - * @param {!Object=} opt_object If provided, we verify if the property exists in - * the object. - * @return {?string} A vendor prefixed property name, or null if it does not - * exist. + * Returns true if the element has a specified tab index. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a specified tab index. + * @private */ -goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) { - // We first check for a non-prefixed property, if available. - if (opt_object && propertyName in opt_object) { - return propertyName; - } - var prefix = goog.dom.vendor.getVendorJsPrefix(); - if (prefix) { - prefix = prefix.toLowerCase(); - var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName); - return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ? - prefixedPropertyName : null; - } - return null; +goog.dom.hasSpecifiedTabIndex_ = function(element) { + // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(), + // which returns an object with a 'specified' property if tabIndex is + // specified. This works on other browsers, too. + var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase! + return goog.isDefAndNotNull(attrNode) && attrNode.specified; }; /** - * @param {string} eventType An event type. - * @return {string} A lower-cased vendor prefixed event type. + * Returns true if the element's tab index allows the element to be focused. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element's tab index allows focus. + * @private */ -goog.dom.vendor.getPrefixedEventType = function(eventType) { - var prefix = goog.dom.vendor.getVendorJsPrefix() || ''; - return (prefix + eventType).toLowerCase(); +goog.dom.isTabIndexFocusable_ = function(element) { + var index = element.tabIndex; + // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534. + return goog.isNumber(index) && index >= 0 && index < 32768; }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A utility class for representing a numeric box. + * Returns true if the element is focusable even when tabIndex is not set. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element natively supports focus. + * @private */ +goog.dom.nativelySupportsFocus_ = function(element) { + return element.tagName == goog.dom.TagName.A || + element.tagName == goog.dom.TagName.INPUT || + element.tagName == goog.dom.TagName.TEXTAREA || + element.tagName == goog.dom.TagName.SELECT || + element.tagName == goog.dom.TagName.BUTTON; +}; -goog.provide('goog.math.Box'); - -goog.require('goog.math.Coordinate'); - +/** + * Returns true if the element has a bounding rectangle that would be visible + * (i.e. its width and height are greater than zero). + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a non-zero bounding rectangle. + * @private + */ +goog.dom.hasNonZeroBoundingRect_ = function(element) { + var rect = goog.isFunction(element['getBoundingClientRect']) ? + element.getBoundingClientRect() : + {'height': element.offsetHeight, 'width': element.offsetWidth}; + return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0; +}; /** - * Class for representing a box. A box is specified as a top, right, bottom, - * and left. A box is useful for representing margins and padding. + * Returns the text content of the current node, without markup and invisible + * symbols. New lines are stripped and whitespace is collapsed, + * such that each character would be visible. * - * This class assumes 'screen coordinates': larger Y coordinates are further - * from the top of the screen. + * In browsers that support it, innerText is used. Other browsers attempt to + * simulate it via node traversal. Line breaks are canonicalized in IE. * - * @param {number} top Top. - * @param {number} right Right. - * @param {number} bottom Bottom. - * @param {number} left Left. - * @constructor + * @param {Node} node The node from which we are getting content. + * @return {string} The text content. */ -goog.math.Box = function(top, right, bottom, left) { - /** - * Top - * @type {number} - */ - this.top = top; +goog.dom.getTextContent = function(node) { + var textContent; + // Note(arv): IE9, Opera, and Safari 3 support innerText but they include + // text nodes in script tags. So we revert to use a user agent test here. + if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) { + textContent = goog.string.canonicalizeNewlines(node.innerText); + // Unfortunately .innerText() returns text with ­ symbols + // We need to filter it out and then remove duplicate whitespaces + } else { + var buf = []; + goog.dom.getTextContent_(node, buf, true); + textContent = buf.join(''); + } - /** - * Right - * @type {number} - */ - this.right = right; + // Strip ­ entities. goog.format.insertWordBreaks inserts them in Opera. + textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, ''); + // Strip ​ entities. goog.format.insertWordBreaks inserts them in IE8. + textContent = textContent.replace(/\u200B/g, ''); - /** - * Bottom - * @type {number} - */ - this.bottom = bottom; + // Skip this replacement on old browsers with working innerText, which + // automatically turns into ' ' and / +/ into ' ' when reading + // innerText. + if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) { + textContent = textContent.replace(/ +/g, ' '); + } + if (textContent != ' ') { + textContent = textContent.replace(/^\s*/, ''); + } - /** - * Left - * @type {number} - */ - this.left = left; + return textContent; }; /** - * Creates a Box by bounding a collection of goog.math.Coordinate objects - * @param {...goog.math.Coordinate} var_args Coordinates to be included inside - * the box. - * @return {!goog.math.Box} A Box containing all the specified Coordinates. + * Returns the text content of the current node, without markup. + * + * Unlike {@code getTextContent} this method does not collapse whitespaces + * or normalize lines breaks. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The raw text content. */ -goog.math.Box.boundingBox = function(var_args) { - var box = new goog.math.Box(arguments[0].y, arguments[0].x, - arguments[0].y, arguments[0].x); - for (var i = 1; i < arguments.length; i++) { - var coord = arguments[i]; - box.top = Math.min(box.top, coord.y); - box.right = Math.max(box.right, coord.x); - box.bottom = Math.max(box.bottom, coord.y); - box.left = Math.min(box.left, coord.x); - } - return box; +goog.dom.getRawTextContent = function(node) { + var buf = []; + goog.dom.getTextContent_(node, buf, false); + + return buf.join(''); }; /** - * @return {number} width The width of this Box. + * Recursive support function for text content retrieval. + * + * @param {Node} node The node from which we are getting content. + * @param {Array<string>} buf string buffer. + * @param {boolean} normalizeWhitespace Whether to normalize whitespace. + * @private */ -goog.math.Box.prototype.getWidth = function() { - return this.right - this.left; +goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) { + if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) { + // ignore certain tags + } else if (node.nodeType == goog.dom.NodeType.TEXT) { + if (normalizeWhitespace) { + buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); + } else { + buf.push(node.nodeValue); + } + } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]); + } else { + var child = node.firstChild; + while (child) { + goog.dom.getTextContent_(child, buf, normalizeWhitespace); + child = child.nextSibling; + } + } }; /** - * @return {number} height The height of this Box. + * Returns the text length of the text contained in a node, without markup. This + * is equivalent to the selection length if the node was selected, or the number + * of cursor movements to traverse the node. Images & BRs take one space. New + * lines are ignored. + * + * @param {Node} node The node whose text content length is being calculated. + * @return {number} The length of {@code node}'s text content. */ -goog.math.Box.prototype.getHeight = function() { - return this.bottom - this.top; +goog.dom.getNodeTextLength = function(node) { + return goog.dom.getTextContent(node).length; }; /** - * Creates a copy of the box with the same dimensions. - * @return {!goog.math.Box} A clone of this Box. + * Returns the text offset of a node relative to one of its ancestors. The text + * length is the same as the length calculated by goog.dom.getNodeTextLength. + * + * @param {Node} node The node whose offset is being calculated. + * @param {Node=} opt_offsetParent The node relative to which the offset will + * be calculated. Defaults to the node's owner document's body. + * @return {number} The text offset. */ -goog.math.Box.prototype.clone = function() { - return new goog.math.Box(this.top, this.right, this.bottom, this.left); +goog.dom.getNodeTextOffset = function(node, opt_offsetParent) { + var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body; + var buf = []; + while (node && node != root) { + var cur = node; + while ((cur = cur.previousSibling)) { + buf.unshift(goog.dom.getTextContent(cur)); + } + node = node.parentNode; + } + // Trim left to deal with FF cases when there might be line breaks and empty + // nodes at the front of the text + return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length; }; -if (goog.DEBUG) { - /** - * Returns a nice string representing the box. - * @return {string} In the form (50t, 73r, 24b, 13l). - * @override - */ - goog.math.Box.prototype.toString = function() { - return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' + - this.left + 'l)'; - }; -} - - /** - * Returns whether the box contains a coordinate or another box. - * - * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. - * @return {boolean} Whether the box contains the coordinate or other box. + * Returns the node at a given offset in a parent node. If an object is + * provided for the optional third parameter, the node and the remainder of the + * offset will stored as properties of this object. + * @param {Node} parent The parent node. + * @param {number} offset The offset into the parent node. + * @param {Object=} opt_result Object to be used to store the return value. The + * return value will be stored in the form {node: Node, remainder: number} + * if this object is provided. + * @return {Node} The node at the given offset. */ -goog.math.Box.prototype.contains = function(other) { - return goog.math.Box.contains(this, other); +goog.dom.getNodeAtOffset = function(parent, offset, opt_result) { + var stack = [parent], pos = 0, cur = null; + while (stack.length > 0 && pos < offset) { + cur = stack.pop(); + if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) { + // ignore certain tags + } else if (cur.nodeType == goog.dom.NodeType.TEXT) { + var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' '); + pos += text.length; + } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length; + } else { + for (var i = cur.childNodes.length - 1; i >= 0; i--) { + stack.push(cur.childNodes[i]); + } + } + } + if (goog.isObject(opt_result)) { + opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0; + opt_result.node = cur; + } + + return cur; }; /** - * Expands box with the given margins. - * - * @param {number|goog.math.Box} top Top margin or box with all margins. - * @param {number=} opt_right Right margin. - * @param {number=} opt_bottom Bottom margin. - * @param {number=} opt_left Left margin. - * @return {!goog.math.Box} A reference to this Box. + * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, + * the object must have a numeric length property and an item function (which + * has type 'string' on IE for some reason). + * @param {Object} val Object to test. + * @return {boolean} Whether the object is a NodeList. */ -goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom, - opt_left) { - if (goog.isObject(top)) { - this.top -= top.top; - this.right += top.right; - this.bottom += top.bottom; - this.left -= top.left; - } else { - this.top -= top; - this.right += opt_right; - this.bottom += opt_bottom; - this.left -= opt_left; +goog.dom.isNodeList = function(val) { + // TODO(attila): Now the isNodeList is part of goog.dom we can use + // goog.userAgent to make this simpler. + // A NodeList must have a length property of type 'number' on all platforms. + if (val && typeof val.length == 'number') { + // A NodeList is an object everywhere except Safari, where it's a function. + if (goog.isObject(val)) { + // A NodeList must have an item function (on non-IE platforms) or an item + // property of type 'string' (on IE). + return typeof val.item == 'function' || typeof val.item == 'string'; + } else if (goog.isFunction(val)) { + // On Safari, a NodeList is a function with an item property that is also + // a function. + return typeof val.item == 'function'; + } } - return this; + // Not a NodeList. + return false; }; /** - * Expand this box to include another box. - * NOTE(user): This is used in code that needs to be very fast, please don't - * add functionality to this function at the expense of speed (variable - * arguments, accepting multiple argument types, etc). - * @param {goog.math.Box} box The box to include in this one. + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * tag name and/or class name. If the passed element matches the specified + * criteria, the element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or + * null/undefined to match only based on class name). + * @param {?string=} opt_class The class name to match (or null/undefined to + * match only based on tag name). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if no match is found. */ -goog.math.Box.prototype.expandToInclude = function(box) { - this.left = Math.min(this.left, box.left); - this.top = Math.min(this.top, box.top); - this.right = Math.max(this.right, box.right); - this.bottom = Math.max(this.bottom, box.bottom); +goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class, + opt_maxSearchSteps) { + if (!opt_tag && !opt_class) { + return null; + } + var tagName = opt_tag ? opt_tag.toUpperCase() : null; + return /** @type {Element} */ (goog.dom.getAncestor(element, + function(node) { + return (!tagName || node.nodeName == tagName) && + (!opt_class || goog.isString(node.className) && + goog.array.contains(node.className.split(/\s+/), opt_class)); + }, true, opt_maxSearchSteps)); }; /** - * Compares boxes for equality. - * @param {goog.math.Box} a A Box. - * @param {goog.math.Box} b A Box. - * @return {boolean} True iff the boxes are equal, or if both are null. + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * class name. If the passed element matches the specified criteria, the + * element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {string} className The class name to match. + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if none match. */ -goog.math.Box.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; - } - return a.top == b.top && a.right == b.right && - a.bottom == b.bottom && a.left == b.left; +goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) { + return goog.dom.getAncestorByTagNameAndClass(element, null, className, + opt_maxSearchSteps); }; /** - * Returns whether a box contains a coordinate or another box. - * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. - * @return {boolean} Whether the box contains the coordinate or other box. + * Walks up the DOM hierarchy returning the first ancestor that passes the + * matcher function. + * @param {Node} element The DOM node to start with. + * @param {function(Node) : boolean} matcher A function that returns true if the + * passed node matches the desired criteria. + * @param {boolean=} opt_includeNode If true, the node itself is included in + * the search (the first call to the matcher will pass startElement as + * the node to test). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Node} DOM node that matched the matcher, or null if there was + * no match. */ -goog.math.Box.contains = function(box, other) { - if (!box || !other) { - return false; +goog.dom.getAncestor = function( + element, matcher, opt_includeNode, opt_maxSearchSteps) { + if (!opt_includeNode) { + element = element.parentNode; } - - if (other instanceof goog.math.Box) { - return other.left >= box.left && other.right <= box.right && - other.top >= box.top && other.bottom <= box.bottom; + var ignoreSearchSteps = opt_maxSearchSteps == null; + var steps = 0; + while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) { + if (matcher(element)) { + return element; + } + element = element.parentNode; + steps++; } - - // other is a Coordinate. - return other.x >= box.left && other.x <= box.right && - other.y >= box.top && other.y <= box.bottom; + // Reached the root of the DOM without a match + return null; }; /** - * Returns the relative x position of a coordinate compared to a box. Returns - * zero if the coordinate is inside the box. - * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate} coord A Coordinate. - * @return {number} The x position of {@code coord} relative to the nearest - * side of {@code box}, or zero if {@code coord} is inside {@code box}. + * Determines the active element in the given document. + * @param {Document} doc The document to look in. + * @return {Element} The active element. */ -goog.math.Box.relativePositionX = function(box, coord) { - if (coord.x < box.left) { - return coord.x - box.left; - } else if (coord.x > box.right) { - return coord.x - box.right; +goog.dom.getActiveElement = function(doc) { + try { + return doc && doc.activeElement; + } catch (e) { + // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE + // throws an exception. I'm not 100% sure why, but I suspect it chokes + // on document.activeElement if the activeElement has been recently + // removed from the DOM by a JS operation. + // + // We assume that an exception here simply means + // "there is no active element." } - return 0; + + return null; }; /** - * Returns the relative y position of a coordinate compared to a box. Returns - * zero if the coordinate is inside the box. + * Gives the current devicePixelRatio. * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate} coord A Coordinate. - * @return {number} The y position of {@code coord} relative to the nearest - * side of {@code box}, or zero if {@code coord} is inside {@code box}. + * By default, this is the value of window.devicePixelRatio (which should be + * preferred if present). + * + * If window.devicePixelRatio is not present, the ratio is calculated with + * window.matchMedia, if present. Otherwise, gives 1.0. + * + * Some browsers (including Chrome) consider the browser zoom level in the pixel + * ratio, so the value may change across multiple calls. + * + * @return {number} The number of actual pixels per virtual pixel. */ -goog.math.Box.relativePositionY = function(box, coord) { - if (coord.y < box.top) { - return coord.y - box.top; - } else if (coord.y > box.bottom) { - return coord.y - box.bottom; +goog.dom.getPixelRatio = function() { + var win = goog.dom.getWindow(); + + // devicePixelRatio does not work on Mobile firefox. + // TODO(user): Enable this check on a known working mobile Gecko version. + // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804 + var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE; + + if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) { + return win.devicePixelRatio; + } else if (win.matchMedia) { + return goog.dom.matchesPixelRatio_(.75) || + goog.dom.matchesPixelRatio_(1.5) || + goog.dom.matchesPixelRatio_(2) || + goog.dom.matchesPixelRatio_(3) || 1; } - return 0; + return 1; }; /** - * Returns the distance between a coordinate and the nearest corner/side of a - * box. Returns zero if the coordinate is inside the box. - * - * @param {goog.math.Box} box A Box. - * @param {goog.math.Coordinate} coord A Coordinate. - * @return {number} The distance between {@code coord} and the nearest - * corner/side of {@code box}, or zero if {@code coord} is inside - * {@code box}. + * Calculates a mediaQuery to check if the current device supports the + * given actual to virtual pixel ratio. + * @param {number} pixelRatio The ratio of actual pixels to virtual pixels. + * @return {number} pixelRatio if applicable, otherwise 0. + * @private */ -goog.math.Box.distance = function(box, coord) { - var x = goog.math.Box.relativePositionX(box, coord); - var y = goog.math.Box.relativePositionY(box, coord); - return Math.sqrt(x * x + y * y); +goog.dom.matchesPixelRatio_ = function(pixelRatio) { + var win = goog.dom.getWindow(); + var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' + + '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' + + '(min-resolution: ' + pixelRatio + 'dppx)'); + return win.matchMedia(query).matches ? pixelRatio : 0; }; + /** - * Returns whether two boxes intersect. - * - * @param {goog.math.Box} a A Box. - * @param {goog.math.Box} b A second Box. - * @return {boolean} Whether the boxes intersect. + * Create an instance of a DOM helper with a new document object. + * @param {Document=} opt_document Document object to associate with this + * DOM helper. + * @constructor */ -goog.math.Box.intersects = function(a, b) { - return (a.left <= b.right && b.left <= a.right && - a.top <= b.bottom && b.top <= a.bottom); +goog.dom.DomHelper = function(opt_document) { + /** + * Reference to the document object to use + * @type {!Document} + * @private + */ + this.document_ = opt_document || goog.global.document || document; }; /** - * Returns whether two boxes would intersect with additional padding. - * - * @param {goog.math.Box} a A Box. - * @param {goog.math.Box} b A second Box. - * @param {number} padding The additional padding. - * @return {boolean} Whether the boxes intersect. + * Gets the dom helper object for the document where the element resides. + * @param {Node=} opt_node If present, gets the DomHelper for this node. + * @return {!goog.dom.DomHelper} The DomHelper. */ -goog.math.Box.intersectsWithPadding = function(a, b, padding) { - return (a.left <= b.right + padding && b.left <= a.right + padding && - a.top <= b.bottom + padding && b.top <= a.bottom + padding); -}; +goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper; /** - * Rounds the fields to the next larger integer values. - * - * @return {!goog.math.Box} This box with ceil'd fields. + * Sets the document object. + * @param {!Document} document Document object. */ -goog.math.Box.prototype.ceil = function() { - this.top = Math.ceil(this.top); - this.right = Math.ceil(this.right); - this.bottom = Math.ceil(this.bottom); - this.left = Math.ceil(this.left); - return this; +goog.dom.DomHelper.prototype.setDocument = function(document) { + this.document_ = document; }; /** - * Rounds the fields to the next smaller integer values. - * - * @return {!goog.math.Box} This box with floored fields. + * Gets the document object being used by the dom library. + * @return {!Document} Document object. */ -goog.math.Box.prototype.floor = function() { - this.top = Math.floor(this.top); - this.right = Math.floor(this.right); - this.bottom = Math.floor(this.bottom); - this.left = Math.floor(this.left); - return this; +goog.dom.DomHelper.prototype.getDocument = function() { + return this.document_; }; /** - * Rounds the fields to nearest integer values. - * - * @return {!goog.math.Box} This box with rounded fields. + * Alias for {@code getElementById}. If a DOM node is passed in then we just + * return that. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. */ -goog.math.Box.prototype.round = function() { - this.top = Math.round(this.top); - this.right = Math.round(this.right); - this.bottom = Math.round(this.bottom); - this.left = Math.round(this.left); - return this; +goog.dom.DomHelper.prototype.getElement = function(element) { + return goog.dom.getElementHelper_(this.document_, element); }; /** - * Translates this box by the given offsets. If a {@code goog.math.Coordinate} - * is given, then the left and right values are translated by the coordinate's - * x value and the top and bottom values are translated by the coordinate's y - * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x - * and y dimension values. + * Gets an element by id, asserting that the element is found. * - * @param {number|goog.math.Coordinate} tx The value to translate the x - * dimension values by or the the coordinate to translate this box by. - * @param {number=} opt_ty The value to translate y dimension values by. - * @return {!goog.math.Box} This box after translating. + * This is used when an element is expected to exist, and should fail with + * an assertion error if it does not (if assertions are enabled). + * + * @param {string} id Element ID. + * @return {!Element} The element with the given ID, if it exists. */ -goog.math.Box.prototype.translate = function(tx, opt_ty) { - if (tx instanceof goog.math.Coordinate) { - this.left += tx.x; - this.right += tx.x; - this.top += tx.y; - this.bottom += tx.y; - } else { - this.left += tx; - this.right += tx; - if (goog.isNumber(opt_ty)) { - this.top += opt_ty; - this.bottom += opt_ty; - } - } - return this; +goog.dom.DomHelper.prototype.getRequiredElement = function(id) { + return goog.dom.getRequiredElementHelper_(this.document_, id); }; /** - * Scales this coordinate by the given scale factors. The x and y dimension - * values are scaled by {@code sx} and {@code opt_sy} respectively. - * If {@code opt_sy} is not given, then {@code sx} is used for both x and y. - * - * @param {number} sx The scale factor to use for the x dimension. - * @param {number=} opt_sy The scale factor to use for the y dimension. - * @return {!goog.math.Box} This box after scaling. + * Alias for {@code getElement}. + * @param {string|Element} element Element ID or a DOM node. + * @return {Element} The element with the given ID, or the node passed in. + * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead. */ -goog.math.Box.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.left *= sx; - this.right *= sx; - this.top *= sy; - this.bottom *= sy; - return this; -}; +goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A utility class for representing rectangles. + * Looks up elements by both tag and class name, using browser native functions + * ({@code querySelectorAll}, {@code getElementsByTagName} or + * {@code getElementsByClassName}) where possible. The returned array is a live + * NodeList or a static list depending on the code path taken. + * + * @see goog.dom.query + * + * @param {?string=} opt_tag Element tag name or * for all tags. + * @param {?string=} opt_class Optional class name. + * @param {(Document|Element)=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). */ - -goog.provide('goog.math.Rect'); - -goog.require('goog.math.Box'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Size'); - +goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag, + opt_class, + opt_el) { + return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag, + opt_class, opt_el); +}; /** - * Class for representing rectangular regions. - * @param {number} x Left. - * @param {number} y Top. - * @param {number} w Width. - * @param {number} h Height. - * @constructor + * Returns an array of all the elements with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {Element|Document=} opt_el Optional element to look in. + * @return { {length: number} } The items found with the class name provided. */ -goog.math.Rect = function(x, y, w, h) { - /** @type {number} */ - this.left = x; - - /** @type {number} */ - this.top = y; - - /** @type {number} */ - this.width = w; - - /** @type {number} */ - this.height = h; +goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) { + var doc = opt_el || this.document_; + return goog.dom.getElementsByClass(className, doc); }; /** - * @return {!goog.math.Rect} A new copy of this Rectangle. + * Returns the first element we find matching the provided class name. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(Element|Document)=} opt_el Optional element to look in. + * @return {Element} The first item found with the class name provided. */ -goog.math.Rect.prototype.clone = function() { - return new goog.math.Rect(this.left, this.top, this.width, this.height); +goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) { + var doc = opt_el || this.document_; + return goog.dom.getElementByClass(className, doc); }; /** - * Returns a new Box object with the same position and dimensions as this - * rectangle. - * @return {!goog.math.Box} A new Box representation of this Rectangle. + * Ensures an element with the given className exists, and then returns the + * first element with the provided className. + * @see {goog.dom.query} + * @param {string} className the name of the class to look for. + * @param {(!Element|!Document)=} opt_root Optional element or document to look + * in. + * @return {!Element} The first item found with the class name provided. + * @throws {goog.asserts.AssertionError} Thrown if no element is found. */ -goog.math.Rect.prototype.toBox = function() { - var right = this.left + this.width; - var bottom = this.top + this.height; - return new goog.math.Box(this.top, - right, - bottom, - this.left); +goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className, + opt_root) { + var root = opt_root || this.document_; + return goog.dom.getRequiredElementByClass(className, root); }; /** - * Creates a new Rect object with the same position and dimensions as a given - * Box. Note that this is only the inverse of toBox if left/top are defined. - * @param {goog.math.Box} box A box. - * @return {!goog.math.Rect} A new Rect initialized with the box's position - * and size. + * Alias for {@code getElementsByTagNameAndClass}. + * @deprecated Use DomHelper getElementsByTagNameAndClass. + * @see goog.dom.query + * + * @param {?string=} opt_tag Element tag name. + * @param {?string=} opt_class Optional class name. + * @param {Element=} opt_el Optional element to look in. + * @return { {length: number} } Array-like list of elements (only a length + * property and numerical indices are guaranteed to exist). */ -goog.math.Rect.createFromBox = function(box) { - return new goog.math.Rect(box.left, box.top, - box.right - box.left, box.bottom - box.top); -}; +goog.dom.DomHelper.prototype.$$ = + goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; -if (goog.DEBUG) { - /** - * Returns a nice string representing size and dimensions of rectangle. - * @return {string} In the form (50, 73 - 75w x 25h). - * @override - */ - goog.math.Rect.prototype.toString = function() { - return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' + - this.height + 'h)'; - }; -} +/** + * Sets a number of properties on a node. + * @param {Element} element DOM node to set properties on. + * @param {Object} properties Hash of property:value pairs. + */ +goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties; /** - * Compares rectangles for equality. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {boolean} True iff the rectangles have the same left, top, width, - * and height, or if both are null. + * Gets the dimensions of the viewport. + * @param {Window=} opt_window Optional window element to test. Defaults to + * the window of the Dom Helper. + * @return {!goog.math.Size} Object with values 'width' and 'height'. */ -goog.math.Rect.equals = function(a, b) { - if (a == b) { - return true; - } - if (!a || !b) { - return false; - } - return a.left == b.left && a.width == b.width && - a.top == b.top && a.height == b.height; +goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) { + // TODO(arv): This should not take an argument. That breaks the rule of a + // a DomHelper representing a single frame/window/document. + return goog.dom.getViewportSize(opt_window || this.getWindow()); }; /** - * Computes the intersection of this rectangle and the rectangle parameter. If - * there is no intersection, returns false and leaves this rectangle as is. - * @param {goog.math.Rect} rect A Rectangle. - * @return {boolean} True iff this rectangle intersects with the parameter. + * Calculates the height of the document. + * + * @return {number} The height of the document. */ -goog.math.Rect.prototype.intersection = function(rect) { - var x0 = Math.max(this.left, rect.left); - var x1 = Math.min(this.left + this.width, rect.left + rect.width); - - if (x0 <= x1) { - var y0 = Math.max(this.top, rect.top); - var y1 = Math.min(this.top + this.height, rect.top + rect.height); - - if (y0 <= y1) { - this.left = x0; - this.top = y0; - this.width = x1 - x0; - this.height = y1 - y0; - - return true; - } - } - return false; +goog.dom.DomHelper.prototype.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(this.getWindow()); }; /** - * Returns the intersection of two rectangles. Two rectangles intersect if they - * touch at all, for example, two zero width and height rectangles would - * intersect if they had the same top and left. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {goog.math.Rect} A new intersection rect (even if width and height - * are 0), or null if there is no intersection. + * Typedef for use with goog.dom.createDom and goog.dom.append. + * @typedef {Object|string|Array|NodeList} */ -goog.math.Rect.intersection = function(a, b) { - // There is no nice way to do intersection via a clone, because any such - // clone might be unnecessary if this function returns null. So, we duplicate - // code from above. - - var x0 = Math.max(a.left, b.left); - var x1 = Math.min(a.left + a.width, b.left + b.width); +goog.dom.Appendable; - if (x0 <= x1) { - var y0 = Math.max(a.top, b.top); - var y1 = Math.min(a.top + a.height, b.top + b.height); - if (y0 <= y1) { - return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0); - } - } - return null; +/** + * Returns a dom node with a set of attributes. This function accepts varargs + * for subsequent nodes to be added. Subsequent nodes will be added to the + * first node as childNodes. + * + * So: + * <code>createDom('div', null, createDom('p'), createDom('p'));</code> + * would return a div with two child paragraphs + * + * An easy way to move all child nodes of an existing element to a new parent + * element is: + * <code>createDom('div', null, oldElement.childNodes);</code> + * which will remove all child nodes from the old element and add them as + * child nodes of the new DIV. + * + * @param {string} tagName Tag to create. + * @param {Object|string=} opt_attributes If object, then a map of name-value + * pairs for attributes. If a string, then this is the className of the new + * element. + * @param {...goog.dom.Appendable} var_args Further DOM nodes or + * strings for text nodes. If one of the var_args is an array or + * NodeList, its elements will be added as childNodes instead. + * @return {!Element} Reference to a DOM node. + */ +goog.dom.DomHelper.prototype.createDom = function(tagName, + opt_attributes, + var_args) { + return goog.dom.createDom_(this.document_, arguments); }; /** - * Returns whether two rectangles intersect. Two rectangles intersect if they - * touch at all, for example, two zero width and height rectangles would - * intersect if they had the same top and left. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {boolean} Whether a and b intersect. + * Alias for {@code createDom}. + * @param {string} tagName Tag to create. + * @param {(Object|string)=} opt_attributes If object, then a map of name-value + * pairs for attributes. If a string, then this is the className of the new + * element. + * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for + * text nodes. If one of the var_args is an array, its children will be + * added as childNodes instead. + * @return {!Element} Reference to a DOM node. + * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead. */ -goog.math.Rect.intersects = function(a, b) { - return (a.left <= b.left + b.width && b.left <= a.left + a.width && - a.top <= b.top + b.height && b.top <= a.top + a.height); -}; +goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom; /** - * Returns whether a rectangle intersects this rectangle. - * @param {goog.math.Rect} rect A rectangle. - * @return {boolean} Whether rect intersects this rectangle. + * Creates a new element. + * @param {string} name Tag name. + * @return {!Element} The new element. */ -goog.math.Rect.prototype.intersects = function(rect) { - return goog.math.Rect.intersects(this, rect); +goog.dom.DomHelper.prototype.createElement = function(name) { + return this.document_.createElement(name); }; /** - * Computes the difference regions between two rectangles. The return value is - * an array of 0 to 4 rectangles defining the remaining regions of the first - * rectangle after the second has been subtracted. - * @param {goog.math.Rect} a A Rectangle. - * @param {goog.math.Rect} b A Rectangle. - * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which - * together define the difference area of rectangle a minus rectangle b. + * Creates a new text node. + * @param {number|string} content Content. + * @return {!Text} The new text node. */ -goog.math.Rect.difference = function(a, b) { - var intersection = goog.math.Rect.intersection(a, b); - if (!intersection || !intersection.height || !intersection.width) { - return [a.clone()]; - } +goog.dom.DomHelper.prototype.createTextNode = function(content) { + return this.document_.createTextNode(String(content)); +}; - var result = []; - var top = a.top; - var height = a.height; +/** + * Create a table. + * @param {number} rows The number of rows in the table. Must be >= 1. + * @param {number} columns The number of columns in the table. Must be >= 1. + * @param {boolean=} opt_fillWithNbsp If true, fills table entries with + * {@code goog.string.Unicode.NBSP} characters. + * @return {!HTMLElement} The created table. + */ +goog.dom.DomHelper.prototype.createTable = function(rows, columns, + opt_fillWithNbsp) { + return goog.dom.createTable_(this.document_, rows, columns, + !!opt_fillWithNbsp); +}; - var ar = a.left + a.width; - var ab = a.top + a.height; - var br = b.left + b.width; - var bb = b.top + b.height; +/** + * Converts an HTML into a node or a document fragment. A single Node is used if + * {@code html} only generates a single node. If {@code html} generates multiple + * nodes then these are put inside a {@code DocumentFragment}. + * @param {!goog.html.SafeHtml} html The HTML markup to convert. + * @return {!Node} The resulting node. + */ +goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) { + return goog.dom.safeHtmlToNode_(this.document_, html); +}; - // Subtract off any area on top where A extends past B - if (b.top > a.top) { - result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top)); - top = b.top; - // If we're moving the top down, we also need to subtract the height diff. - height -= b.top - a.top; - } - // Subtract off any area on bottom where A extends past B - if (bb < ab) { - result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb)); - height = bb - top; - } - // Subtract any area on left where A extends past B - if (b.left > a.left) { - result.push(new goog.math.Rect(a.left, top, b.left - a.left, height)); - } - // Subtract any area on right where A extends past B - if (br < ar) { - result.push(new goog.math.Rect(br, top, ar - br, height)); - } - return result; +/** + * Converts an HTML string into a node or a document fragment. A single Node + * is used if the {@code htmlString} only generates a single node. If the + * {@code htmlString} generates multiple nodes then these are put inside a + * {@code DocumentFragment}. + * + * @param {string} htmlString The HTML string to convert. + * @return {!Node} The resulting node. + */ +goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) { + return goog.dom.htmlToDocumentFragment_(this.document_, htmlString); }; /** - * Computes the difference regions between this rectangle and {@code rect}. The - * return value is an array of 0 to 4 rectangles defining the remaining regions - * of this rectangle after the other has been subtracted. - * @param {goog.math.Rect} rect A Rectangle. - * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which - * together define the difference area of rectangle a minus rectangle b. + * Returns true if the browser is in "CSS1-compatible" (standards-compliant) + * mode, false otherwise. + * @return {boolean} True if in CSS1-compatible mode. */ -goog.math.Rect.prototype.difference = function(rect) { - return goog.math.Rect.difference(this, rect); +goog.dom.DomHelper.prototype.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(this.document_); }; /** - * Expand this rectangle to also include the area of the given rectangle. - * @param {goog.math.Rect} rect The other rectangle. + * Gets the window object associated with the document. + * @return {!Window} The window associated with the given document. */ -goog.math.Rect.prototype.boundingRect = function(rect) { - // We compute right and bottom before we change left and top below. - var right = Math.max(this.left + this.width, rect.left + rect.width); - var bottom = Math.max(this.top + this.height, rect.top + rect.height); +goog.dom.DomHelper.prototype.getWindow = function() { + return goog.dom.getWindow_(this.document_); +}; - this.left = Math.min(this.left, rect.left); - this.top = Math.min(this.top, rect.top); - this.width = right - this.left; - this.height = bottom - this.top; +/** + * Gets the document scroll element. + * @return {!Element} Scrolling element. + */ +goog.dom.DomHelper.prototype.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(this.document_); }; /** - * Returns a new rectangle which completely contains both input rectangles. - * @param {goog.math.Rect} a A rectangle. - * @param {goog.math.Rect} b A rectangle. - * @return {goog.math.Rect} A new bounding rect, or null if either rect is - * null. + * Gets the document scroll distance as a coordinate object. + * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'. */ -goog.math.Rect.boundingRect = function(a, b) { - if (!a || !b) { - return null; - } +goog.dom.DomHelper.prototype.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(this.document_); +}; - var clone = a.clone(); - clone.boundingRect(b); - return clone; +/** + * Determines the active element in the given document. + * @param {Document=} opt_doc The document to look in. + * @return {Element} The active element. + */ +goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) { + return goog.dom.getActiveElement(opt_doc || this.document_); }; /** - * Tests whether this rectangle entirely contains another rectangle or - * coordinate. - * - * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or - * coordinate to test for containment. - * @return {boolean} Whether this rectangle contains given rectangle or - * coordinate. + * Appends a child to a node. + * @param {Node} parent Parent. + * @param {Node} child Child. */ -goog.math.Rect.prototype.contains = function(another) { - if (another instanceof goog.math.Rect) { - return this.left <= another.left && - this.left + this.width >= another.left + another.width && - this.top <= another.top && - this.top + this.height >= another.top + another.height; - } else { // (another instanceof goog.math.Coordinate) - return another.x >= this.left && - another.x <= this.left + this.width && - another.y >= this.top && - another.y <= this.top + this.height; - } -}; +goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild; /** - * @param {!goog.math.Coordinate} point A coordinate. - * @return {number} The squared distance between the point and the closest - * point inside the rectangle. Returns 0 if the point is inside the - * rectangle. + * Appends a node with text or other nodes. + * @param {!Node} parent The node to append nodes to. + * @param {...goog.dom.Appendable} var_args The things to append to the node. + * If this is a Node it is appended as is. + * If this is a string then a text node is appended. + * If this is an array like object then fields 0 to length - 1 are appended. */ -goog.math.Rect.prototype.squaredDistance = function(point) { - var dx = point.x < this.left ? - this.left - point.x : Math.max(point.x - (this.left + this.width), 0); - var dy = point.y < this.top ? - this.top - point.y : Math.max(point.y - (this.top + this.height), 0); - return dx * dx + dy * dy; -}; +goog.dom.DomHelper.prototype.append = goog.dom.append; /** - * @param {!goog.math.Coordinate} point A coordinate. - * @return {number} The distance between the point and the closest point - * inside the rectangle. Returns 0 if the point is inside the rectangle. + * Determines if the given node can contain children, intended to be used for + * HTML generation. + * + * @param {Node} node The node to check. + * @return {boolean} Whether the node can contain children. */ -goog.math.Rect.prototype.distance = function(point) { - return Math.sqrt(this.squaredDistance(point)); -}; +goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren; /** - * @return {!goog.math.Size} The size of this rectangle. + * Removes all the child nodes on a DOM node. + * @param {Node} node Node to remove children from. */ -goog.math.Rect.prototype.getSize = function() { - return new goog.math.Size(this.width, this.height); -}; +goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren; /** - * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of - * the rectangle. + * Inserts a new node before an existing reference node (i.e., as the previous + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert before. */ -goog.math.Rect.prototype.getTopLeft = function() { - return new goog.math.Coordinate(this.left, this.top); -}; +goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore; /** - * @return {!goog.math.Coordinate} A new coordinate for the center of the - * rectangle. + * Inserts a new node after an existing reference node (i.e., as the next + * sibling). If the reference node has no parent, then does nothing. + * @param {Node} newNode Node to insert. + * @param {Node} refNode Reference node to insert after. */ -goog.math.Rect.prototype.getCenter = function() { - return new goog.math.Coordinate( - this.left + this.width / 2, this.top + this.height / 2); -}; +goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter; /** - * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner - * of the rectangle. + * Insert a child at a given index. If index is larger than the number of child + * nodes that the parent currently has, the node is inserted as the last child + * node. + * @param {Element} parent The element into which to insert the child. + * @param {Node} child The element to insert. + * @param {number} index The index at which to insert the new child node. Must + * not be negative. */ -goog.math.Rect.prototype.getBottomRight = function() { - return new goog.math.Coordinate( - this.left + this.width, this.top + this.height); -}; +goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt; /** - * Rounds the fields to the next larger integer values. - * @return {!goog.math.Rect} This rectangle with ceil'd fields. + * Removes a node from its parent. + * @param {Node} node The node to remove. + * @return {Node} The node removed if removed; else, null. */ -goog.math.Rect.prototype.ceil = function() { - this.left = Math.ceil(this.left); - this.top = Math.ceil(this.top); - this.width = Math.ceil(this.width); - this.height = Math.ceil(this.height); - return this; -}; +goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode; /** - * Rounds the fields to the next smaller integer values. - * @return {!goog.math.Rect} This rectangle with floored fields. + * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no + * parent. + * @param {Node} newNode Node to insert. + * @param {Node} oldNode Node to replace. */ -goog.math.Rect.prototype.floor = function() { - this.left = Math.floor(this.left); - this.top = Math.floor(this.top); - this.width = Math.floor(this.width); - this.height = Math.floor(this.height); - return this; -}; +goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode; /** - * Rounds the fields to nearest integer values. - * @return {!goog.math.Rect} This rectangle with rounded fields. + * Flattens an element. That is, removes it and replace it with its children. + * @param {Element} element The element to flatten. + * @return {Element|undefined} The original element, detached from the document + * tree, sans children, or undefined if the element was already not in the + * document. */ -goog.math.Rect.prototype.round = function() { - this.left = Math.round(this.left); - this.top = Math.round(this.top); - this.width = Math.round(this.width); - this.height = Math.round(this.height); - return this; -}; +goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement; /** - * Translates this rectangle by the given offsets. If a - * {@code goog.math.Coordinate} is given, then the left and top values are - * translated by the coordinate's x and y values. Otherwise, top and left are - * translated by {@code tx} and {@code opt_ty} respectively. - * @param {number|goog.math.Coordinate} tx The value to translate left by or the - * the coordinate to translate this rect by. - * @param {number=} opt_ty The value to translate top by. - * @return {!goog.math.Rect} This rectangle after translating. + * Returns an array containing just the element children of the given element. + * @param {Element} element The element whose element children we want. + * @return {!(Array|NodeList)} An array or array-like list of just the element + * children of the given element. */ -goog.math.Rect.prototype.translate = function(tx, opt_ty) { - if (tx instanceof goog.math.Coordinate) { - this.left += tx.x; - this.top += tx.y; - } else { - this.left += tx; - if (goog.isNumber(opt_ty)) { - this.top += opt_ty; - } - } - return this; -}; +goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren; /** - * Scales this rectangle by the given scale factors. The left and width values - * are scaled by {@code sx} and the top and height values are scaled by - * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled - * by {@code sx}. - * @param {number} sx The scale factor to use for the x dimension. - * @param {number=} opt_sy The scale factor to use for the y dimension. - * @return {!goog.math.Rect} This rectangle after scaling. + * Returns the first child node that is an element. + * @param {Node} node The node to get the first child element of. + * @return {Element} The first child node of {@code node} that is an element. */ -goog.math.Rect.prototype.scale = function(sx, opt_sy) { - var sy = goog.isNumber(opt_sy) ? opt_sy : sx; - this.left *= sx; - this.width *= sx; - this.top *= sy; - this.height *= sy; - return this; -}; +goog.dom.DomHelper.prototype.getFirstElementChild = + goog.dom.getFirstElementChild; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for element styles. - * - * @author arv@google.com (Erik Arvidsson) - * @author eae@google.com (Emil A Eklund) - * @see ../demos/inline_block_quirks.html - * @see ../demos/inline_block_standards.html - * @see ../demos/style_viewport.html + * Returns the last child node that is an element. + * @param {Node} node The node to get the last child element of. + * @return {Element} The last child node of {@code node} that is an element. */ - -goog.provide('goog.style'); - - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.vendor'); -goog.require('goog.math.Box'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Rect'); -goog.require('goog.math.Size'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.userAgent'); +goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild; /** - * @define {boolean} Whether we know at compile time that - * getBoundingClientRect() is present and bug-free on the browser. + * Returns the first next sibling that is an element. + * @param {Node} node The node to get the next sibling element of. + * @return {Element} The next sibling of {@code node} that is an element. */ -goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false); +goog.dom.DomHelper.prototype.getNextElementSibling = + goog.dom.getNextElementSibling; /** - * Sets a style value on an element. - * - * This function is not indended to patch issues in the browser's style - * handling, but to allow easy programmatic access to setting dash-separated - * style properties. An example is setting a batch of properties from a data - * object without overwriting old styles. When possible, use native APIs: - * elem.style.propertyKey = 'value' or (if obliterating old styles is fine) - * elem.style.cssText = 'property1: value1; property2: value2'. - * - * @param {Element} element The element to change. - * @param {string|Object} style If a string, a style name. If an object, a hash - * of style names to style values. - * @param {string|number|boolean=} opt_value If style was a string, then this - * should be the value. + * Returns the first previous sibling that is an element. + * @param {Node} node The node to get the previous sibling element of. + * @return {Element} The first previous sibling of {@code node} that is + * an element. */ -goog.style.setStyle = function(element, style, opt_value) { - if (goog.isString(style)) { - goog.style.setStyle_(element, opt_value, style); - } else { - for (var key in style) { - goog.style.setStyle_(element, style[key], key); - } - } -}; +goog.dom.DomHelper.prototype.getPreviousElementSibling = + goog.dom.getPreviousElementSibling; /** - * Sets a style value on an element, with parameters swapped to work with - * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when - * necessary. - * @param {Element} element The element to change. - * @param {string|number|boolean|undefined} value Style value. - * @param {string} style Style name. - * @private + * Returns the next node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The next node in the DOM tree, or null if this was the last + * node. */ -goog.style.setStyle_ = function(element, value, style) { - var propertyName = goog.style.getVendorJsStyleName_(element, style); - - if (propertyName) { - element.style[propertyName] = value; - } -}; +goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode; /** - * Style name cache that stores previous property name lookups. - * - * This is used by setStyle to speed up property lookups, entries look like: - * { StyleName: ActualPropertyName } - * - * @private {!Object<string, string>} + * Returns the previous node in source order from the given node. + * @param {Node} node The node. + * @return {Node} The previous node in the DOM tree, or null if this was the + * first node. */ -goog.style.styleNameCache_ = {}; +goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode; /** - * Returns the style property name in camel-case. If it does not exist and a - * vendor-specific version of the property does exist, then return the vendor- - * specific property name instead. - * @param {Element} element The element to change. - * @param {string} style Style name. - * @return {string} Vendor-specific style. - * @private + * Whether the object looks like a DOM node. + * @param {?} obj The object being tested for node likeness. + * @return {boolean} Whether the object looks like a DOM node. */ -goog.style.getVendorJsStyleName_ = function(element, style) { - var propertyName = goog.style.styleNameCache_[style]; - if (!propertyName) { - var camelStyle = goog.string.toCamelCase(style); - propertyName = camelStyle; - - if (element.style[camelStyle] === undefined) { - var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + - goog.string.toTitleCase(camelStyle); - - if (element.style[prefixedStyle] !== undefined) { - propertyName = prefixedStyle; - } - } - goog.style.styleNameCache_[style] = propertyName; - } - - return propertyName; -}; +goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike; /** - * Returns the style property name in CSS notation. If it does not exist and a - * vendor-specific version of the property does exist, then return the vendor- - * specific property name instead. - * @param {Element} element The element to change. - * @param {string} style Style name. - * @return {string} Vendor-specific style. - * @private + * Whether the object looks like an Element. + * @param {?} obj The object being tested for Element likeness. + * @return {boolean} Whether the object looks like an Element. */ -goog.style.getVendorStyleName_ = function(element, style) { - var camelStyle = goog.string.toCamelCase(style); - - if (element.style[camelStyle] === undefined) { - var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + - goog.string.toTitleCase(camelStyle); - - if (element.style[prefixedStyle] !== undefined) { - return goog.dom.vendor.getVendorPrefix() + '-' + style; - } - } - - return style; -}; +goog.dom.DomHelper.prototype.isElement = goog.dom.isElement; /** - * Retrieves an explicitly-set style value of a node. This returns '' if there - * isn't a style attribute on the element or if this style property has not been - * explicitly set in script. - * - * @param {Element} element Element to get style of. - * @param {string} property Property to get, css-style (if you have a camel-case - * property, use element.style[style]). - * @return {string} Style value. + * Returns true if the specified value is a Window object. This includes the + * global window for HTML pages, and iframe windows. + * @param {?} obj Variable to test. + * @return {boolean} Whether the variable is a window. */ -goog.style.getStyle = function(element, property) { - // element.style is '' for well-known properties which are unset. - // For for browser specific styles as 'filter' is undefined - // so we need to return '' explicitly to make it consistent across - // browsers. - var styleValue = element.style[goog.string.toCamelCase(property)]; +goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow; - // Using typeof here because of a bug in Safari 5.1, where this value - // was undefined, but === undefined returned false. - if (typeof(styleValue) !== 'undefined') { - return styleValue; - } - return element.style[goog.style.getVendorJsStyleName_(element, property)] || - ''; -}; +/** + * Returns an element's parent, if it's an Element. + * @param {Element} element The DOM element. + * @return {Element} The parent, or null if not an Element. + */ +goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement; /** - * Retrieves a computed style value of a node. It returns empty string if the - * value cannot be computed (which will be the case in Internet Explorer) or - * "none" if the property requested is an SVG one and it has not been - * explicitly set (firefox and webkit). - * - * @param {Element} element Element to get style of. - * @param {string} property Property to get (camel-case). - * @return {string} Style value. + * Whether a node contains another node. + * @param {Node} parent The node that should contain the other node. + * @param {Node} descendant The node to test presence of. + * @return {boolean} Whether the parent node contains the descendent node. */ -goog.style.getComputedStyle = function(element, property) { - var doc = goog.dom.getOwnerDocument(element); - if (doc.defaultView && doc.defaultView.getComputedStyle) { - var styles = doc.defaultView.getComputedStyle(element, null); - if (styles) { - // element.style[..] is undefined for browser specific styles - // as 'filter'. - return styles[property] || styles.getPropertyValue(property) || ''; - } - } - - return ''; -}; +goog.dom.DomHelper.prototype.contains = goog.dom.contains; /** - * Gets the cascaded style value of a node, or null if the value cannot be - * computed (only Internet Explorer can do this). + * Compares the document order of two nodes, returning 0 if they are the same + * node, a negative number if node1 is before node2, and a positive number if + * node2 is before node1. Note that we compare the order the tags appear in the + * document so in the tree <b><i>text</i></b> the B node is considered to be + * before the I node. * - * @param {Element} element Element to get style of. - * @param {string} style Property to get (camel-case). - * @return {string} Style value. + * @param {Node} node1 The first node to compare. + * @param {Node} node2 The second node to compare. + * @return {number} 0 if the nodes are the same node, a negative number if node1 + * is before node2, and a positive number if node2 is before node1. */ -goog.style.getCascadedStyle = function(element, style) { - // TODO(nicksantos): This should be documented to return null. #fixTypes - return element.currentStyle ? element.currentStyle[style] : null; -}; +goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder; /** - * Cross-browser pseudo get computed style. It returns the computed style where - * available. If not available it tries the cascaded style value (IE - * currentStyle) and in worst case the inline style value. It shouldn't be - * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for - * discussion. - * - * @param {Element} element Element to get style of. - * @param {string} style Property to get (must be camelCase, not css-style.). - * @return {string} Style value. - * @private + * Find the deepest common ancestor of the given nodes. + * @param {...Node} var_args The nodes to find a common ancestor of. + * @return {Node} The common ancestor of the nodes, or null if there is none. + * null will only be returned if two or more of the nodes are from different + * documents. */ -goog.style.getStyle_ = function(element, style) { - return goog.style.getComputedStyle(element, style) || - goog.style.getCascadedStyle(element, style) || - (element.style && element.style[style]); -}; +goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor; /** - * Retrieves the computed value of the box-sizing CSS attribute. - * Browser support: http://caniuse.com/css3-boxsizing. - * @param {!Element} element The element whose box-sizing to get. - * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if - * box-sizing is not supported (IE7 and below). + * Returns the owner document for a node. + * @param {Node} node The node to get the document for. + * @return {!Document} The document owning the node. */ -goog.style.getComputedBoxSizing = function(element) { - return goog.style.getStyle_(element, 'boxSizing') || - goog.style.getStyle_(element, 'MozBoxSizing') || - goog.style.getStyle_(element, 'WebkitBoxSizing') || null; -}; +goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument; /** - * Retrieves the computed value of the position CSS attribute. - * @param {Element} element The element to get the position of. - * @return {string} Position value. + * Cross browser function for getting the document element of an iframe. + * @param {Element} iframe Iframe element. + * @return {!Document} The frame content document. */ -goog.style.getComputedPosition = function(element) { - return goog.style.getStyle_(element, 'position'); -}; +goog.dom.DomHelper.prototype.getFrameContentDocument = + goog.dom.getFrameContentDocument; /** - * Retrieves the computed background color string for a given element. The - * string returned is suitable for assigning to another element's - * background-color, but is not guaranteed to be in any particular string - * format. Accessing the color in a numeric form may not be possible in all - * browsers or with all input. - * - * If the background color for the element is defined as a hexadecimal value, - * the resulting string can be parsed by goog.color.parse in all supported - * browsers. - * - * Whether named colors like "red" or "lightblue" get translated into a - * format which can be parsed is browser dependent. Calling this function on - * transparent elements will return "transparent" in most browsers or - * "rgba(0, 0, 0, 0)" in WebKit. - * @param {Element} element The element to get the background color of. - * @return {string} The computed string value of the background color. + * Cross browser function for getting the window of a frame or iframe. + * @param {Element} frame Frame element. + * @return {Window} The window associated with the given frame. */ -goog.style.getBackgroundColor = function(element) { - return goog.style.getStyle_(element, 'backgroundColor'); -}; +goog.dom.DomHelper.prototype.getFrameContentWindow = + goog.dom.getFrameContentWindow; /** - * Retrieves the computed value of the overflow-x CSS attribute. - * @param {Element} element The element to get the overflow-x of. - * @return {string} The computed string value of the overflow-x attribute. + * Sets the text content of a node, with cross-browser support. + * @param {Node} node The node to change the text content of. + * @param {string|number} text The value that should replace the node's content. */ -goog.style.getComputedOverflowX = function(element) { - return goog.style.getStyle_(element, 'overflowX'); -}; +goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent; /** - * Retrieves the computed value of the overflow-y CSS attribute. - * @param {Element} element The element to get the overflow-y of. - * @return {string} The computed string value of the overflow-y attribute. + * Gets the outerHTML of a node, which islike innerHTML, except that it + * actually contains the HTML of the node itself. + * @param {Element} element The element to get the HTML of. + * @return {string} The outerHTML of the given element. */ -goog.style.getComputedOverflowY = function(element) { - return goog.style.getStyle_(element, 'overflowY'); -}; +goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml; /** - * Retrieves the computed value of the z-index CSS attribute. - * @param {Element} element The element to get the z-index of. - * @return {string|number} The computed value of the z-index attribute. + * Finds the first descendant node that matches the filter function. This does + * a depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Node|undefined} The found node or undefined if none is found. */ -goog.style.getComputedZIndex = function(element) { - return goog.style.getStyle_(element, 'zIndex'); -}; +goog.dom.DomHelper.prototype.findNode = goog.dom.findNode; /** - * Retrieves the computed value of the text-align CSS attribute. - * @param {Element} element The element to get the text-align of. - * @return {string} The computed string value of the text-align attribute. + * Finds all the descendant nodes that matches the filter function. This does a + * depth first search. + * @param {Node} root The root of the tree to search. + * @param {function(Node) : boolean} p The filter function. + * @return {Array<Node>} The found nodes or an empty array if none are found. */ -goog.style.getComputedTextAlign = function(element) { - return goog.style.getStyle_(element, 'textAlign'); -}; +goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes; /** - * Retrieves the computed value of the cursor CSS attribute. - * @param {Element} element The element to get the cursor of. - * @return {string} The computed string value of the cursor attribute. + * Returns true if the element has a tab index that allows it to receive + * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + * natively support keyboard focus, even if they have no tab index. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element has a tab index that allows keyboard + * focus. */ -goog.style.getComputedCursor = function(element) { - return goog.style.getStyle_(element, 'cursor'); -}; +goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex; /** - * Retrieves the computed value of the CSS transform attribute. - * @param {Element} element The element to get the transform of. - * @return {string} The computed string representation of the transform matrix. + * Enables or disables keyboard focus support on the element via its tab index. + * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true + * (or elements that natively support keyboard focus, like form elements) can + * receive keyboard focus. See http://go/tabindex for more info. + * @param {Element} element Element whose tab index is to be changed. + * @param {boolean} enable Whether to set or remove a tab index on the element + * that supports keyboard focus. */ -goog.style.getComputedTransform = function(element) { - var property = goog.style.getVendorStyleName_(element, 'transform'); - return goog.style.getStyle_(element, property) || - goog.style.getStyle_(element, 'transform'); -}; +goog.dom.DomHelper.prototype.setFocusableTabIndex = + goog.dom.setFocusableTabIndex; /** - * Sets the top/left values of an element. If no unit is specified in the - * argument then it will add px. The second argument is required if the first - * argument is a string or number and is ignored if the first argument - * is a coordinate. - * @param {Element} el Element to move. - * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. - * @param {string|number=} opt_arg2 Top position. + * Returns true if the element can be focused, i.e. it has a tab index that + * allows it to receive keyboard focus (tabIndex >= 0), or it is an element + * that natively supports keyboard focus. + * @param {!Element} element Element to check. + * @return {boolean} Whether the element allows keyboard focus. */ -goog.style.setPosition = function(el, arg1, opt_arg2) { - var x, y; - var buggyGeckoSubPixelPos = goog.userAgent.GECKO && - (goog.userAgent.MAC || goog.userAgent.X11) && - goog.userAgent.isVersionOrHigher('1.9'); +goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable; - if (arg1 instanceof goog.math.Coordinate) { - x = arg1.x; - y = arg1.y; - } else { - x = arg1; - y = opt_arg2; - } - // Round to the nearest pixel for buggy sub-pixel support. - el.style.left = goog.style.getPixelStyleValue_( - /** @type {number|string} */ (x), buggyGeckoSubPixelPos); - el.style.top = goog.style.getPixelStyleValue_( - /** @type {number|string} */ (y), buggyGeckoSubPixelPos); -}; +/** + * Returns the text contents of the current node, without markup. New lines are + * stripped and whitespace is collapsed, such that each character would be + * visible. + * + * In browsers that support it, innerText is used. Other browsers attempt to + * simulate it via node traversal. Line breaks are canonicalized in IE. + * + * @param {Node} node The node from which we are getting content. + * @return {string} The text content. + */ +goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent; /** - * Gets the offsetLeft and offsetTop properties of an element and returns them - * in a Coordinate object - * @param {Element} element Element. - * @return {!goog.math.Coordinate} The position. + * Returns the text length of the text contained in a node, without markup. This + * is equivalent to the selection length if the node was selected, or the number + * of cursor movements to traverse the node. Images & BRs take one space. New + * lines are ignored. + * + * @param {Node} node The node whose text content length is being calculated. + * @return {number} The length of {@code node}'s text content. */ -goog.style.getPosition = function(element) { - return new goog.math.Coordinate(element.offsetLeft, element.offsetTop); -}; +goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength; /** - * Returns the viewport element for a particular document - * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element - * of. - * @return {Element} document.documentElement or document.body. + * Returns the text offset of a node relative to one of its ancestors. The text + * length is the same as the length calculated by + * {@code goog.dom.getNodeTextLength}. + * + * @param {Node} node The node whose offset is being calculated. + * @param {Node=} opt_offsetParent Defaults to the node's owner document's body. + * @return {number} The text offset. */ -goog.style.getClientViewportElement = function(opt_node) { - var doc; - if (opt_node) { - doc = goog.dom.getOwnerDocument(opt_node); - } else { - doc = goog.dom.getDocument(); - } - - // In old IE versions the document.body represented the viewport - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && - !goog.dom.getDomHelper(doc).isCss1CompatMode()) { - return doc.body; - } - return doc.documentElement; -}; +goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset; /** - * Calculates the viewport coordinates relative to the page/document - * containing the node. The viewport may be the browser viewport for - * non-iframe document, or the iframe container for iframe'd document. - * @param {!Document} doc The document to use as the reference point. - * @return {!goog.math.Coordinate} The page offset of the viewport. + * Returns the node at a given offset in a parent node. If an object is + * provided for the optional third parameter, the node and the remainder of the + * offset will stored as properties of this object. + * @param {Node} parent The parent node. + * @param {number} offset The offset into the parent node. + * @param {Object=} opt_result Object to be used to store the return value. The + * return value will be stored in the form {node: Node, remainder: number} + * if this object is provided. + * @return {Node} The node at the given offset. */ -goog.style.getViewportPageOffset = function(doc) { - var body = doc.body; - var documentElement = doc.documentElement; - var scrollLeft = body.scrollLeft || documentElement.scrollLeft; - var scrollTop = body.scrollTop || documentElement.scrollTop; - return new goog.math.Coordinate(scrollLeft, scrollTop); -}; +goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset; /** - * Gets the client rectangle of the DOM element. - * - * getBoundingClientRect is part of a new CSS object model draft (with a - * long-time presence in IE), replacing the error-prone parent offset - * computation and the now-deprecated Gecko getBoxObjectFor. - * - * This utility patches common browser bugs in getBoundingClientRect. It - * will fail if getBoundingClientRect is unsupported. - * - * If the element is not in the DOM, the result is undefined, and an error may - * be thrown depending on user agent. - * - * @param {!Element} el The element whose bounding rectangle is being queried. - * @return {Object} A native bounding rectangle with numerical left, top, - * right, and bottom. Reported by Firefox to be of object type ClientRect. - * @private + * Returns true if the object is a {@code NodeList}. To qualify as a NodeList, + * the object must have a numeric length property and an item function (which + * has type 'string' on IE for some reason). + * @param {Object} val Object to test. + * @return {boolean} Whether the object is a NodeList. */ -goog.style.getBoundingClientRect_ = function(el) { - var rect; - try { - rect = el.getBoundingClientRect(); - } catch (e) { - // In IE < 9, calling getBoundingClientRect on an orphan element raises an - // "Unspecified Error". All other browsers return zeros. - return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0}; - } +goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList; - // Patch the result in IE only, so that this function can be inlined if - // compiled for non-IE. - if (goog.userAgent.IE && el.ownerDocument.body) { - // In IE, most of the time, 2 extra pixels are added to the top and left - // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and - // IE6 standards mode, this border can be overridden by setting the - // document element's border to zero -- thus, we cannot rely on the - // offset always being 2 pixels. +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * tag name and/or class name. If the passed element matches the specified + * criteria, the element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or + * null/undefined to match only based on class name). + * @param {?string=} opt_class The class name to match (or null/undefined to + * match only based on tag name). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if no match is found. + */ +goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass = + goog.dom.getAncestorByTagNameAndClass; - // In quirks mode, the offset can be determined by querying the body's - // clientLeft/clientTop, but in standards mode, it is found by querying - // the document element's clientLeft/clientTop. Since we already called - // getBoundingClientRect we have already forced a reflow, so it is not - // too expensive just to query them all. - // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx - var doc = el.ownerDocument; - rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; - rect.top -= doc.documentElement.clientTop + doc.body.clientTop; - } - return /** @type {Object} */ (rect); -}; +/** + * Walks up the DOM hierarchy returning the first ancestor that has the passed + * class name. If the passed element matches the specified criteria, the + * element itself is returned. + * @param {Node} element The DOM node to start with. + * @param {string} class The class name to match. + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Element} The first ancestor that matches the passed criteria, or + * null if none match. + */ +goog.dom.DomHelper.prototype.getAncestorByClass = + goog.dom.getAncestorByClass; /** - * Returns the first parent that could affect the position of a given element. - * @param {Element} element The element to get the offset parent for. - * @return {Element} The first offset parent or null if one cannot be found. + * Walks up the DOM hierarchy returning the first ancestor that passes the + * matcher function. + * @param {Node} element The DOM node to start with. + * @param {function(Node) : boolean} matcher A function that returns true if the + * passed node matches the desired criteria. + * @param {boolean=} opt_includeNode If true, the node itself is included in + * the search (the first call to the matcher will pass startElement as + * the node to test). + * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the + * dom. + * @return {Node} DOM node that matched the matcher, or null if there was + * no match. */ -goog.style.getOffsetParent = function(element) { - // element.offsetParent does the right thing in IE7 and below. In other - // browsers it only includes elements with position absolute, relative or - // fixed, not elements with overflow set to auto or scroll. - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) { - return element.offsetParent; - } - - var doc = goog.dom.getOwnerDocument(element); - var positionStyle = goog.style.getStyle_(element, 'position'); - var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; - for (var parent = element.parentNode; parent && parent != doc; - parent = parent.parentNode) { - positionStyle = - goog.style.getStyle_(/** @type {!Element} */ (parent), 'position'); - skipStatic = skipStatic && positionStyle == 'static' && - parent != doc.documentElement && parent != doc.body; - if (!skipStatic && (parent.scrollWidth > parent.clientWidth || - parent.scrollHeight > parent.clientHeight || - positionStyle == 'fixed' || - positionStyle == 'absolute' || - positionStyle == 'relative')) { - return /** @type {!Element} */ (parent); - } - } - return null; -}; +goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor; +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Calculates and returns the visible rectangle for a given element. Returns a - * box describing the visible portion of the nearest scrollable offset ancestor. - * Coordinates are given relative to the document. + * @fileoverview Utilities for detecting, adding and removing classes. Prefer + * this over goog.dom.classes for new code since it attempts to use classList + * (DOMTokenList: http://dom.spec.whatwg.org/#domtokenlist) which is faster + * and requires less code. * - * @param {Element} element Element to get the visible rect for. - * @return {goog.math.Box} Bounding elementBox describing the visible rect or - * null if scrollable ancestor isn't inside the visible viewport. + * Note: these utilities are meant to operate on HTMLElements + * and may have unexpected behavior on elements with differing interfaces + * (such as SVGElements). */ -goog.style.getVisibleRectForElement = function(element) { - var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); - var dom = goog.dom.getDomHelper(element); - var body = dom.getDocument().body; - var documentElement = dom.getDocument().documentElement; - var scrollEl = dom.getDocumentScrollElement(); - // Determine the size of the visible rect by climbing the dom accounting for - // all scrollable containers. - for (var el = element; el = goog.style.getOffsetParent(el); ) { - // clientWidth is zero for inline block elements in IE. - // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0 - if ((!goog.userAgent.IE || el.clientWidth != 0) && - (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) && - // body may have overflow set on it, yet we still get the entire - // viewport. In some browsers, el.offsetParent may be - // document.documentElement, so check for that too. - (el != body && el != documentElement && - goog.style.getStyle_(el, 'overflow') != 'visible')) { - var pos = goog.style.getPageOffset(el); - var client = goog.style.getClientLeftTop(el); - pos.x += client.x; - pos.y += client.y; - visibleRect.top = Math.max(visibleRect.top, pos.y); - visibleRect.right = Math.min(visibleRect.right, - pos.x + el.clientWidth); - visibleRect.bottom = Math.min(visibleRect.bottom, - pos.y + el.clientHeight); - visibleRect.left = Math.max(visibleRect.left, pos.x); - } - } +goog.provide('goog.dom.classlist'); - // Clip by window's viewport. - var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; - visibleRect.left = Math.max(visibleRect.left, scrollX); - visibleRect.top = Math.max(visibleRect.top, scrollY); - var winSize = dom.getViewportSize(); - visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); - visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); - return visibleRect.top >= 0 && visibleRect.left >= 0 && - visibleRect.bottom > visibleRect.top && - visibleRect.right > visibleRect.left ? - visibleRect : null; -}; +goog.require('goog.array'); /** - * Calculate the scroll position of {@code container} with the minimum amount so - * that the content and the borders of the given {@code element} become visible. - * If the element is bigger than the container, its top left corner will be - * aligned as close to the container's top left corner as possible. - * - * @param {Element} element The element to make visible. - * @param {Element} container The container to scroll. - * @param {boolean=} opt_center Whether to center the element in the container. - * Defaults to false. - * @return {!goog.math.Coordinate} The new scroll position of the container, - * in form of goog.math.Coordinate(scrollLeft, scrollTop). + * Override this define at build-time if you know your target supports it. + * @define {boolean} Whether to use the classList property (DOMTokenList). */ -goog.style.getContainerOffsetToScrollInto = - function(element, container, opt_center) { - // Absolute position of the element's border's top left corner. - var elementPos = goog.style.getPageOffset(element); - // Absolute position of the container's border's top left corner. - var containerPos = goog.style.getPageOffset(container); - var containerBorder = goog.style.getBorderBox(container); - // Relative pos. of the element's border box to the container's content box. - var relX = elementPos.x - containerPos.x - containerBorder.left; - var relY = elementPos.y - containerPos.y - containerBorder.top; - // How much the element can move in the container, i.e. the difference between - // the element's bottom-right-most and top-left-most position where it's - // fully visible. - var spaceX = container.clientWidth - element.offsetWidth; - var spaceY = container.clientHeight - element.offsetHeight; +goog.define('goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST', false); - var scrollLeft = container.scrollLeft; - var scrollTop = container.scrollTop; - if (opt_center) { - // All browsers round non-integer scroll positions down. - scrollLeft += relX - spaceX / 2; - scrollTop += relY - spaceY / 2; - } else { - // This formula was designed to give the correct scroll values in the - // following cases: - // - element is higher than container (spaceY < 0) => scroll down by relY - // - element is not higher that container (spaceY >= 0): - // - it is above container (relY < 0) => scroll up by abs(relY) - // - it is below container (relY > spaceY) => scroll down by relY - spaceY - // - it is in the container => don't scroll - scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0)); - scrollTop += Math.min(relY, Math.max(relY - spaceY, 0)); + +/** + * Gets an array-like object of class names on an element. + * @param {Element} element DOM node to get the classes of. + * @return {!goog.array.ArrayLike} Class names on {@code element}. + */ +goog.dom.classlist.get = function(element) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + return element.classList; } - return new goog.math.Coordinate(scrollLeft, scrollTop); + + var className = element.className; + // Some types of elements don't have a className in IE (e.g. iframes). + // Furthermore, in Firefox, className is not a string when the element is + // an SVG element. + return goog.isString(className) && className.match(/\S+/g) || []; }; /** - * Changes the scroll position of {@code container} with the minimum amount so - * that the content and the borders of the given {@code element} become visible. - * If the element is bigger than the container, its top left corner will be - * aligned as close to the container's top left corner as possible. - * - * @param {Element} element The element to make visible. - * @param {Element} container The container to scroll. - * @param {boolean=} opt_center Whether to center the element in the container. - * Defaults to false. + * Sets the entire class name of an element. + * @param {Element} element DOM node to set class of. + * @param {string} className Class name(s) to apply to element. */ -goog.style.scrollIntoContainerView = function(element, container, opt_center) { - var offset = - goog.style.getContainerOffsetToScrollInto(element, container, opt_center); - container.scrollLeft = offset.x; - container.scrollTop = offset.y; +goog.dom.classlist.set = function(element, className) { + element.className = className; }; /** - * Returns clientLeft (width of the left border and, if the directionality is - * right to left, the vertical scrollbar) and clientTop as a coordinate object. - * - * @param {Element} el Element to get clientLeft for. - * @return {!goog.math.Coordinate} Client left and top. + * Returns true if an element has a class. This method may throw a DOM + * exception for an invalid or empty class name if DOMTokenList is used. + * @param {Element} element DOM node to test. + * @param {string} className Class name to test for. + * @return {boolean} Whether element has the class. */ -goog.style.getClientLeftTop = function(el) { - // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see - // https://bugzilla.mozilla.org/show_bug.cgi?id=111207 - if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) { - var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth')); - if (goog.style.isRightToLeft(el)) { - var scrollbarWidth = el.offsetWidth - el.clientWidth - left - - parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth')); - left += scrollbarWidth; - } - return new goog.math.Coordinate(left, - parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth'))); +goog.dom.classlist.contains = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + return element.classList.contains(className); } - - return new goog.math.Coordinate(el.clientLeft, el.clientTop); + return goog.array.contains(goog.dom.classlist.get(element), className); }; /** - * Returns a Coordinate object relative to the top-left of the HTML document. - * Implemented as a single function to save having to do two recursive loops in - * opera and safari just to get both coordinates. If you just want one value do - * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but - * note if you call both those methods the tree will be analysed twice. - * - * @param {Element} el Element to get the page offset for. - * @return {!goog.math.Coordinate} The page offset. + * Adds a class to an element. Does not add multiples of class names. This + * method may throw a DOM exception for an invalid or empty class name if + * DOMTokenList is used. + * @param {Element} element DOM node to add class to. + * @param {string} className Class name to add. */ -goog.style.getPageOffset = function(el) { - var box, doc = goog.dom.getOwnerDocument(el); - var positionStyle = goog.style.getStyle_(el, 'position'); - // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe. - goog.asserts.assertObject(el, 'Parameter is required'); - - // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the - // position. When invoked for an element with position absolute and a negative - // position though it can be off by one. Therefor the recursive implementation - // is used in those (relatively rare) cases. - var BUGGY_GECKO_BOX_OBJECT = - !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS && - goog.userAgent.GECKO && doc.getBoxObjectFor && - !el.getBoundingClientRect && positionStyle == 'absolute' && - (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0); - - // NOTE(arv): If element is hidden (display none or disconnected or any the - // ancestors are hidden) we get (0,0) by default but we still do the - // accumulation of scroll position. - - // TODO(arv): Should we check if the node is disconnected and in that case - // return (0,0)? - - var pos = new goog.math.Coordinate(0, 0); - var viewportElement = goog.style.getClientViewportElement(doc); - if (el == viewportElement) { - // viewport is always at 0,0 as that defined the coordinate system for this - // function - this avoids special case checks in the code below - return pos; +goog.dom.classlist.add = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + element.classList.add(className); + return; } - // IE, Gecko 1.9+, and most modern WebKit. - if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS || - el.getBoundingClientRect) { - box = goog.style.getBoundingClientRect_(el); - // Must add the scroll coordinates in to get the absolute page offset - // of element since getBoundingClientRect returns relative coordinates to - // the viewport. - var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); - pos.x = box.left + scrollCoord.x; - pos.y = box.top + scrollCoord.y; - - // Gecko prior to 1.9. - } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { - // Gecko ignores the scroll values for ancestors, up to 1.9. See: - // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and - // https://bugzilla.mozilla.org/show_bug.cgi?id=330619 - - box = doc.getBoxObjectFor(el); - // TODO(user): Fix the off-by-one error when window is scrolled down - // or right more than 1 pixel. The viewport offset does not move in lock - // step with the window scroll; it moves in increments of 2px and at - // somewhat random intervals. - var vpBox = doc.getBoxObjectFor(viewportElement); - pos.x = box.screenX - vpBox.screenX; - pos.y = box.screenY - vpBox.screenY; - - // Safari, Opera and Camino up to 1.0.4. - } else { - var parent = el; - do { - pos.x += parent.offsetLeft; - pos.y += parent.offsetTop; - // For safari/chrome, we need to add parent's clientLeft/Top as well. - if (parent != el) { - pos.x += parent.clientLeft || 0; - pos.y += parent.clientTop || 0; - } - // In Safari when hit a position fixed element the rest of the offsets - // are not correct. - if (goog.userAgent.WEBKIT && - goog.style.getComputedPosition(parent) == 'fixed') { - pos.x += doc.body.scrollLeft; - pos.y += doc.body.scrollTop; - break; - } - parent = parent.offsetParent; - } while (parent && parent != el); - - // Opera & (safari absolute) incorrectly account for body offsetTop. - if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT && - positionStyle == 'absolute')) { - pos.y -= doc.body.offsetTop; - } - - for (parent = el; (parent = goog.style.getOffsetParent(parent)) && - parent != doc.body && parent != viewportElement; ) { - pos.x -= parent.scrollLeft; - // Workaround for a bug in Opera 9.2 (and earlier) where table rows may - // report an invalid scroll top value. The bug was fixed in Opera 9.5 - // however as that version supports getBoundingClientRect it won't - // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965 - if (!goog.userAgent.OPERA || parent.tagName != 'TR') { - pos.y -= parent.scrollTop; - } - } + if (!goog.dom.classlist.contains(element, className)) { + // Ensure we add a space if this is not the first class name added. + element.className += element.className.length > 0 ? + (' ' + className) : className; } - - return pos; }; /** - * Returns the left coordinate of an element relative to the HTML document - * @param {Element} el Elements. - * @return {number} The left coordinate. + * Convenience method to add a number of class names at once. + * @param {Element} element The element to which to add classes. + * @param {goog.array.ArrayLike<string>} classesToAdd An array-like object + * containing a collection of class names to add to the element. + * This method may throw a DOM exception if classesToAdd contains invalid + * or empty class names. */ -goog.style.getPageOffsetLeft = function(el) { - return goog.style.getPageOffset(el).x; -}; +goog.dom.classlist.addAll = function(element, classesToAdd) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + goog.array.forEach(classesToAdd, function(className) { + goog.dom.classlist.add(element, className); + }); + return; + } + var classMap = {}; -/** - * Returns the top coordinate of an element relative to the HTML document - * @param {Element} el Elements. - * @return {number} The top coordinate. - */ -goog.style.getPageOffsetTop = function(el) { - return goog.style.getPageOffset(el).y; + // Get all current class names into a map. + goog.array.forEach(goog.dom.classlist.get(element), + function(className) { + classMap[className] = true; + }); + + // Add new class names to the map. + goog.array.forEach(classesToAdd, + function(className) { + classMap[className] = true; + }); + + // Flatten the keys of the map into the className. + element.className = ''; + for (var className in classMap) { + element.className += element.className.length > 0 ? + (' ' + className) : className; + } }; /** - * Returns a Coordinate object relative to the top-left of an HTML document - * in an ancestor frame of this element. Used for measuring the position of - * an element inside a frame relative to a containing frame. - * - * @param {Element} el Element to get the page offset for. - * @param {Window} relativeWin The window to measure relative to. If relativeWin - * is not in the ancestor frame chain of the element, we measure relative to - * the top-most window. - * @return {!goog.math.Coordinate} The page offset. + * Removes a class from an element. This method may throw a DOM exception + * for an invalid or empty class name if DOMTokenList is used. + * @param {Element} element DOM node to remove class from. + * @param {string} className Class name to remove. */ -goog.style.getFramedPageOffset = function(el, relativeWin) { - var position = new goog.math.Coordinate(0, 0); - - // Iterate up the ancestor frame chain, keeping track of the current window - // and the current element in that window. - var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); - var currentEl = el; - do { - // if we're at the top window, we want to get the page offset. - // if we're at an inner frame, we only want to get the window position - // so that we can determine the actual page offset in the context of - // the outer window. - var offset = currentWin == relativeWin ? - goog.style.getPageOffset(currentEl) : - goog.style.getClientPositionForElement_( - goog.asserts.assert(currentEl)); - - position.x += offset.x; - position.y += offset.y; - } while (currentWin && currentWin != relativeWin && - (currentEl = currentWin.frameElement) && - (currentWin = currentWin.parent)); +goog.dom.classlist.remove = function(element, className) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + element.classList.remove(className); + return; + } - return position; + if (goog.dom.classlist.contains(element, className)) { + // Filter out the class name. + element.className = goog.array.filter( + goog.dom.classlist.get(element), + function(c) { + return c != className; + }).join(' '); + } }; /** - * Translates the specified rect relative to origBase page, for newBase page. - * If origBase and newBase are the same, this function does nothing. - * - * @param {goog.math.Rect} rect The source rectangle relative to origBase page, - * and it will have the translated result. - * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle. - * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant - * coordinate. This must be a DOM for an ancestor frame of origBase - * or the same as origBase. + * Removes a set of classes from an element. Prefer this call to + * repeatedly calling {@code goog.dom.classlist.remove} if you want to remove + * a large set of class names at once. + * @param {Element} element The element from which to remove classes. + * @param {goog.array.ArrayLike<string>} classesToRemove An array-like object + * containing a collection of class names to remove from the element. + * This method may throw a DOM exception if classesToRemove contains invalid + * or empty class names. */ -goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { - if (origBase.getDocument() != newBase.getDocument()) { - var body = origBase.getDocument().body; - var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); - - // Adjust Body's margin. - pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); +goog.dom.classlist.removeAll = function(element, classesToRemove) { + if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) { + goog.array.forEach(classesToRemove, function(className) { + goog.dom.classlist.remove(element, className); + }); + return; + } + // Filter out those classes in classesToRemove. + element.className = goog.array.filter( + goog.dom.classlist.get(element), + function(className) { + // If this class is not one we are trying to remove, + // add it to the array of new class names. + return !goog.array.contains(classesToRemove, className); + }).join(' '); +}; - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && - !origBase.isCss1CompatMode()) { - pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); - } - rect.left += pos.x; - rect.top += pos.y; +/** + * Adds or removes a class depending on the enabled argument. This method + * may throw a DOM exception for an invalid or empty class name if DOMTokenList + * is used. + * @param {Element} element DOM node to add or remove the class on. + * @param {string} className Class name to add or remove. + * @param {boolean} enabled Whether to add or remove the class (true adds, + * false removes). + */ +goog.dom.classlist.enable = function(element, className, enabled) { + if (enabled) { + goog.dom.classlist.add(element, className); + } else { + goog.dom.classlist.remove(element, className); } }; /** - * Returns the position of an element relative to another element in the - * document. A relative to B - * @param {Element|Event|goog.events.Event} a Element or mouse event whose - * position we're calculating. - * @param {Element|Event|goog.events.Event} b Element or mouse event position - * is relative to. - * @return {!goog.math.Coordinate} The relative position. + * Adds or removes a set of classes depending on the enabled argument. This + * method may throw a DOM exception for an invalid or empty class name if + * DOMTokenList is used. + * @param {!Element} element DOM node to add or remove the class on. + * @param {goog.array.ArrayLike<string>} classesToEnable An array-like object + * containing a collection of class names to add or remove from the element. + * @param {boolean} enabled Whether to add or remove the classes (true adds, + * false removes). */ -goog.style.getRelativePosition = function(a, b) { - var ap = goog.style.getClientPosition(a); - var bp = goog.style.getClientPosition(b); - return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); +goog.dom.classlist.enableAll = function(element, classesToEnable, enabled) { + var f = enabled ? goog.dom.classlist.addAll : + goog.dom.classlist.removeAll; + f(element, classesToEnable); }; /** - * Returns the position of the event or the element's border box relative to - * the client viewport. - * @param {!Element} el Element whose position to get. - * @return {!goog.math.Coordinate} The position. - * @private + * Switches a class on an element from one to another without disturbing other + * classes. If the fromClass isn't removed, the toClass won't be added. This + * method may throw a DOM exception if the class names are empty or invalid. + * @param {Element} element DOM node to swap classes on. + * @param {string} fromClass Class to remove. + * @param {string} toClass Class to add. + * @return {boolean} Whether classes were switched. */ -goog.style.getClientPositionForElement_ = function(el) { - var pos; - if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS || - el.getBoundingClientRect) { - // IE, Gecko 1.9+, and most modern WebKit - var box = goog.style.getBoundingClientRect_(el); - pos = new goog.math.Coordinate(box.left, box.top); - } else { - var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll(); - var pageCoord = goog.style.getPageOffset(el); - pos = new goog.math.Coordinate( - pageCoord.x - scrollCoord.x, - pageCoord.y - scrollCoord.y); - } - - // Gecko below version 12 doesn't add CSS translation to the client position - // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do - // so manually. - if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) { - return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el)); - } else { - return pos; +goog.dom.classlist.swap = function(element, fromClass, toClass) { + if (goog.dom.classlist.contains(element, fromClass)) { + goog.dom.classlist.remove(element, fromClass); + goog.dom.classlist.add(element, toClass); + return true; } + return false; }; /** - * Returns the position of the event or the element's border box relative to - * the client viewport. - * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event. - * @return {!goog.math.Coordinate} The position. + * Removes a class if an element has it, and adds it the element doesn't have + * it. Won't affect other classes on the node. This method may throw a DOM + * exception if the class name is empty or invalid. + * @param {Element} element DOM node to toggle class on. + * @param {string} className Class to toggle. + * @return {boolean} True if class was added, false if it was removed + * (in other words, whether element has the class after this function has + * been called). */ -goog.style.getClientPosition = function(el) { - goog.asserts.assert(el); - if (el.nodeType == goog.dom.NodeType.ELEMENT) { - return goog.style.getClientPositionForElement_( - /** @type {!Element} */ (el)); - } else { - var isAbstractedEvent = goog.isFunction(el.getBrowserEvent); - var be = /** @type {!goog.events.BrowserEvent} */ (el); - var targetEvent = el; - - if (el.targetTouches && el.targetTouches.length) { - targetEvent = el.targetTouches[0]; - } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches && - be.getBrowserEvent().targetTouches.length) { - targetEvent = be.getBrowserEvent().targetTouches[0]; - } - - return new goog.math.Coordinate( - targetEvent.clientX, - targetEvent.clientY); - } +goog.dom.classlist.toggle = function(element, className) { + var add = !goog.dom.classlist.contains(element, className); + goog.dom.classlist.enable(element, className, add); + return add; }; /** - * Moves an element to the given coordinates relative to the client viewport. - * @param {Element} el Absolutely positioned element to set page offset for. - * It must be in the document. - * @param {number|goog.math.Coordinate} x Left position of the element's margin - * box or a coordinate object. - * @param {number=} opt_y Top position of the element's margin box. + * Adds and removes a class of an element. Unlike + * {@link goog.dom.classlist.swap}, this method adds the classToAdd regardless + * of whether the classToRemove was present and had been removed. This method + * may throw a DOM exception if the class names are empty or invalid. + * + * @param {Element} element DOM node to swap classes on. + * @param {string} classToRemove Class to remove. + * @param {string} classToAdd Class to add. */ -goog.style.setPageOffset = function(el, x, opt_y) { - // Get current pageoffset - var cur = goog.style.getPageOffset(el); +goog.dom.classlist.addRemove = function(element, classToRemove, classToAdd) { + goog.dom.classlist.remove(element, classToRemove); + goog.dom.classlist.add(element, classToAdd); +}; - if (x instanceof goog.math.Coordinate) { - opt_y = x.y; - x = x.x; - } +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. - // NOTE(arv): We cannot allow strings for x and y. We could but that would - // require us to manually transform between different units +/** + * @fileoverview Vendor prefix getters. + */ - // Work out deltas - var dx = x - cur.x; - var dy = opt_y - cur.y; +goog.provide('goog.dom.vendor'); - // Set position to current left/top + delta - goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy); -}; +goog.require('goog.string'); +goog.require('goog.userAgent'); /** - * Sets the width/height values of an element. If an argument is numeric, - * or a goog.math.Size is passed, it is assumed to be pixels and will add - * 'px' after converting it to an integer in string form. (This just sets the - * CSS width and height properties so it might set content-box or border-box - * size depending on the box model the browser is using.) + * Returns the JS vendor prefix used in CSS properties. Different vendors + * use different methods of changing the case of the property names. * - * @param {Element} element Element to set the size of. - * @param {string|number|goog.math.Size} w Width of the element, or a - * size object. - * @param {string|number=} opt_h Height of the element. Required if w is not a - * size object. + * @return {?string} The JS vendor prefix or null if there is none. */ -goog.style.setSize = function(element, w, opt_h) { - var h; - if (w instanceof goog.math.Size) { - h = w.height; - w = w.width; - } else { - if (opt_h == undefined) { - throw Error('missing height argument'); - } - h = opt_h; +goog.dom.vendor.getVendorJsPrefix = function() { + if (goog.userAgent.WEBKIT) { + return 'Webkit'; + } else if (goog.userAgent.GECKO) { + return 'Moz'; + } else if (goog.userAgent.IE) { + return 'ms'; + } else if (goog.userAgent.OPERA) { + return 'O'; } - goog.style.setWidth(element, /** @type {string|number} */ (w)); - goog.style.setHeight(element, /** @type {string|number} */ (h)); + return null; }; /** - * Helper function to create a string to be set into a pixel-value style - * property of an element. Can round to the nearest integer value. + * Returns the vendor prefix used in CSS properties. * - * @param {string|number} value The style value to be used. If a number, - * 'px' will be appended, otherwise the value will be applied directly. - * @param {boolean} round Whether to round the nearest integer (if property - * is a number). - * @return {string} The string value for the property. - * @private + * @return {?string} The vendor prefix or null if there is none. */ -goog.style.getPixelStyleValue_ = function(value, round) { - if (typeof value == 'number') { - value = (round ? Math.round(value) : value) + 'px'; +goog.dom.vendor.getVendorPrefix = function() { + if (goog.userAgent.WEBKIT) { + return '-webkit'; + } else if (goog.userAgent.GECKO) { + return '-moz'; + } else if (goog.userAgent.IE) { + return '-ms'; + } else if (goog.userAgent.OPERA) { + return '-o'; } - return value; + return null; }; /** - * Set the height of an element. Sets the element's style property. - * @param {Element} element Element to set the height of. - * @param {string|number} height The height value to set. If a number, 'px' - * will be appended, otherwise the value will be applied directly. + * @param {string} propertyName A property name. + * @param {!Object=} opt_object If provided, we verify if the property exists in + * the object. + * @return {?string} A vendor prefixed property name, or null if it does not + * exist. */ -goog.style.setHeight = function(element, height) { - element.style.height = goog.style.getPixelStyleValue_(height, true); +goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) { + // We first check for a non-prefixed property, if available. + if (opt_object && propertyName in opt_object) { + return propertyName; + } + var prefix = goog.dom.vendor.getVendorJsPrefix(); + if (prefix) { + prefix = prefix.toLowerCase(); + var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName); + return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ? + prefixedPropertyName : null; + } + return null; }; /** - * Set the width of an element. Sets the element's style property. - * @param {Element} element Element to set the width of. - * @param {string|number} width The width value to set. If a number, 'px' - * will be appended, otherwise the value will be applied directly. + * @param {string} eventType An event type. + * @return {string} A lower-cased vendor prefixed event type. */ -goog.style.setWidth = function(element, width) { - element.style.width = goog.style.getPixelStyleValue_(width, true); +goog.dom.vendor.getPrefixedEventType = function(eventType) { + var prefix = goog.dom.vendor.getVendorJsPrefix() || ''; + return (prefix + eventType).toLowerCase(); }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Gets the height and width of an element, even if its display is none. + * @fileoverview A utility class for representing a numeric box. + */ + + +goog.provide('goog.math.Box'); + +goog.require('goog.math.Coordinate'); + + + +/** + * Class for representing a box. A box is specified as a top, right, bottom, + * and left. A box is useful for representing margins and padding. * - * Specifically, this returns the height and width of the border box, - * irrespective of the box model in effect. + * This class assumes 'screen coordinates': larger Y coordinates are further + * from the top of the screen. * - * Note that this function does not take CSS transforms into account. Please see - * {@code goog.style.getTransformedSize}. - * @param {Element} element Element to get size of. - * @return {!goog.math.Size} Object with width/height properties. + * @param {number} top Top. + * @param {number} right Right. + * @param {number} bottom Bottom. + * @param {number} left Left. + * @struct + * @constructor */ -goog.style.getSize = function(element) { - return goog.style.evaluateWithTemporaryDisplay_( - goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element)); +goog.math.Box = function(top, right, bottom, left) { + /** + * Top + * @type {number} + */ + this.top = top; + + /** + * Right + * @type {number} + */ + this.right = right; + + /** + * Bottom + * @type {number} + */ + this.bottom = bottom; + + /** + * Left + * @type {number} + */ + this.left = left; }; /** - * Call {@code fn} on {@code element} such that {@code element}'s dimensions are - * accurate when it's passed to {@code fn}. - * @param {function(!Element): T} fn Function to call with {@code element} as - * an argument after temporarily changing {@code element}'s display such - * that its dimensions are accurate. - * @param {!Element} element Element (which may have display none) to use as - * argument to {@code fn}. - * @return {T} Value returned by calling {@code fn} with {@code element}. - * @template T - * @private + * Creates a Box by bounding a collection of goog.math.Coordinate objects + * @param {...goog.math.Coordinate} var_args Coordinates to be included inside + * the box. + * @return {!goog.math.Box} A Box containing all the specified Coordinates. */ -goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) { - if (goog.style.getStyle_(element, 'display') != 'none') { - return fn(element); +goog.math.Box.boundingBox = function(var_args) { + var box = new goog.math.Box(arguments[0].y, arguments[0].x, + arguments[0].y, arguments[0].x); + for (var i = 1; i < arguments.length; i++) { + var coord = arguments[i]; + box.top = Math.min(box.top, coord.y); + box.right = Math.max(box.right, coord.x); + box.bottom = Math.max(box.bottom, coord.y); + box.left = Math.min(box.left, coord.x); } + return box; +}; - var style = element.style; - var originalDisplay = style.display; - var originalVisibility = style.visibility; - var originalPosition = style.position; - - style.visibility = 'hidden'; - style.position = 'absolute'; - style.display = 'inline'; - var retVal = fn(element); +/** + * @return {number} width The width of this Box. + */ +goog.math.Box.prototype.getWidth = function() { + return this.right - this.left; +}; - style.display = originalDisplay; - style.position = originalPosition; - style.visibility = originalVisibility; - return retVal; +/** + * @return {number} height The height of this Box. + */ +goog.math.Box.prototype.getHeight = function() { + return this.bottom - this.top; }; /** - * Gets the height and width of an element when the display is not none. - * @param {Element} element Element to get size of. - * @return {!goog.math.Size} Object with width/height properties. - * @private + * Creates a copy of the box with the same dimensions. + * @return {!goog.math.Box} A clone of this Box. */ -goog.style.getSizeWithDisplay_ = function(element) { - var offsetWidth = element.offsetWidth; - var offsetHeight = element.offsetHeight; - var webkitOffsetsZero = - goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight; - if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) && - element.getBoundingClientRect) { - // Fall back to calling getBoundingClientRect when offsetWidth or - // offsetHeight are not defined, or when they are zero in WebKit browsers. - // This makes sure that we return for the correct size for SVG elements, but - // will still return 0 on Webkit prior to 534.8, see - // http://trac.webkit.org/changeset/67252. - var clientRect = goog.style.getBoundingClientRect_(element); - return new goog.math.Size(clientRect.right - clientRect.left, - clientRect.bottom - clientRect.top); - } - return new goog.math.Size(offsetWidth, offsetHeight); +goog.math.Box.prototype.clone = function() { + return new goog.math.Box(this.top, this.right, this.bottom, this.left); }; -/** - * Gets the height and width of an element, post transform, even if its display - * is none. - * - * This is like {@code goog.style.getSize}, except: - * <ol> - * <li>Takes webkitTransforms such as rotate and scale into account. - * <li>Will return null if {@code element} doesn't respond to - * {@code getBoundingClientRect}. - * <li>Currently doesn't make sense on non-WebKit browsers which don't support - * webkitTransforms. - * </ol> - * @param {!Element} element Element to get size of. - * @return {goog.math.Size} Object with width/height properties. - */ -goog.style.getTransformedSize = function(element) { - if (!element.getBoundingClientRect) { - return null; - } - - var clientRect = goog.style.evaluateWithTemporaryDisplay_( - goog.style.getBoundingClientRect_, element); - return new goog.math.Size(clientRect.right - clientRect.left, - clientRect.bottom - clientRect.top); -}; +if (goog.DEBUG) { + /** + * Returns a nice string representing the box. + * @return {string} In the form (50t, 73r, 24b, 13l). + * @override + */ + goog.math.Box.prototype.toString = function() { + return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' + + this.left + 'l)'; + }; +} /** - * Returns a bounding rectangle for a given element in page space. - * @param {Element} element Element to get bounds of. Must not be display none. - * @return {!goog.math.Rect} Bounding rectangle for the element. + * Returns whether the box contains a coordinate or another box. + * + * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. + * @return {boolean} Whether the box contains the coordinate or other box. */ -goog.style.getBounds = function(element) { - var o = goog.style.getPageOffset(element); - var s = goog.style.getSize(element); - return new goog.math.Rect(o.x, o.y, s.width, s.height); +goog.math.Box.prototype.contains = function(other) { + return goog.math.Box.contains(this, other); }; /** - * Converts a CSS selector in the form style-property to styleProperty. - * @param {*} selector CSS Selector. - * @return {string} Camel case selector. - * @deprecated Use goog.string.toCamelCase instead. + * Expands box with the given margins. + * + * @param {number|goog.math.Box} top Top margin or box with all margins. + * @param {number=} opt_right Right margin. + * @param {number=} opt_bottom Bottom margin. + * @param {number=} opt_left Left margin. + * @return {!goog.math.Box} A reference to this Box. */ -goog.style.toCamelCase = function(selector) { - return goog.string.toCamelCase(String(selector)); +goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom, + opt_left) { + if (goog.isObject(top)) { + this.top -= top.top; + this.right += top.right; + this.bottom += top.bottom; + this.left -= top.left; + } else { + this.top -= top; + this.right += opt_right; + this.bottom += opt_bottom; + this.left -= opt_left; + } + + return this; }; /** - * Converts a CSS selector in the form styleProperty to style-property. - * @param {string} selector Camel case selector. - * @return {string} Selector cased. - * @deprecated Use goog.string.toSelectorCase instead. + * Expand this box to include another box. + * NOTE(user): This is used in code that needs to be very fast, please don't + * add functionality to this function at the expense of speed (variable + * arguments, accepting multiple argument types, etc). + * @param {goog.math.Box} box The box to include in this one. */ -goog.style.toSelectorCase = function(selector) { - return goog.string.toSelectorCase(selector); +goog.math.Box.prototype.expandToInclude = function(box) { + this.left = Math.min(this.left, box.left); + this.top = Math.min(this.top, box.top); + this.right = Math.max(this.right, box.right); + this.bottom = Math.max(this.bottom, box.bottom); }; /** - * Gets the opacity of a node (x-browser). This gets the inline style opacity - * of the node, and does not take into account the cascaded or the computed - * style for this node. - * @param {Element} el Element whose opacity has to be found. - * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} - * if the opacity is not set. + * Compares boxes for equality. + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A Box. + * @return {boolean} True iff the boxes are equal, or if both are null. */ -goog.style.getOpacity = function(el) { - var style = el.style; - var result = ''; - if ('opacity' in style) { - result = style.opacity; - } else if ('MozOpacity' in style) { - result = style.MozOpacity; - } else if ('filter' in style) { - var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); - if (match) { - result = String(match[1] / 100); - } +goog.math.Box.equals = function(a, b) { + if (a == b) { + return true; } - return result == '' ? result : Number(result); + if (!a || !b) { + return false; + } + return a.top == b.top && a.right == b.right && + a.bottom == b.bottom && a.left == b.left; }; /** - * Sets the opacity of a node (x-browser). - * @param {Element} el Elements whose opacity has to be set. - * @param {number|string} alpha Opacity between 0 and 1 or an empty string - * {@code ''} to clear the opacity. + * Returns whether a box contains a coordinate or another box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box. + * @return {boolean} Whether the box contains the coordinate or other box. */ -goog.style.setOpacity = function(el, alpha) { - var style = el.style; - if ('opacity' in style) { - style.opacity = alpha; - } else if ('MozOpacity' in style) { - style.MozOpacity = alpha; - } else if ('filter' in style) { - // TODO(arv): Overwriting the filter might have undesired side effects. - if (alpha === '') { - style.filter = ''; - } else { - style.filter = 'alpha(opacity=' + alpha * 100 + ')'; - } +goog.math.Box.contains = function(box, other) { + if (!box || !other) { + return false; } + + if (other instanceof goog.math.Box) { + return other.left >= box.left && other.right <= box.right && + other.top >= box.top && other.bottom <= box.bottom; + } + + // other is a Coordinate. + return other.x >= box.left && other.x <= box.right && + other.y >= box.top && other.y <= box.bottom; }; /** - * Sets the background of an element to a transparent image in a browser- - * independent manner. - * - * This function does not support repeating backgrounds or alternate background - * positions to match the behavior of Internet Explorer. It also does not - * support sizingMethods other than crop since they cannot be replicated in - * browsers other than Internet Explorer. + * Returns the relative x position of a coordinate compared to a box. Returns + * zero if the coordinate is inside the box. * - * @param {Element} el The element to set background on. - * @param {string} src The image source URL. + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The x position of {@code coord} relative to the nearest + * side of {@code box}, or zero if {@code coord} is inside {@code box}. */ -goog.style.setTransparentBackgroundImage = function(el, src) { - var style = el.style; - // It is safe to use the style.filter in IE only. In Safari 'filter' is in - // style object but access to style.filter causes it to throw an exception. - // Note: IE8 supports images with an alpha channel. - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { - // See TODO in setOpacity. - style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + - 'src="' + src + '", sizingMethod="crop")'; - } else { - // Set style properties individually instead of using background shorthand - // to prevent overwriting a pre-existing background color. - style.backgroundImage = 'url(' + src + ')'; - style.backgroundPosition = 'top left'; - style.backgroundRepeat = 'no-repeat'; +goog.math.Box.relativePositionX = function(box, coord) { + if (coord.x < box.left) { + return coord.x - box.left; + } else if (coord.x > box.right) { + return coord.x - box.right; } + return 0; }; /** - * Clears the background image of an element in a browser independent manner. - * @param {Element} el The element to clear background image for. + * Returns the relative y position of a coordinate compared to a box. Returns + * zero if the coordinate is inside the box. + * + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The y position of {@code coord} relative to the nearest + * side of {@code box}, or zero if {@code coord} is inside {@code box}. */ -goog.style.clearTransparentBackgroundImage = function(el) { - var style = el.style; - if ('filter' in style) { - // See TODO in setOpacity. - style.filter = ''; - } else { - // Set style properties individually instead of using background shorthand - // to prevent overwriting a pre-existing background color. - style.backgroundImage = 'none'; +goog.math.Box.relativePositionY = function(box, coord) { + if (coord.y < box.top) { + return coord.y - box.top; + } else if (coord.y > box.bottom) { + return coord.y - box.bottom; } + return 0; }; /** - * Shows or hides an element from the page. Hiding the element is done by - * setting the display property to "none", removing the element from the - * rendering hierarchy so it takes up no space. To show the element, the default - * inherited display property is restored (defined either in stylesheets or by - * the browser's default style rules.) - * - * Caveat 1: if the inherited display property for the element is set to "none" - * by the stylesheets, that is the property that will be restored by a call to - * showElement(), effectively toggling the display between "none" and "none". + * Returns the distance between a coordinate and the nearest corner/side of a + * box. Returns zero if the coordinate is inside the box. * - * Caveat 2: if the element display style is set inline (by setting either - * element.style.display or a style attribute in the HTML), a call to - * showElement will clear that setting and defer to the inherited style in the - * stylesheet. - * @param {Element} el Element to show or hide. - * @param {*} display True to render the element in its default style, - * false to disable rendering the element. - * @deprecated Use goog.style.setElementShown instead. + * @param {goog.math.Box} box A Box. + * @param {goog.math.Coordinate} coord A Coordinate. + * @return {number} The distance between {@code coord} and the nearest + * corner/side of {@code box}, or zero if {@code coord} is inside + * {@code box}. */ -goog.style.showElement = function(el, display) { - goog.style.setElementShown(el, display); +goog.math.Box.distance = function(box, coord) { + var x = goog.math.Box.relativePositionX(box, coord); + var y = goog.math.Box.relativePositionY(box, coord); + return Math.sqrt(x * x + y * y); }; /** - * Shows or hides an element from the page. Hiding the element is done by - * setting the display property to "none", removing the element from the - * rendering hierarchy so it takes up no space. To show the element, the default - * inherited display property is restored (defined either in stylesheets or by - * the browser's default style rules). - * - * Caveat 1: if the inherited display property for the element is set to "none" - * by the stylesheets, that is the property that will be restored by a call to - * setElementShown(), effectively toggling the display between "none" and - * "none". + * Returns whether two boxes intersect. * - * Caveat 2: if the element display style is set inline (by setting either - * element.style.display or a style attribute in the HTML), a call to - * setElementShown will clear that setting and defer to the inherited style in - * the stylesheet. - * @param {Element} el Element to show or hide. - * @param {*} isShown True to render the element in its default style, - * false to disable rendering the element. + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A second Box. + * @return {boolean} Whether the boxes intersect. */ -goog.style.setElementShown = function(el, isShown) { - el.style.display = isShown ? '' : 'none'; +goog.math.Box.intersects = function(a, b) { + return (a.left <= b.right && b.left <= a.right && + a.top <= b.bottom && b.top <= a.bottom); }; /** - * Test whether the given element has been shown or hidden via a call to - * {@link #setElementShown}. - * - * Note this is strictly a companion method for a call - * to {@link #setElementShown} and the same caveats apply; in particular, this - * method does not guarantee that the return value will be consistent with - * whether or not the element is actually visible. + * Returns whether two boxes would intersect with additional padding. * - * @param {Element} el The element to test. - * @return {boolean} Whether the element has been shown. - * @see #setElementShown + * @param {goog.math.Box} a A Box. + * @param {goog.math.Box} b A second Box. + * @param {number} padding The additional padding. + * @return {boolean} Whether the boxes intersect. */ -goog.style.isElementShown = function(el) { - return el.style.display != 'none'; +goog.math.Box.intersectsWithPadding = function(a, b, padding) { + return (a.left <= b.right + padding && b.left <= a.right + padding && + a.top <= b.bottom + padding && b.top <= a.bottom + padding); }; /** - * Installs the styles string into the window that contains opt_element. If - * opt_element is null, the main window is used. - * @param {string} stylesString The style string to install. - * @param {Node=} opt_node Node whose parent document should have the - * styles installed. - * @return {Element|StyleSheet} The style element created. + * Rounds the fields to the next larger integer values. + * + * @return {!goog.math.Box} This box with ceil'd fields. */ -goog.style.installStyles = function(stylesString, opt_node) { - var dh = goog.dom.getDomHelper(opt_node); - var styleSheet = null; +goog.math.Box.prototype.ceil = function() { + this.top = Math.ceil(this.top); + this.right = Math.ceil(this.right); + this.bottom = Math.ceil(this.bottom); + this.left = Math.ceil(this.left); + return this; +}; - // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be - // undefined as of IE 11. - var doc = dh.getDocument(); - if (goog.userAgent.IE && doc.createStyleSheet) { - styleSheet = doc.createStyleSheet(); - goog.style.setStyles(styleSheet, stylesString); - } else { - var head = dh.getElementsByTagNameAndClass('head')[0]; - // In opera documents are not guaranteed to have a head element, thus we - // have to make sure one exists before using it. - if (!head) { - var body = dh.getElementsByTagNameAndClass('body')[0]; - head = dh.createDom('head'); - body.parentNode.insertBefore(head, body); - } - styleSheet = dh.createDom('style'); - // NOTE(user): Setting styles after the style element has been appended - // to the head results in a nasty Webkit bug in certain scenarios. Please - // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional - // details. - goog.style.setStyles(styleSheet, stylesString); - dh.appendChild(head, styleSheet); - } - return styleSheet; +/** + * Rounds the fields to the next smaller integer values. + * + * @return {!goog.math.Box} This box with floored fields. + */ +goog.math.Box.prototype.floor = function() { + this.top = Math.floor(this.top); + this.right = Math.floor(this.right); + this.bottom = Math.floor(this.bottom); + this.left = Math.floor(this.left); + return this; }; /** - * Removes the styles added by {@link #installStyles}. - * @param {Element|StyleSheet} styleSheet The value returned by - * {@link #installStyles}. + * Rounds the fields to nearest integer values. + * + * @return {!goog.math.Box} This box with rounded fields. */ -goog.style.uninstallStyles = function(styleSheet) { - var node = styleSheet.ownerNode || styleSheet.owningElement || - /** @type {Element} */ (styleSheet); - goog.dom.removeNode(node); +goog.math.Box.prototype.round = function() { + this.top = Math.round(this.top); + this.right = Math.round(this.right); + this.bottom = Math.round(this.bottom); + this.left = Math.round(this.left); + return this; }; /** - * Sets the content of a style element. The style element can be any valid - * style element. This element will have its content completely replaced by - * the new stylesString. - * @param {Element|StyleSheet} element A stylesheet element as returned by - * installStyles. - * @param {string} stylesString The new content of the stylesheet. + * Translates this box by the given offsets. If a {@code goog.math.Coordinate} + * is given, then the left and right values are translated by the coordinate's + * x value and the top and bottom values are translated by the coordinate's y + * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x + * and y dimension values. + * + * @param {number|goog.math.Coordinate} tx The value to translate the x + * dimension values by or the the coordinate to translate this box by. + * @param {number=} opt_ty The value to translate y dimension values by. + * @return {!goog.math.Box} This box after translating. */ -goog.style.setStyles = function(element, stylesString) { - if (goog.userAgent.IE && goog.isDef(element.cssText)) { - // Adding the selectors individually caused the browser to hang if the - // selector was invalid or there were CSS comments. Setting the cssText of - // the style node works fine and ignores CSS that IE doesn't understand. - // However IE >= 11 doesn't support cssText any more, so we make sure that - // cssText is a defined property and otherwise fall back to innerHTML. - element.cssText = stylesString; +goog.math.Box.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.left += tx.x; + this.right += tx.x; + this.top += tx.y; + this.bottom += tx.y; } else { - element.innerHTML = stylesString; + this.left += tx; + this.right += tx; + if (goog.isNumber(opt_ty)) { + this.top += opt_ty; + this.bottom += opt_ty; + } } + return this; }; /** - * Sets 'white-space: pre-wrap' for a node (x-browser). - * - * There are as many ways of specifying pre-wrap as there are browsers. - * - * CSS3/IE8: white-space: pre-wrap; - * Mozilla: white-space: -moz-pre-wrap; - * Opera: white-space: -o-pre-wrap; - * IE6/7: white-space: pre; word-wrap: break-word; + * Scales this coordinate by the given scale factors. The x and y dimension + * values are scaled by {@code sx} and {@code opt_sy} respectively. + * If {@code opt_sy} is not given, then {@code sx} is used for both x and y. * - * @param {Element} el Element to enable pre-wrap for. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Box} This box after scaling. */ -goog.style.setPreWrap = function(el) { - var style = el.style; - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { - style.whiteSpace = 'pre'; - style.wordWrap = 'break-word'; - } else if (goog.userAgent.GECKO) { - style.whiteSpace = '-moz-pre-wrap'; - } else { - style.whiteSpace = 'pre-wrap'; - } +goog.math.Box.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.left *= sx; + this.right *= sx; + this.top *= sy; + this.bottom *= sy; + return this; }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Sets 'display: inline-block' for an element (cross-browser). - * @param {Element} el Element to which the inline-block display style is to be - * applied. - * @see ../demos/inline_block_quirks.html - * @see ../demos/inline_block_standards.html + * @fileoverview A utility class for representing rectangles. */ -goog.style.setInlineBlock = function(el) { - var style = el.style; - // Without position:relative, weirdness ensues. Just accept it and move on. - style.position = 'relative'; - if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { - // IE8 supports inline-block so fall through to the else - // Zoom:1 forces hasLayout, display:inline gives inline behavior. - style.zoom = '1'; - style.display = 'inline'; - } else if (goog.userAgent.GECKO) { - // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box - // is close enough. - style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' : - '-moz-inline-box'; - } else { - // Opera, Webkit, and Safari seem to do OK with the standard inline-block - // style. - style.display = 'inline-block'; - } -}; +goog.provide('goog.math.Rect'); + +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Size'); + /** - * Returns true if the element is using right to left (rtl) direction. - * @param {Element} el The element to test. - * @return {boolean} True for right to left, false for left to right. + * Class for representing rectangular regions. + * @param {number} x Left. + * @param {number} y Top. + * @param {number} w Width. + * @param {number} h Height. + * @struct + * @constructor */ -goog.style.isRightToLeft = function(el) { - return 'rtl' == goog.style.getStyle_(el, 'direction'); +goog.math.Rect = function(x, y, w, h) { + /** @type {number} */ + this.left = x; + + /** @type {number} */ + this.top = y; + + /** @type {number} */ + this.width = w; + + /** @type {number} */ + this.height = h; }; /** - * The CSS style property corresponding to an element being - * unselectable on the current browser platform (null if none). - * Opera and IE instead use a DOM attribute 'unselectable'. - * @type {?string} - * @private + * @return {!goog.math.Rect} A new copy of this Rectangle. */ -goog.style.unselectableStyle_ = - goog.userAgent.GECKO ? 'MozUserSelect' : - goog.userAgent.WEBKIT ? 'WebkitUserSelect' : - null; +goog.math.Rect.prototype.clone = function() { + return new goog.math.Rect(this.left, this.top, this.width, this.height); +}; /** - * Returns true if the element is set to be unselectable, false otherwise. - * Note that on some platforms (e.g. Mozilla), even if an element isn't set - * to be unselectable, it will behave as such if any of its ancestors is - * unselectable. - * @param {Element} el Element to check. - * @return {boolean} Whether the element is set to be unselectable. + * Returns a new Box object with the same position and dimensions as this + * rectangle. + * @return {!goog.math.Box} A new Box representation of this Rectangle. */ -goog.style.isUnselectable = function(el) { - if (goog.style.unselectableStyle_) { - return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none'; - } else if (goog.userAgent.IE || goog.userAgent.OPERA) { - return el.getAttribute('unselectable') == 'on'; - } - return false; +goog.math.Rect.prototype.toBox = function() { + var right = this.left + this.width; + var bottom = this.top + this.height; + return new goog.math.Box(this.top, + right, + bottom, + this.left); }; /** - * Makes the element and its descendants selectable or unselectable. Note - * that on some platforms (e.g. Mozilla), even if an element isn't set to - * be unselectable, it will behave as such if any of its ancestors is - * unselectable. - * @param {Element} el The element to alter. - * @param {boolean} unselectable Whether the element and its descendants - * should be made unselectable. - * @param {boolean=} opt_noRecurse Whether to only alter the element's own - * selectable state, and leave its descendants alone; defaults to false. + * Creates a new Rect object with the same position and dimensions as a given + * Box. Note that this is only the inverse of toBox if left/top are defined. + * @param {goog.math.Box} box A box. + * @return {!goog.math.Rect} A new Rect initialized with the box's position + * and size. */ -goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) { - // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure? - var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null; - var name = goog.style.unselectableStyle_; - if (name) { - // Add/remove the appropriate CSS style to/from the element and its - // descendants. - var value = unselectable ? 'none' : ''; - el.style[name] = value; - if (descendants) { - for (var i = 0, descendant; descendant = descendants[i]; i++) { - descendant.style[name] = value; - } - } - } else if (goog.userAgent.IE || goog.userAgent.OPERA) { - // Toggle the 'unselectable' attribute on the element and its descendants. - var value = unselectable ? 'on' : ''; - el.setAttribute('unselectable', value); - if (descendants) { - for (var i = 0, descendant; descendant = descendants[i]; i++) { - descendant.setAttribute('unselectable', value); - } - } - } +goog.math.Rect.createFromBox = function(box) { + return new goog.math.Rect(box.left, box.top, + box.right - box.left, box.bottom - box.top); }; +if (goog.DEBUG) { + /** + * Returns a nice string representing size and dimensions of rectangle. + * @return {string} In the form (50, 73 - 75w x 25h). + * @override + */ + goog.math.Rect.prototype.toString = function() { + return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' + + this.height + 'h)'; + }; +} + + /** - * Gets the border box size for an element. - * @param {Element} element The element to get the size for. - * @return {!goog.math.Size} The border box size. + * Compares rectangles for equality. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {boolean} True iff the rectangles have the same left, top, width, + * and height, or if both are null. */ -goog.style.getBorderBoxSize = function(element) { - return new goog.math.Size(element.offsetWidth, element.offsetHeight); +goog.math.Rect.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.left == b.left && a.width == b.width && + a.top == b.top && a.height == b.height; }; /** - * Sets the border box size of an element. This is potentially expensive in IE - * if the document is CSS1Compat mode - * @param {Element} element The element to set the size on. - * @param {goog.math.Size} size The new size. + * Computes the intersection of this rectangle and the rectangle parameter. If + * there is no intersection, returns false and leaves this rectangle as is. + * @param {goog.math.Rect} rect A Rectangle. + * @return {boolean} True iff this rectangle intersects with the parameter. */ -goog.style.setBorderBoxSize = function(element, size) { - var doc = goog.dom.getOwnerDocument(element); - var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); +goog.math.Rect.prototype.intersection = function(rect) { + var x0 = Math.max(this.left, rect.left); + var x1 = Math.min(this.left + this.width, rect.left + rect.width); - if (goog.userAgent.IE && - !goog.userAgent.isVersionOrHigher('10') && - (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { - var style = element.style; - if (isCss1CompatMode) { - var paddingBox = goog.style.getPaddingBox(element); - var borderBox = goog.style.getBorderBox(element); - style.pixelWidth = size.width - borderBox.left - paddingBox.left - - paddingBox.right - borderBox.right; - style.pixelHeight = size.height - borderBox.top - paddingBox.top - - paddingBox.bottom - borderBox.bottom; - } else { - style.pixelWidth = size.width; - style.pixelHeight = size.height; + if (x0 <= x1) { + var y0 = Math.max(this.top, rect.top); + var y1 = Math.min(this.top + this.height, rect.top + rect.height); + + if (y0 <= y1) { + this.left = x0; + this.top = y0; + this.width = x1 - x0; + this.height = y1 - y0; + + return true; } - } else { - goog.style.setBoxSizingSize_(element, size, 'border-box'); } + return false; }; /** - * Gets the content box size for an element. This is potentially expensive in - * all browsers. - * @param {Element} element The element to get the size for. - * @return {!goog.math.Size} The content box size. + * Returns the intersection of two rectangles. Two rectangles intersect if they + * touch at all, for example, two zero width and height rectangles would + * intersect if they had the same top and left. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {goog.math.Rect} A new intersection rect (even if width and height + * are 0), or null if there is no intersection. */ -goog.style.getContentBoxSize = function(element) { - var doc = goog.dom.getOwnerDocument(element); - var ieCurrentStyle = goog.userAgent.IE && element.currentStyle; - if (ieCurrentStyle && - goog.dom.getDomHelper(doc).isCss1CompatMode() && - ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' && - !ieCurrentStyle.boxSizing) { - // If IE in CSS1Compat mode than just use the width and height. - // If we have a boxSizing then fall back on measuring the borders etc. - var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width, - 'width', 'pixelWidth'); - var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height, - 'height', 'pixelHeight'); - return new goog.math.Size(width, height); - } else { - var borderBoxSize = goog.style.getBorderBoxSize(element); - var paddingBox = goog.style.getPaddingBox(element); - var borderBox = goog.style.getBorderBox(element); - return new goog.math.Size(borderBoxSize.width - - borderBox.left - paddingBox.left - - paddingBox.right - borderBox.right, - borderBoxSize.height - - borderBox.top - paddingBox.top - - paddingBox.bottom - borderBox.bottom); +goog.math.Rect.intersection = function(a, b) { + // There is no nice way to do intersection via a clone, because any such + // clone might be unnecessary if this function returns null. So, we duplicate + // code from above. + + var x0 = Math.max(a.left, b.left); + var x1 = Math.min(a.left + a.width, b.left + b.width); + + if (x0 <= x1) { + var y0 = Math.max(a.top, b.top); + var y1 = Math.min(a.top + a.height, b.top + b.height); + + if (y0 <= y1) { + return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0); + } } + return null; }; /** - * Sets the content box size of an element. This is potentially expensive in IE - * if the document is BackCompat mode. - * @param {Element} element The element to set the size on. - * @param {goog.math.Size} size The new size. + * Returns whether two rectangles intersect. Two rectangles intersect if they + * touch at all, for example, two zero width and height rectangles would + * intersect if they had the same top and left. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {boolean} Whether a and b intersect. */ -goog.style.setContentBoxSize = function(element, size) { - var doc = goog.dom.getOwnerDocument(element); - var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); - if (goog.userAgent.IE && - !goog.userAgent.isVersionOrHigher('10') && - (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { - var style = element.style; - if (isCss1CompatMode) { - style.pixelWidth = size.width; - style.pixelHeight = size.height; - } else { - var paddingBox = goog.style.getPaddingBox(element); - var borderBox = goog.style.getBorderBox(element); - style.pixelWidth = size.width + borderBox.left + paddingBox.left + - paddingBox.right + borderBox.right; - style.pixelHeight = size.height + borderBox.top + paddingBox.top + - paddingBox.bottom + borderBox.bottom; - } - } else { - goog.style.setBoxSizingSize_(element, size, 'content-box'); - } +goog.math.Rect.intersects = function(a, b) { + return (a.left <= b.left + b.width && b.left <= a.left + a.width && + a.top <= b.top + b.height && b.top <= a.top + a.height); }; /** - * Helper function that sets the box sizing as well as the width and height - * @param {Element} element The element to set the size on. - * @param {goog.math.Size} size The new size to set. - * @param {string} boxSizing The box-sizing value. - * @private + * Returns whether a rectangle intersects this rectangle. + * @param {goog.math.Rect} rect A rectangle. + * @return {boolean} Whether rect intersects this rectangle. */ -goog.style.setBoxSizingSize_ = function(element, size, boxSizing) { - var style = element.style; - if (goog.userAgent.GECKO) { - style.MozBoxSizing = boxSizing; - } else if (goog.userAgent.WEBKIT) { - style.WebkitBoxSizing = boxSizing; - } else { - // Includes IE8 and Opera 9.50+ - style.boxSizing = boxSizing; +goog.math.Rect.prototype.intersects = function(rect) { + return goog.math.Rect.intersects(this, rect); +}; + + +/** + * Computes the difference regions between two rectangles. The return value is + * an array of 0 to 4 rectangles defining the remaining regions of the first + * rectangle after the second has been subtracted. + * @param {goog.math.Rect} a A Rectangle. + * @param {goog.math.Rect} b A Rectangle. + * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which + * together define the difference area of rectangle a minus rectangle b. + */ +goog.math.Rect.difference = function(a, b) { + var intersection = goog.math.Rect.intersection(a, b); + if (!intersection || !intersection.height || !intersection.width) { + return [a.clone()]; } - // Setting this to a negative value will throw an exception on IE - // (and doesn't do anything different than setting it to 0). - style.width = Math.max(size.width, 0) + 'px'; - style.height = Math.max(size.height, 0) + 'px'; + var result = []; + + var top = a.top; + var height = a.height; + + var ar = a.left + a.width; + var ab = a.top + a.height; + + var br = b.left + b.width; + var bb = b.top + b.height; + + // Subtract off any area on top where A extends past B + if (b.top > a.top) { + result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top)); + top = b.top; + // If we're moving the top down, we also need to subtract the height diff. + height -= b.top - a.top; + } + // Subtract off any area on bottom where A extends past B + if (bb < ab) { + result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb)); + height = bb - top; + } + // Subtract any area on left where A extends past B + if (b.left > a.left) { + result.push(new goog.math.Rect(a.left, top, b.left - a.left, height)); + } + // Subtract any area on right where A extends past B + if (br < ar) { + result.push(new goog.math.Rect(br, top, ar - br, height)); + } + + return result; }; /** - * IE specific function that converts a non pixel unit to pixels. - * @param {Element} element The element to convert the value for. - * @param {string} value The current value as a string. The value must not be - * ''. - * @param {string} name The CSS property name to use for the converstion. This - * should be 'left', 'top', 'width' or 'height'. - * @param {string} pixelName The CSS pixel property name to use to get the - * value in pixels. - * @return {number} The value in pixels. - * @private + * Computes the difference regions between this rectangle and {@code rect}. The + * return value is an array of 0 to 4 rectangles defining the remaining regions + * of this rectangle after the other has been subtracted. + * @param {goog.math.Rect} rect A Rectangle. + * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which + * together define the difference area of rectangle a minus rectangle b. */ -goog.style.getIePixelValue_ = function(element, value, name, pixelName) { - // Try if we already have a pixel value. IE does not do half pixels so we - // only check if it matches a number followed by 'px'. - if (/^\d+px?$/.test(value)) { - return parseInt(value, 10); - } else { - var oldStyleValue = element.style[name]; - var oldRuntimeValue = element.runtimeStyle[name]; - // set runtime style to prevent changes - element.runtimeStyle[name] = element.currentStyle[name]; - element.style[name] = value; - var pixelValue = element.style[pixelName]; - // restore - element.style[name] = oldStyleValue; - element.runtimeStyle[name] = oldRuntimeValue; - return pixelValue; - } +goog.math.Rect.prototype.difference = function(rect) { + return goog.math.Rect.difference(this, rect); }; /** - * Helper function for getting the pixel padding or margin for IE. - * @param {Element} element The element to get the padding for. - * @param {string} propName The property name. - * @return {number} The pixel padding. - * @private + * Expand this rectangle to also include the area of the given rectangle. + * @param {goog.math.Rect} rect The other rectangle. */ -goog.style.getIePixelDistance_ = function(element, propName) { - var value = goog.style.getCascadedStyle(element, propName); - return value ? - goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0; +goog.math.Rect.prototype.boundingRect = function(rect) { + // We compute right and bottom before we change left and top below. + var right = Math.max(this.left + this.width, rect.left + rect.width); + var bottom = Math.max(this.top + this.height, rect.top + rect.height); + + this.left = Math.min(this.left, rect.left); + this.top = Math.min(this.top, rect.top); + + this.width = right - this.left; + this.height = bottom - this.top; }; /** - * Gets the computed paddings or margins (on all sides) in pixels. - * @param {Element} element The element to get the padding for. - * @param {string} stylePrefix Pass 'padding' to retrieve the padding box, - * or 'margin' to retrieve the margin box. - * @return {!goog.math.Box} The computed paddings or margins. - * @private + * Returns a new rectangle which completely contains both input rectangles. + * @param {goog.math.Rect} a A rectangle. + * @param {goog.math.Rect} b A rectangle. + * @return {goog.math.Rect} A new bounding rect, or null if either rect is + * null. */ -goog.style.getBox_ = function(element, stylePrefix) { - if (goog.userAgent.IE) { - var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left'); - var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right'); - var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top'); - var bottom = goog.style.getIePixelDistance_( - element, stylePrefix + 'Bottom'); - return new goog.math.Box(top, right, bottom, left); - } else { - // On non-IE browsers, getComputedStyle is always non-null. - var left = /** @type {string} */ ( - goog.style.getComputedStyle(element, stylePrefix + 'Left')); - var right = /** @type {string} */ ( - goog.style.getComputedStyle(element, stylePrefix + 'Right')); - var top = /** @type {string} */ ( - goog.style.getComputedStyle(element, stylePrefix + 'Top')); - var bottom = /** @type {string} */ ( - goog.style.getComputedStyle(element, stylePrefix + 'Bottom')); +goog.math.Rect.boundingRect = function(a, b) { + if (!a || !b) { + return null; + } - // NOTE(arv): Gecko can return floating point numbers for the computed - // style values. - return new goog.math.Box(parseFloat(top), - parseFloat(right), - parseFloat(bottom), - parseFloat(left)); + var clone = a.clone(); + clone.boundingRect(b); + + return clone; +}; + + +/** + * Tests whether this rectangle entirely contains another rectangle or + * coordinate. + * + * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or + * coordinate to test for containment. + * @return {boolean} Whether this rectangle contains given rectangle or + * coordinate. + */ +goog.math.Rect.prototype.contains = function(another) { + if (another instanceof goog.math.Rect) { + return this.left <= another.left && + this.left + this.width >= another.left + another.width && + this.top <= another.top && + this.top + this.height >= another.top + another.height; + } else { // (another instanceof goog.math.Coordinate) + return another.x >= this.left && + another.x <= this.left + this.width && + another.y >= this.top && + another.y <= this.top + this.height; } }; /** - * Gets the computed paddings (on all sides) in pixels. - * @param {Element} element The element to get the padding for. - * @return {!goog.math.Box} The computed paddings. + * @param {!goog.math.Coordinate} point A coordinate. + * @return {number} The squared distance between the point and the closest + * point inside the rectangle. Returns 0 if the point is inside the + * rectangle. */ -goog.style.getPaddingBox = function(element) { - return goog.style.getBox_(element, 'padding'); +goog.math.Rect.prototype.squaredDistance = function(point) { + var dx = point.x < this.left ? + this.left - point.x : Math.max(point.x - (this.left + this.width), 0); + var dy = point.y < this.top ? + this.top - point.y : Math.max(point.y - (this.top + this.height), 0); + return dx * dx + dy * dy; }; /** - * Gets the computed margins (on all sides) in pixels. - * @param {Element} element The element to get the margins for. - * @return {!goog.math.Box} The computed margins. + * @param {!goog.math.Coordinate} point A coordinate. + * @return {number} The distance between the point and the closest point + * inside the rectangle. Returns 0 if the point is inside the rectangle. */ -goog.style.getMarginBox = function(element) { - return goog.style.getBox_(element, 'margin'); +goog.math.Rect.prototype.distance = function(point) { + return Math.sqrt(this.squaredDistance(point)); }; /** - * A map used to map the border width keywords to a pixel width. - * @type {Object} - * @private + * @return {!goog.math.Size} The size of this rectangle. */ -goog.style.ieBorderWidthKeywords_ = { - 'thin': 2, - 'medium': 4, - 'thick': 6 +goog.math.Rect.prototype.getSize = function() { + return new goog.math.Size(this.width, this.height); }; /** - * Helper function for IE to get the pixel border. - * @param {Element} element The element to get the pixel border for. - * @param {string} prop The part of the property name. - * @return {number} The value in pixels. - * @private + * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of + * the rectangle. */ -goog.style.getIePixelBorder_ = function(element, prop) { - if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') { - return 0; - } - var width = goog.style.getCascadedStyle(element, prop + 'Width'); - if (width in goog.style.ieBorderWidthKeywords_) { - return goog.style.ieBorderWidthKeywords_[width]; - } - return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft'); +goog.math.Rect.prototype.getTopLeft = function() { + return new goog.math.Coordinate(this.left, this.top); }; /** - * Gets the computed border widths (on all sides) in pixels - * @param {Element} element The element to get the border widths for. - * @return {!goog.math.Box} The computed border widths. + * @return {!goog.math.Coordinate} A new coordinate for the center of the + * rectangle. */ -goog.style.getBorderBox = function(element) { - if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { - var left = goog.style.getIePixelBorder_(element, 'borderLeft'); - var right = goog.style.getIePixelBorder_(element, 'borderRight'); - var top = goog.style.getIePixelBorder_(element, 'borderTop'); - var bottom = goog.style.getIePixelBorder_(element, 'borderBottom'); - return new goog.math.Box(top, right, bottom, left); - } else { - // On non-IE browsers, getComputedStyle is always non-null. - var left = /** @type {string} */ ( - goog.style.getComputedStyle(element, 'borderLeftWidth')); - var right = /** @type {string} */ ( - goog.style.getComputedStyle(element, 'borderRightWidth')); - var top = /** @type {string} */ ( - goog.style.getComputedStyle(element, 'borderTopWidth')); - var bottom = /** @type {string} */ ( - goog.style.getComputedStyle(element, 'borderBottomWidth')); +goog.math.Rect.prototype.getCenter = function() { + return new goog.math.Coordinate( + this.left + this.width / 2, this.top + this.height / 2); +}; - return new goog.math.Box(parseFloat(top), - parseFloat(right), - parseFloat(bottom), - parseFloat(left)); - } + +/** + * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner + * of the rectangle. + */ +goog.math.Rect.prototype.getBottomRight = function() { + return new goog.math.Coordinate( + this.left + this.width, this.top + this.height); }; /** - * Returns the font face applied to a given node. Opera and IE should return - * the font actually displayed. Firefox returns the author's most-preferred - * font (whether the browser is capable of displaying it or not.) - * @param {Element} el The element whose font family is returned. - * @return {string} The font family applied to el. + * Rounds the fields to the next larger integer values. + * @return {!goog.math.Rect} This rectangle with ceil'd fields. */ -goog.style.getFontFamily = function(el) { - var doc = goog.dom.getOwnerDocument(el); - var font = ''; - // The moveToElementText method from the TextRange only works if the element - // is attached to the owner document. - if (doc.body.createTextRange && goog.dom.contains(doc, el)) { - var range = doc.body.createTextRange(); - range.moveToElementText(el); - /** @preserveTry */ - try { - font = range.queryCommandValue('FontName'); - } catch (e) { - // This is a workaround for a awkward exception. - // On some IE, there is an exception coming from it. - // The error description from this exception is: - // This window has already been registered as a drop target - // This is bogus description, likely due to a bug in ie. - font = ''; - } - } - if (!font) { - // Note if for some reason IE can't derive FontName with a TextRange, we - // fallback to using currentStyle - font = goog.style.getStyle_(el, 'fontFamily'); - } - - // Firefox returns the applied font-family string (author's list of - // preferred fonts.) We want to return the most-preferred font, in lieu of - // the *actually* applied font. - var fontsArray = font.split(','); - if (fontsArray.length > 1) font = fontsArray[0]; - - // Sanitize for x-browser consistency: - // Strip quotes because browsers aren't consistent with how they're - // applied; Opera always encloses, Firefox sometimes, and IE never. - return goog.string.stripQuotes(font, '"\''); +goog.math.Rect.prototype.ceil = function() { + this.left = Math.ceil(this.left); + this.top = Math.ceil(this.top); + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this; }; /** - * Regular expression used for getLengthUnits. - * @type {RegExp} - * @private + * Rounds the fields to the next smaller integer values. + * @return {!goog.math.Rect} This rectangle with floored fields. */ -goog.style.lengthUnitRegex_ = /[^\d]+$/; +goog.math.Rect.prototype.floor = function() { + this.left = Math.floor(this.left); + this.top = Math.floor(this.top); + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this; +}; /** - * Returns the units used for a CSS length measurement. - * @param {string} value A CSS length quantity. - * @return {?string} The units of measurement. + * Rounds the fields to nearest integer values. + * @return {!goog.math.Rect} This rectangle with rounded fields. */ -goog.style.getLengthUnits = function(value) { - var units = value.match(goog.style.lengthUnitRegex_); - return units && units[0] || null; +goog.math.Rect.prototype.round = function() { + this.left = Math.round(this.left); + this.top = Math.round(this.top); + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this; }; /** - * Map of absolute CSS length units - * @type {Object} - * @private + * Translates this rectangle by the given offsets. If a + * {@code goog.math.Coordinate} is given, then the left and top values are + * translated by the coordinate's x and y values. Otherwise, top and left are + * translated by {@code tx} and {@code opt_ty} respectively. + * @param {number|goog.math.Coordinate} tx The value to translate left by or the + * the coordinate to translate this rect by. + * @param {number=} opt_ty The value to translate top by. + * @return {!goog.math.Rect} This rectangle after translating. */ -goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = { - 'cm' : 1, - 'in' : 1, - 'mm' : 1, - 'pc' : 1, - 'pt' : 1 +goog.math.Rect.prototype.translate = function(tx, opt_ty) { + if (tx instanceof goog.math.Coordinate) { + this.left += tx.x; + this.top += tx.y; + } else { + this.left += tx; + if (goog.isNumber(opt_ty)) { + this.top += opt_ty; + } + } + return this; }; /** - * Map of relative CSS length units that can be accurately converted to px - * font-size values using getIePixelValue_. Only units that are defined in - * relation to a font size are convertible (%, small, etc. are not). - * @type {Object} - * @private + * Scales this rectangle by the given scale factors. The left and width values + * are scaled by {@code sx} and the top and height values are scaled by + * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled + * by {@code sx}. + * @param {number} sx The scale factor to use for the x dimension. + * @param {number=} opt_sy The scale factor to use for the y dimension. + * @return {!goog.math.Rect} This rectangle after scaling. */ -goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = { - 'em' : 1, - 'ex' : 1 +goog.math.Rect.prototype.scale = function(sx, opt_sy) { + var sy = goog.isNumber(opt_sy) ? opt_sy : sx; + this.left *= sx; + this.width *= sx; + this.top *= sy; + this.height *= sy; + return this; }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Returns the font size, in pixels, of text in an element. - * @param {Element} el The element whose font size is returned. - * @return {number} The font size (in pixels). + * @fileoverview Utilities for element styles. + * + * @author arv@google.com (Erik Arvidsson) + * @author eae@google.com (Emil A Eklund) + * @see ../demos/inline_block_quirks.html + * @see ../demos/inline_block_standards.html + * @see ../demos/style_viewport.html */ -goog.style.getFontSize = function(el) { - var fontSize = goog.style.getStyle_(el, 'fontSize'); - var sizeUnits = goog.style.getLengthUnits(fontSize); - if (fontSize && 'px' == sizeUnits) { - // NOTE(user): This could be parseFloat instead, but IE doesn't return - // decimal fractions in getStyle_ and Firefox reports the fractions, but - // ignores them when rendering. Interestingly enough, when we force the - // issue and size something to e.g., 50% of 25px, the browsers round in - // opposite directions with Firefox reporting 12px and IE 13px. I punt. - return parseInt(fontSize, 10); - } - // In IE, we can convert absolute length units to a px value using - // goog.style.getIePixelValue_. Units defined in relation to a font size - // (em, ex) are applied relative to the element's parentNode and can also - // be converted. - if (goog.userAgent.IE) { - if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) { - return goog.style.getIePixelValue_(el, - fontSize, - 'left', - 'pixelLeft'); - } else if (el.parentNode && - el.parentNode.nodeType == goog.dom.NodeType.ELEMENT && - sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) { - // Check the parent size - if it is the same it means the relative size - // value is inherited and we therefore don't want to count it twice. If - // it is different, this element either has explicit style or has a CSS - // rule applying to it. - var parentElement = /** @type {!Element} */ (el.parentNode); - var parentSize = goog.style.getStyle_(parentElement, 'fontSize'); - return goog.style.getIePixelValue_(parentElement, - fontSize == parentSize ? - '1em' : fontSize, - 'left', - 'pixelLeft'); - } - } +goog.provide('goog.style'); - // Sometimes we can't cleanly find the font size (some units relative to a - // node's parent's font size are difficult: %, smaller et al), so we create - // an invisible, absolutely-positioned span sized to be the height of an 'M' - // rendered in its parent's (i.e., our target element's) font size. This is - // the definition of CSS's font size attribute. - var sizeElement = goog.dom.createDom( - 'span', - {'style': 'visibility:hidden;position:absolute;' + - 'line-height:0;padding:0;margin:0;border:0;height:1em;'}); - goog.dom.appendChild(el, sizeElement); - fontSize = sizeElement.offsetHeight; - goog.dom.removeNode(sizeElement); - return fontSize; -}; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.vendor'); +goog.require('goog.math.Box'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Rect'); +goog.require('goog.math.Size'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.userAgent'); /** - * Parses a style attribute value. Converts CSS property names to camel case. - * @param {string} value The style attribute value. - * @return {!Object} Map of CSS properties to string values. + * Sets a style value on an element. + * + * This function is not indended to patch issues in the browser's style + * handling, but to allow easy programmatic access to setting dash-separated + * style properties. An example is setting a batch of properties from a data + * object without overwriting old styles. When possible, use native APIs: + * elem.style.propertyKey = 'value' or (if obliterating old styles is fine) + * elem.style.cssText = 'property1: value1; property2: value2'. + * + * @param {Element} element The element to change. + * @param {string|Object} style If a string, a style name. If an object, a hash + * of style names to style values. + * @param {string|number|boolean=} opt_value If style was a string, then this + * should be the value. */ -goog.style.parseStyleAttribute = function(value) { - var result = {}; - goog.array.forEach(value.split(/\s*;\s*/), function(pair) { - var keyValue = pair.split(/\s*:\s*/); - if (keyValue.length == 2) { - result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1]; +goog.style.setStyle = function(element, style, opt_value) { + if (goog.isString(style)) { + goog.style.setStyle_(element, opt_value, style); + } else { + for (var key in style) { + goog.style.setStyle_(element, style[key], key); } - }); - return result; + } }; /** - * Reverse of parseStyleAttribute; that is, takes a style object and returns the - * corresponding attribute value. Converts camel case property names to proper - * CSS selector names. - * @param {Object} obj Map of CSS properties to values. - * @return {string} The style attribute value. + * Sets a style value on an element, with parameters swapped to work with + * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when + * necessary. + * @param {Element} element The element to change. + * @param {string|number|boolean|undefined} value Style value. + * @param {string} style Style name. + * @private */ -goog.style.toStyleAttribute = function(obj) { - var buffer = []; - goog.object.forEach(obj, function(value, key) { - buffer.push(goog.string.toSelectorCase(key), ':', value, ';'); - }); - return buffer.join(''); +goog.style.setStyle_ = function(element, value, style) { + var propertyName = goog.style.getVendorJsStyleName_(element, style); + + if (propertyName) { + element.style[propertyName] = value; + } }; /** - * Sets CSS float property on an element. - * @param {Element} el The element to set float property on. - * @param {string} value The value of float CSS property to set on this element. + * Style name cache that stores previous property name lookups. + * + * This is used by setStyle to speed up property lookups, entries look like: + * { StyleName: ActualPropertyName } + * + * @private {!Object<string, string>} */ -goog.style.setFloat = function(el, value) { - el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value; -}; +goog.style.styleNameCache_ = {}; /** - * Gets value of explicitly-set float CSS property on an element. - * @param {Element} el The element to get float property of. - * @return {string} The value of explicitly-set float CSS property on this - * element. + * Returns the style property name in camel-case. If it does not exist and a + * vendor-specific version of the property does exist, then return the vendor- + * specific property name instead. + * @param {Element} element The element to change. + * @param {string} style Style name. + * @return {string} Vendor-specific style. + * @private */ -goog.style.getFloat = function(el) { - return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || ''; +goog.style.getVendorJsStyleName_ = function(element, style) { + var propertyName = goog.style.styleNameCache_[style]; + if (!propertyName) { + var camelStyle = goog.string.toCamelCase(style); + propertyName = camelStyle; + + if (element.style[camelStyle] === undefined) { + var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + + goog.string.toTitleCase(camelStyle); + + if (element.style[prefixedStyle] !== undefined) { + propertyName = prefixedStyle; + } + } + goog.style.styleNameCache_[style] = propertyName; + } + + return propertyName; }; /** - * Returns the scroll bar width (represents the width of both horizontal - * and vertical scroll). - * - * @param {string=} opt_className An optional class name (or names) to apply - * to the invisible div created to measure the scrollbar. This is necessary - * if some scrollbars are styled differently than others. - * @return {number} The scroll bar width in px. + * Returns the style property name in CSS notation. If it does not exist and a + * vendor-specific version of the property does exist, then return the vendor- + * specific property name instead. + * @param {Element} element The element to change. + * @param {string} style Style name. + * @return {string} Vendor-specific style. + * @private */ -goog.style.getScrollbarWidth = function(opt_className) { - // Add two hidden divs. The child div is larger than the parent and - // forces scrollbars to appear on it. - // Using overflow:scroll does not work consistently with scrollbars that - // are styled with ::-webkit-scrollbar. - var outerDiv = goog.dom.createElement('div'); - if (opt_className) { - outerDiv.className = opt_className; +goog.style.getVendorStyleName_ = function(element, style) { + var camelStyle = goog.string.toCamelCase(style); + + if (element.style[camelStyle] === undefined) { + var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() + + goog.string.toTitleCase(camelStyle); + + if (element.style[prefixedStyle] !== undefined) { + return goog.dom.vendor.getVendorPrefix() + '-' + style; + } } - outerDiv.style.cssText = 'overflow:auto;' + - 'position:absolute;top:0;width:100px;height:100px'; - var innerDiv = goog.dom.createElement('div'); - goog.style.setSize(innerDiv, '200px', '200px'); - outerDiv.appendChild(innerDiv); - goog.dom.appendChild(goog.dom.getDocument().body, outerDiv); - var width = outerDiv.offsetWidth - outerDiv.clientWidth; - goog.dom.removeNode(outerDiv); - return width; + + return style; }; /** - * Regular expression to extract x and y translation components from a CSS - * transform Matrix representation. + * Retrieves an explicitly-set style value of a node. This returns '' if there + * isn't a style attribute on the element or if this style property has not been + * explicitly set in script. * - * @type {!RegExp} - * @const - * @private + * @param {Element} element Element to get style of. + * @param {string} property Property to get, css-style (if you have a camel-case + * property, use element.style[style]). + * @return {string} Style value. */ -goog.style.MATRIX_TRANSLATION_REGEX_ = - new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' + - '[0-9\\.\\-]+, [0-9\\.\\-]+, ' + - '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)'); +goog.style.getStyle = function(element, property) { + // element.style is '' for well-known properties which are unset. + // For for browser specific styles as 'filter' is undefined + // so we need to return '' explicitly to make it consistent across + // browsers. + var styleValue = element.style[goog.string.toCamelCase(property)]; + + // Using typeof here because of a bug in Safari 5.1, where this value + // was undefined, but === undefined returned false. + if (typeof(styleValue) !== 'undefined') { + return styleValue; + } + + return element.style[goog.style.getVendorJsStyleName_(element, property)] || + ''; +}; /** - * Returns the x,y translation component of any CSS transforms applied to the - * element, in pixels. + * Retrieves a computed style value of a node. It returns empty string if the + * value cannot be computed (which will be the case in Internet Explorer) or + * "none" if the property requested is an SVG one and it has not been + * explicitly set (firefox and webkit). * - * @param {!Element} element The element to get the translation of. - * @return {!goog.math.Coordinate} The CSS translation of the element in px. + * @param {Element} element Element to get style of. + * @param {string} property Property to get (camel-case). + * @return {string} Style value. */ -goog.style.getCssTranslation = function(element) { - var transform = goog.style.getComputedTransform(element); - if (!transform) { - return new goog.math.Coordinate(0, 0); - } - var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_); - if (!matches) { - return new goog.math.Coordinate(0, 0); +goog.style.getComputedStyle = function(element, property) { + var doc = goog.dom.getOwnerDocument(element); + if (doc.defaultView && doc.defaultView.getComputedStyle) { + var styles = doc.defaultView.getComputedStyle(element, null); + if (styles) { + // element.style[..] is undefined for browser specific styles + // as 'filter'. + return styles[property] || styles.getPropertyValue(property) || ''; + } } - return new goog.math.Coordinate(parseFloat(matches[1]), - parseFloat(matches[2])); -}; - -goog.provide('ol.MapEvent'); -goog.provide('ol.MapEventType'); -goog.require('goog.events.Event'); + return ''; +}; /** - * @enum {string} + * Gets the cascaded style value of a node, or null if the value cannot be + * computed (only Internet Explorer can do this). + * + * @param {Element} element Element to get style of. + * @param {string} style Property to get (camel-case). + * @return {string} Style value. */ -ol.MapEventType = { - /** - * Triggered after a map frame is rendered. - * @event ol.MapEvent#postrender - * @api - */ - POSTRENDER: 'postrender', - /** - * Triggered after the map is moved. - * @event ol.MapEvent#moveend - * @api - */ - MOVEEND: 'moveend' +goog.style.getCascadedStyle = function(element, style) { + // TODO(nicksantos): This should be documented to return null. #fixTypes + return element.currentStyle ? element.currentStyle[style] : null; }; - /** - * @classdesc - * Events emitted as map events are instances of this type. - * See {@link ol.Map} for which events trigger a map event. + * Cross-browser pseudo get computed style. It returns the computed style where + * available. If not available it tries the cascaded style value (IE + * currentStyle) and in worst case the inline style value. It shouldn't be + * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for + * discussion. * - * @constructor - * @extends {goog.events.Event} - * @implements {oli.MapEvent} - * @param {string} type Event type. - * @param {ol.Map} map Map. - * @param {?olx.FrameState=} opt_frameState Frame state. + * @param {Element} element Element to get style of. + * @param {string} style Property to get (must be camelCase, not css-style.). + * @return {string} Style value. + * @private */ -ol.MapEvent = function(type, map, opt_frameState) { - - goog.base(this, type); - - /** - * The map where the event occurred. - * @type {ol.Map} - * @api stable - */ - this.map = map; +goog.style.getStyle_ = function(element, style) { + return goog.style.getComputedStyle(element, style) || + goog.style.getCascadedStyle(element, style) || + (element.style && element.style[style]); +}; - /** - * The frame state at the time of the event. - * @type {?olx.FrameState} - * @api - */ - this.frameState = goog.isDef(opt_frameState) ? opt_frameState : null; +/** + * Retrieves the computed value of the box-sizing CSS attribute. + * Browser support: http://caniuse.com/css3-boxsizing. + * @param {!Element} element The element whose box-sizing to get. + * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if + * box-sizing is not supported (IE7 and below). + */ +goog.style.getComputedBoxSizing = function(element) { + return goog.style.getStyle_(element, 'boxSizing') || + goog.style.getStyle_(element, 'MozBoxSizing') || + goog.style.getStyle_(element, 'WebkitBoxSizing') || null; }; -goog.inherits(ol.MapEvent, goog.events.Event); -goog.provide('ol.control.Control'); - -goog.require('goog.array'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('ol.MapEventType'); -goog.require('ol.Object'); +/** + * Retrieves the computed value of the position CSS attribute. + * @param {Element} element The element to get the position of. + * @return {string} Position value. + */ +goog.style.getComputedPosition = function(element) { + return goog.style.getStyle_(element, 'position'); +}; /** - * @classdesc - * A control is a visible widget with a DOM element in a fixed position on the - * screen. They can involve user input (buttons), or be informational only; - * the position is determined using CSS. By default these are placed in the - * container with CSS class name `ol-overlaycontainer-stopevent`, but can use - * any outside DOM element. - * - * This is the base class for controls. You can use it for simple custom - * controls by creating the element with listeners, creating an instance: - * ```js - * var myControl = new ol.control.Control({element: myElement}); - * ``` - * and then adding this to the map. - * - * The main advantage of having this as a control rather than a simple separate - * DOM element is that preventing propagation is handled for you. Controls - * will also be `ol.Object`s in a `ol.Collection`, so you can use their - * methods. + * Retrieves the computed background color string for a given element. The + * string returned is suitable for assigning to another element's + * background-color, but is not guaranteed to be in any particular string + * format. Accessing the color in a numeric form may not be possible in all + * browsers or with all input. * - * You can also extend this base for your own control class. See - * examples/custom-controls for an example of how to do this. + * If the background color for the element is defined as a hexadecimal value, + * the resulting string can be parsed by goog.color.parse in all supported + * browsers. * - * @constructor - * @extends {ol.Object} - * @implements {oli.control.Control} - * @param {olx.control.ControlOptions} options Control options. - * @api stable + * Whether named colors like "red" or "lightblue" get translated into a + * format which can be parsed is browser dependent. Calling this function on + * transparent elements will return "transparent" in most browsers or + * "rgba(0, 0, 0, 0)" in WebKit. + * @param {Element} element The element to get the background color of. + * @return {string} The computed string value of the background color. */ -ol.control.Control = function(options) { - - goog.base(this); - - /** - * @protected - * @type {Element} - */ - this.element = goog.isDef(options.element) ? options.element : null; - - /** - * @private - * @type {Element} - */ - this.target_ = goog.isDef(options.target) ? - goog.dom.getElement(options.target) : null; - - /** - * @private - * @type {ol.Map} - */ - this.map_ = null; - - /** - * @protected - * @type {!Array.<?number>} - */ - this.listenerKeys = []; - - /** - * @type {function(ol.MapEvent)} - */ - this.render = goog.isDef(options.render) ? options.render : goog.nullFunction; - +goog.style.getBackgroundColor = function(element) { + return goog.style.getStyle_(element, 'backgroundColor'); }; -goog.inherits(ol.control.Control, ol.Object); /** - * @inheritDoc + * Retrieves the computed value of the overflow-x CSS attribute. + * @param {Element} element The element to get the overflow-x of. + * @return {string} The computed string value of the overflow-x attribute. */ -ol.control.Control.prototype.disposeInternal = function() { - goog.dom.removeNode(this.element); - goog.base(this, 'disposeInternal'); +goog.style.getComputedOverflowX = function(element) { + return goog.style.getStyle_(element, 'overflowX'); }; /** - * Get the map associated with this control. - * @return {ol.Map} Map. - * @api stable + * Retrieves the computed value of the overflow-y CSS attribute. + * @param {Element} element The element to get the overflow-y of. + * @return {string} The computed string value of the overflow-y attribute. */ -ol.control.Control.prototype.getMap = function() { - return this.map_; +goog.style.getComputedOverflowY = function(element) { + return goog.style.getStyle_(element, 'overflowY'); }; /** - * Remove the control from its current map and attach it to the new map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {ol.Map} map Map. - * @api stable + * Retrieves the computed value of the z-index CSS attribute. + * @param {Element} element The element to get the z-index of. + * @return {string|number} The computed value of the z-index attribute. */ -ol.control.Control.prototype.setMap = function(map) { - if (!goog.isNull(this.map_)) { - goog.dom.removeNode(this.element); - } - if (!goog.array.isEmpty(this.listenerKeys)) { - goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); - this.listenerKeys.length = 0; - } - this.map_ = map; - if (!goog.isNull(this.map_)) { - var target = !goog.isNull(this.target_) ? - this.target_ : map.getOverlayContainerStopEvent(); - goog.dom.appendChild(target, this.element); - if (this.render !== goog.nullFunction) { - this.listenerKeys.push(goog.events.listen(map, - ol.MapEventType.POSTRENDER, this.render, false, this)); - } - map.render(); - } +goog.style.getComputedZIndex = function(element) { + return goog.style.getStyle_(element, 'zIndex'); }; -goog.provide('ol.css'); - /** - * The CSS class for hidden feature. - * - * @const - * @type {string} + * Retrieves the computed value of the text-align CSS attribute. + * @param {Element} element The element to get the text-align of. + * @return {string} The computed string value of the text-align attribute. */ -ol.css.CLASS_HIDDEN = 'ol-hidden'; +goog.style.getComputedTextAlign = function(element) { + return goog.style.getStyle_(element, 'textAlign'); +}; /** - * The CSS class that we'll give the DOM elements to have them unselectable. - * - * @const - * @type {string} + * Retrieves the computed value of the cursor CSS attribute. + * @param {Element} element The element to get the cursor of. + * @return {string} The computed string value of the cursor attribute. */ -ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; +goog.style.getComputedCursor = function(element) { + return goog.style.getStyle_(element, 'cursor'); +}; /** - * The CSS class for unsupported feature. - * - * @const - * @type {string} + * Retrieves the computed value of the CSS transform attribute. + * @param {Element} element The element to get the transform of. + * @return {string} The computed string representation of the transform matrix. */ -ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; +goog.style.getComputedTransform = function(element) { + var property = goog.style.getVendorStyleName_(element, 'transform'); + return goog.style.getStyle_(element, property) || + goog.style.getStyle_(element, 'transform'); +}; /** - * The CSS class for controls. - * - * @const - * @type {string} + * Sets the top/left values of an element. If no unit is specified in the + * argument then it will add px. The second argument is required if the first + * argument is a string or number and is ignored if the first argument + * is a coordinate. + * @param {Element} el Element to move. + * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. + * @param {string|number=} opt_arg2 Top position. */ -ol.css.CLASS_CONTROL = 'ol-control'; - -// FIXME handle date line wrap - -goog.provide('ol.control.Attribution'); +goog.style.setPosition = function(el, arg1, opt_arg2) { + var x, y; -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.classlist'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('goog.style'); -goog.require('ol.Attribution'); -goog.require('ol.control.Control'); -goog.require('ol.css'); + if (arg1 instanceof goog.math.Coordinate) { + x = arg1.x; + y = arg1.y; + } else { + x = arg1; + y = opt_arg2; + } + el.style.left = goog.style.getPixelStyleValue_( + /** @type {number|string} */ (x), false); + el.style.top = goog.style.getPixelStyleValue_( + /** @type {number|string} */ (y), false); +}; /** - * @classdesc - * Control to show all the attributions associated with the layer sources - * in the map. This control is one of the default controls included in maps. - * By default it will show in the bottom right portion of the map, but this can - * be changed by using a css selector for `.ol-attribution`. - * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.AttributionOptions=} opt_options Attribution options. - * @api stable + * Gets the offsetLeft and offsetTop properties of an element and returns them + * in a Coordinate object + * @param {Element} element Element. + * @return {!goog.math.Coordinate} The position. */ -ol.control.Attribution = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {Element} - */ - this.ulElement_ = goog.dom.createElement(goog.dom.TagName.UL); - - /** - * @private - * @type {Element} - */ - this.logoLi_ = goog.dom.createElement(goog.dom.TagName.LI); - - goog.dom.appendChild(this.ulElement_, this.logoLi_); - goog.style.setElementShown(this.logoLi_, false); - - /** - * @private - * @type {boolean} - */ - this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true; +goog.style.getPosition = function(element) { + return new goog.math.Coordinate(element.offsetLeft, element.offsetTop); +}; - /** - * @private - * @type {boolean} - */ - this.collapsible_ = goog.isDef(options.collapsible) ? - options.collapsible : true; - if (!this.collapsible_) { - this.collapsed_ = false; +/** + * Returns the viewport element for a particular document + * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element + * of. + * @return {Element} document.documentElement or document.body. + */ +goog.style.getClientViewportElement = function(opt_node) { + var doc; + if (opt_node) { + doc = goog.dom.getOwnerDocument(opt_node); + } else { + doc = goog.dom.getDocument(); } - var className = goog.isDef(options.className) ? - options.className : 'ol-attribution'; - - var tipLabel = goog.isDef(options.tipLabel) ? - options.tipLabel : 'Attributions'; - - /** - * @private - * @type {string} - */ - this.collapseLabel_ = goog.isDef(options.collapseLabel) ? - options.collapseLabel : '\u00BB'; - - /** - * @private - * @type {string} - */ - this.label_ = goog.isDef(options.label) ? options.label : 'i'; - var label = goog.dom.createDom(goog.dom.TagName.SPAN, {}, - (this.collapsible_ && !this.collapsed_) ? - this.collapseLabel_ : this.label_); - - - /** - * @private - * @type {Element} - */ - this.labelSpan_ = label; - var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'type': 'button', - 'title': tipLabel - }, this.labelSpan_); - - goog.events.listen(button, goog.events.EventType.CLICK, - this.handleClick_, false, this); - - goog.events.listen(button, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); + // In old IE versions the document.body represented the viewport + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && + !goog.dom.getDomHelper(doc).isCss1CompatMode()) { + return doc.body; + } + return doc.documentElement; +}; - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL + - (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + - (this.collapsible_ ? '' : ' ol-uncollapsible'); - var element = goog.dom.createDom(goog.dom.TagName.DIV, - cssClasses, this.ulElement_, button); - var render = goog.isDef(options.render) ? - options.render : ol.control.Attribution.render; +/** + * Calculates the viewport coordinates relative to the page/document + * containing the node. The viewport may be the browser viewport for + * non-iframe document, or the iframe container for iframe'd document. + * @param {!Document} doc The document to use as the reference point. + * @return {!goog.math.Coordinate} The page offset of the viewport. + */ +goog.style.getViewportPageOffset = function(doc) { + var body = doc.body; + var documentElement = doc.documentElement; + var scrollLeft = body.scrollLeft || documentElement.scrollLeft; + var scrollTop = body.scrollTop || documentElement.scrollTop; + return new goog.math.Coordinate(scrollLeft, scrollTop); +}; - goog.base(this, { - element: element, - render: render, - target: options.target - }); - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; +/** + * Gets the client rectangle of the DOM element. + * + * getBoundingClientRect is part of a new CSS object model draft (with a + * long-time presence in IE), replacing the error-prone parent offset + * computation and the now-deprecated Gecko getBoxObjectFor. + * + * This utility patches common browser bugs in getBoundingClientRect. It + * will fail if getBoundingClientRect is unsupported. + * + * If the element is not in the DOM, the result is undefined, and an error may + * be thrown depending on user agent. + * + * @param {!Element} el The element whose bounding rectangle is being queried. + * @return {Object} A native bounding rectangle with numerical left, top, + * right, and bottom. Reported by Firefox to be of object type ClientRect. + * @private + */ +goog.style.getBoundingClientRect_ = function(el) { + var rect; + try { + rect = el.getBoundingClientRect(); + } catch (e) { + // In IE < 9, calling getBoundingClientRect on an orphan element raises an + // "Unspecified Error". All other browsers return zeros. + return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0}; + } - /** - * @private - * @type {Object.<string, Element>} - */ - this.attributionElements_ = {}; + // Patch the result in IE only, so that this function can be inlined if + // compiled for non-IE. + if (goog.userAgent.IE && el.ownerDocument.body) { - /** - * @private - * @type {Object.<string, boolean>} - */ - this.attributionElementRenderedVisible_ = {}; + // In IE, most of the time, 2 extra pixels are added to the top and left + // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and + // IE6 standards mode, this border can be overridden by setting the + // document element's border to zero -- thus, we cannot rely on the + // offset always being 2 pixels. - /** - * @private - * @type {Object.<string, Element>} - */ - this.logoElements_ = {}; + // In quirks mode, the offset can be determined by querying the body's + // clientLeft/clientTop, but in standards mode, it is found by querying + // the document element's clientLeft/clientTop. Since we already called + // getBoundingClientRect we have already forced a reflow, so it is not + // too expensive just to query them all. + // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx + var doc = el.ownerDocument; + rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; + rect.top -= doc.documentElement.clientTop + doc.body.clientTop; + } + return /** @type {Object} */ (rect); }; -goog.inherits(ol.control.Attribution, ol.control.Control); /** - * @param {?olx.FrameState} frameState Frame state. - * @return {Array.<Object.<string, ol.Attribution>>} Attributions. + * Returns the first parent that could affect the position of a given element. + * @param {Element} element The element to get the offset parent for. + * @return {Element} The first offset parent or null if one cannot be found. */ -ol.control.Attribution.prototype.getSourceAttributions = function(frameState) { - var i, ii, j, jj, tileRanges, source, sourceAttribution, - sourceAttributionKey, sourceAttributions, sourceKey; - var layerStatesArray = frameState.layerStatesArray; - /** @type {Object.<string, ol.Attribution>} */ - var attributions = goog.object.clone(frameState.attributions); - /** @type {Object.<string, ol.Attribution>} */ - var hiddenAttributions = {}; - for (i = 0, ii = layerStatesArray.length; i < ii; i++) { - source = layerStatesArray[i].layer.getSource(); - if (goog.isNull(source)) { - continue; - } - sourceKey = goog.getUid(source).toString(); - sourceAttributions = source.getAttributions(); - if (goog.isNull(sourceAttributions)) { - continue; - } - for (j = 0, jj = sourceAttributions.length; j < jj; j++) { - sourceAttribution = sourceAttributions[j]; - sourceAttributionKey = goog.getUid(sourceAttribution).toString(); - if (sourceAttributionKey in attributions) { - continue; - } - tileRanges = frameState.usedTiles[sourceKey]; - if (goog.isDef(tileRanges) && - sourceAttribution.intersectsAnyTileRange(tileRanges)) { - if (sourceAttributionKey in hiddenAttributions) { - delete hiddenAttributions[sourceAttributionKey]; - } - attributions[sourceAttributionKey] = sourceAttribution; - } - else { - hiddenAttributions[sourceAttributionKey] = sourceAttribution; - } +goog.style.getOffsetParent = function(element) { + // element.offsetParent does the right thing in IE7 and below. In other + // browsers it only includes elements with position absolute, relative or + // fixed, not elements with overflow set to auto or scroll. + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) { + return element.offsetParent; + } + + var doc = goog.dom.getOwnerDocument(element); + var positionStyle = goog.style.getStyle_(element, 'position'); + var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; + for (var parent = element.parentNode; parent && parent != doc; + parent = parent.parentNode) { + positionStyle = + goog.style.getStyle_(/** @type {!Element} */ (parent), 'position'); + skipStatic = skipStatic && positionStyle == 'static' && + parent != doc.documentElement && parent != doc.body; + if (!skipStatic && (parent.scrollWidth > parent.clientWidth || + parent.scrollHeight > parent.clientHeight || + positionStyle == 'fixed' || + positionStyle == 'absolute' || + positionStyle == 'relative')) { + return /** @type {!Element} */ (parent); } } - return [attributions, hiddenAttributions]; + return null; }; /** - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.Attribution} - * @api + * Calculates and returns the visible rectangle for a given element. Returns a + * box describing the visible portion of the nearest scrollable offset ancestor. + * Coordinates are given relative to the document. + * + * @param {Element} element Element to get the visible rect for. + * @return {goog.math.Box} Bounding elementBox describing the visible rect or + * null if scrollable ancestor isn't inside the visible viewport. */ -ol.control.Attribution.render = function(mapEvent) { - this.updateElement_(mapEvent.frameState); -}; - +goog.style.getVisibleRectForElement = function(element) { + var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); + var dom = goog.dom.getDomHelper(element); + var body = dom.getDocument().body; + var documentElement = dom.getDocument().documentElement; + var scrollEl = dom.getDocumentScrollElement(); -/** - * @private - * @param {?olx.FrameState} frameState Frame state. - */ -ol.control.Attribution.prototype.updateElement_ = function(frameState) { + // Determine the size of the visible rect by climbing the dom accounting for + // all scrollable containers. + for (var el = element; el = goog.style.getOffsetParent(el); ) { + // clientWidth is zero for inline block elements in IE. + // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0 + if ((!goog.userAgent.IE || el.clientWidth != 0) && + (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) && + // body may have overflow set on it, yet we still get the entire + // viewport. In some browsers, el.offsetParent may be + // document.documentElement, so check for that too. + (el != body && el != documentElement && + goog.style.getStyle_(el, 'overflow') != 'visible')) { + var pos = goog.style.getPageOffset(el); + var client = goog.style.getClientLeftTop(el); + pos.x += client.x; + pos.y += client.y; - if (goog.isNull(frameState)) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.element, false); - this.renderedVisible_ = false; + visibleRect.top = Math.max(visibleRect.top, pos.y); + visibleRect.right = Math.min(visibleRect.right, + pos.x + el.clientWidth); + visibleRect.bottom = Math.min(visibleRect.bottom, + pos.y + el.clientHeight); + visibleRect.left = Math.max(visibleRect.left, pos.x); } - return; } - var attributions = this.getSourceAttributions(frameState); - /** @type {Object.<string, ol.Attribution>} */ - var visibleAttributions = attributions[0]; - /** @type {Object.<string, ol.Attribution>} */ - var hiddenAttributions = attributions[1]; - - var attributionElement, attributionKey; - for (attributionKey in this.attributionElements_) { - if (attributionKey in visibleAttributions) { - if (!this.attributionElementRenderedVisible_[attributionKey]) { - goog.style.setElementShown( - this.attributionElements_[attributionKey], true); - this.attributionElementRenderedVisible_[attributionKey] = true; - } - delete visibleAttributions[attributionKey]; - } - else if (attributionKey in hiddenAttributions) { - if (this.attributionElementRenderedVisible_[attributionKey]) { - goog.style.setElementShown( - this.attributionElements_[attributionKey], false); - delete this.attributionElementRenderedVisible_[attributionKey]; - } - delete hiddenAttributions[attributionKey]; - } - else { - goog.dom.removeNode(this.attributionElements_[attributionKey]); - delete this.attributionElements_[attributionKey]; - delete this.attributionElementRenderedVisible_[attributionKey]; - } - } - for (attributionKey in visibleAttributions) { - attributionElement = goog.dom.createElement(goog.dom.TagName.LI); - attributionElement.innerHTML = - visibleAttributions[attributionKey].getHTML(); - goog.dom.appendChild(this.ulElement_, attributionElement); - this.attributionElements_[attributionKey] = attributionElement; - this.attributionElementRenderedVisible_[attributionKey] = true; - } - for (attributionKey in hiddenAttributions) { - attributionElement = goog.dom.createElement(goog.dom.TagName.LI); - attributionElement.innerHTML = - hiddenAttributions[attributionKey].getHTML(); - goog.style.setElementShown(attributionElement, false); - goog.dom.appendChild(this.ulElement_, attributionElement); - this.attributionElements_[attributionKey] = attributionElement; - } - - var renderVisible = - !goog.object.isEmpty(this.attributionElementRenderedVisible_) || - !goog.object.isEmpty(frameState.logos); - if (this.renderedVisible_ != renderVisible) { - goog.style.setElementShown(this.element, renderVisible); - this.renderedVisible_ = renderVisible; - } - if (renderVisible && - goog.object.isEmpty(this.attributionElementRenderedVisible_)) { - goog.dom.classlist.add(this.element, 'ol-logo-only'); - } else { - goog.dom.classlist.remove(this.element, 'ol-logo-only'); - } - - this.insertLogos_(frameState); - + // Clip by window's viewport. + var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; + visibleRect.left = Math.max(visibleRect.left, scrollX); + visibleRect.top = Math.max(visibleRect.top, scrollY); + var winSize = dom.getViewportSize(); + visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); + visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); + return visibleRect.top >= 0 && visibleRect.left >= 0 && + visibleRect.bottom > visibleRect.top && + visibleRect.right > visibleRect.left ? + visibleRect : null; }; /** - * @param {?olx.FrameState} frameState Frame state. - * @private + * Calculate the scroll position of {@code container} with the minimum amount so + * that the content and the borders of the given {@code element} become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * + * @param {Element} element The element to make visible. + * @param {Element} container The container to scroll. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. + * @return {!goog.math.Coordinate} The new scroll position of the container, + * in form of goog.math.Coordinate(scrollLeft, scrollTop). */ -ol.control.Attribution.prototype.insertLogos_ = function(frameState) { +goog.style.getContainerOffsetToScrollInto = + function(element, container, opt_center) { + // Absolute position of the element's border's top left corner. + var elementPos = goog.style.getPageOffset(element); + // Absolute position of the container's border's top left corner. + var containerPos = goog.style.getPageOffset(container); + var containerBorder = goog.style.getBorderBox(container); + // Relative pos. of the element's border box to the container's content box. + var relX = elementPos.x - containerPos.x - containerBorder.left; + var relY = elementPos.y - containerPos.y - containerBorder.top; + // How much the element can move in the container, i.e. the difference between + // the element's bottom-right-most and top-left-most position where it's + // fully visible. + var spaceX = container.clientWidth - element.offsetWidth; + var spaceY = container.clientHeight - element.offsetHeight; - var logo; - var logos = frameState.logos; - var logoElements = this.logoElements_; + var scrollLeft = container.scrollLeft; + var scrollTop = container.scrollTop; + if (container == goog.dom.getDocument().body || + container == goog.dom.getDocument().documentElement) { + // If the container is the document scroll element (usually <body>), + // getPageOffset(element) is already relative to it and there is no need to + // consider the current scroll. + scrollLeft = containerPos.x + containerBorder.left; + scrollTop = containerPos.y + containerBorder.top; - for (logo in logoElements) { - if (!(logo in logos)) { - goog.dom.removeNode(logoElements[logo]); - delete logoElements[logo]; + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) { + // In older versions of IE getPageOffset(element) does not include the + // continaer border so it has to be added to accomodate. + scrollLeft += containerBorder.left; + scrollTop += containerBorder.top; } } - - var image, logoElement, logoKey; - for (logoKey in logos) { - if (!(logoKey in logoElements)) { - image = new Image(); - image.src = logoKey; - var logoValue = logos[logoKey]; - if (logoValue === '') { - logoElement = image; - } else { - logoElement = goog.dom.createDom(goog.dom.TagName.A, { - 'href': logoValue - }); - logoElement.appendChild(image); - } - goog.dom.appendChild(this.logoLi_, logoElement); - logoElements[logoKey] = logoElement; - } + if (opt_center) { + // All browsers round non-integer scroll positions down. + scrollLeft += relX - spaceX / 2; + scrollTop += relY - spaceY / 2; + } else { + // This formula was designed to give the correct scroll values in the + // following cases: + // - element is higher than container (spaceY < 0) => scroll down by relY + // - element is not higher that container (spaceY >= 0): + // - it is above container (relY < 0) => scroll up by abs(relY) + // - it is below container (relY > spaceY) => scroll down by relY - spaceY + // - it is in the container => don't scroll + scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0)); + scrollTop += Math.min(relY, Math.max(relY - spaceY, 0)); } - - goog.style.setElementShown(this.logoLi_, !goog.object.isEmpty(logos)); - + return new goog.math.Coordinate(scrollLeft, scrollTop); }; /** - * @param {goog.events.BrowserEvent} event The event to handle - * @private + * Changes the scroll position of {@code container} with the minimum amount so + * that the content and the borders of the given {@code element} become visible. + * If the element is bigger than the container, its top left corner will be + * aligned as close to the container's top left corner as possible. + * + * @param {Element} element The element to make visible. + * @param {Element} container The container to scroll. + * @param {boolean=} opt_center Whether to center the element in the container. + * Defaults to false. */ -ol.control.Attribution.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.handleToggle_(); +goog.style.scrollIntoContainerView = function(element, container, opt_center) { + var offset = + goog.style.getContainerOffsetToScrollInto(element, container, opt_center); + container.scrollLeft = offset.x; + container.scrollTop = offset.y; }; /** - * @private + * Returns clientLeft (width of the left border and, if the directionality is + * right to left, the vertical scrollbar) and clientTop as a coordinate object. + * + * @param {Element} el Element to get clientLeft for. + * @return {!goog.math.Coordinate} Client left and top. */ -ol.control.Attribution.prototype.handleToggle_ = function() { - goog.dom.classlist.toggle(this.element, 'ol-collapsed'); - goog.dom.setTextContent(this.labelSpan_, - (this.collapsed_) ? this.collapseLabel_ : this.label_); - this.collapsed_ = !this.collapsed_; +goog.style.getClientLeftTop = function(el) { + return new goog.math.Coordinate(el.clientLeft, el.clientTop); }; /** - * @return {boolean} True if the widget is collapsible. - * @api stable + * Returns a Coordinate object relative to the top-left of the HTML document. + * Implemented as a single function to save having to do two recursive loops in + * opera and safari just to get both coordinates. If you just want one value do + * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but + * note if you call both those methods the tree will be analysed twice. + * + * @param {Element} el Element to get the page offset for. + * @return {!goog.math.Coordinate} The page offset. */ -ol.control.Attribution.prototype.getCollapsible = function() { - return this.collapsible_; -}; +goog.style.getPageOffset = function(el) { + var doc = goog.dom.getOwnerDocument(el); + var positionStyle = goog.style.getStyle_(el, 'position'); + // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe. + goog.asserts.assertObject(el, 'Parameter is required'); + // NOTE(arv): If element is hidden (display none or disconnected or any the + // ancestors are hidden) we get (0,0) by default but we still do the + // accumulation of scroll position. -/** - * @param {boolean} collapsible True if the widget is collapsible. - * @api stable - */ -ol.control.Attribution.prototype.setCollapsible = function(collapsible) { - if (this.collapsible_ === collapsible) { - return; - } - this.collapsible_ = collapsible; - goog.dom.classlist.toggle(this.element, 'ol-uncollapsible'); - if (!collapsible && this.collapsed_) { - this.handleToggle_(); + // TODO(arv): Should we check if the node is disconnected and in that case + // return (0,0)? + + var pos = new goog.math.Coordinate(0, 0); + var viewportElement = goog.style.getClientViewportElement(doc); + if (el == viewportElement) { + // viewport is always at 0,0 as that defined the coordinate system for this + // function - this avoids special case checks in the code below + return pos; } + + var box = goog.style.getBoundingClientRect_(el); + // Must add the scroll coordinates in to get the absolute page offset + // of element since getBoundingClientRect returns relative coordinates to + // the viewport. + var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); + pos.x = box.left + scrollCoord.x; + pos.y = box.top + scrollCoord.y; + + return pos; }; /** - * @param {boolean} collapsed True if the widget is collapsed. - * @api stable + * Returns the left coordinate of an element relative to the HTML document + * @param {Element} el Elements. + * @return {number} The left coordinate. */ -ol.control.Attribution.prototype.setCollapsed = function(collapsed) { - if (!this.collapsible_ || this.collapsed_ === collapsed) { - return; - } - this.handleToggle_(); +goog.style.getPageOffsetLeft = function(el) { + return goog.style.getPageOffset(el).x; }; /** - * @return {boolean} True if the widget is collapsed. - * @api stable + * Returns the top coordinate of an element relative to the HTML document + * @param {Element} el Elements. + * @return {number} The top coordinate. */ -ol.control.Attribution.prototype.getCollapsed = function() { - return this.collapsed_; +goog.style.getPageOffsetTop = function(el) { + return goog.style.getPageOffset(el).y; }; -goog.provide('ol.control.Rotate'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.classlist'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.math'); -goog.require('ol.animation'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.easing'); - - /** - * @classdesc - * A button control to reset rotation to 0. - * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css - * selector is added to the button when the rotation is 0. + * Returns a Coordinate object relative to the top-left of an HTML document + * in an ancestor frame of this element. Used for measuring the position of + * an element inside a frame relative to a containing frame. * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.RotateOptions=} opt_options Rotate options. - * @api stable + * @param {Element} el Element to get the page offset for. + * @param {Window} relativeWin The window to measure relative to. If relativeWin + * is not in the ancestor frame chain of the element, we measure relative to + * the top-most window. + * @return {!goog.math.Coordinate} The page offset. */ -ol.control.Rotate = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - var className = goog.isDef(options.className) ? - options.className : 'ol-rotate'; - - /** - * @type {Element} - * @private - */ - this.label_ = goog.dom.createDom(goog.dom.TagName.SPAN, - 'ol-compass', goog.isDef(options.label) ? options.label : '\u21E7'); - - var tipLabel = goog.isDef(options.tipLabel) ? - options.tipLabel : 'Reset rotation'; - - var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'class': className + '-reset', - 'type' : 'button', - 'title': tipLabel - }, this.label_); - - goog.events.listen(button, goog.events.EventType.CLICK, - ol.control.Rotate.prototype.handleClick_, false, this); +goog.style.getFramedPageOffset = function(el, relativeWin) { + var position = new goog.math.Coordinate(0, 0); - goog.events.listen(button, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); + // Iterate up the ancestor frame chain, keeping track of the current window + // and the current element in that window. + var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); + var currentEl = el; + do { + // if we're at the top window, we want to get the page offset. + // if we're at an inner frame, we only want to get the window position + // so that we can determine the actual page offset in the context of + // the outer window. + var offset = currentWin == relativeWin ? + goog.style.getPageOffset(currentEl) : + goog.style.getClientPositionForElement_( + goog.asserts.assert(currentEl)); - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL; - var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); + position.x += offset.x; + position.y += offset.y; + } while (currentWin && currentWin != relativeWin && + currentWin != currentWin.parent && + (currentEl = currentWin.frameElement) && + (currentWin = currentWin.parent)); - var render = goog.isDef(options.render) ? - options.render : ol.control.Rotate.render; + return position; +}; - goog.base(this, { - element: element, - render: render, - target: options.target - }); - /** - * @type {number} - * @private - */ - this.duration_ = goog.isDef(options.duration) ? options.duration : 250; +/** + * Translates the specified rect relative to origBase page, for newBase page. + * If origBase and newBase are the same, this function does nothing. + * + * @param {goog.math.Rect} rect The source rectangle relative to origBase page, + * and it will have the translated result. + * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle. + * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant + * coordinate. This must be a DOM for an ancestor frame of origBase + * or the same as origBase. + */ +goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { + if (origBase.getDocument() != newBase.getDocument()) { + var body = origBase.getDocument().body; + var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); - /** - * @type {boolean} - * @private - */ - this.autoHide_ = goog.isDef(options.autoHide) ? options.autoHide : true; + // Adjust Body's margin. + pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = undefined; + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) && + !origBase.isCss1CompatMode()) { + pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); + } - if (this.autoHide_) { - goog.dom.classlist.add(this.element, ol.css.CLASS_HIDDEN); + rect.left += pos.x; + rect.top += pos.y; } - }; -goog.inherits(ol.control.Rotate, ol.control.Control); /** - * @param {goog.events.BrowserEvent} event The event to handle - * @private + * Returns the position of an element relative to another element in the + * document. A relative to B + * @param {Element|Event|goog.events.Event} a Element or mouse event whose + * position we're calculating. + * @param {Element|Event|goog.events.Event} b Element or mouse event position + * is relative to. + * @return {!goog.math.Coordinate} The relative position. */ -ol.control.Rotate.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.resetNorth_(); +goog.style.getRelativePosition = function(a, b) { + var ap = goog.style.getClientPosition(a); + var bp = goog.style.getClientPosition(b); + return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); }; /** + * Returns the position of the event or the element's border box relative to + * the client viewport. + * @param {!Element} el Element whose position to get. + * @return {!goog.math.Coordinate} The position. * @private */ -ol.control.Rotate.prototype.resetNorth_ = function() { - var map = this.getMap(); - var view = map.getView(); - if (goog.isNull(view)) { - // the map does not have a view, so we can't act - // upon it - return; - } - var currentRotation = view.getRotation(); - while (currentRotation < -Math.PI) { - currentRotation += 2 * Math.PI; - } - while (currentRotation > Math.PI) { - currentRotation -= 2 * Math.PI; - } - if (goog.isDef(currentRotation)) { - if (this.duration_ > 0) { - map.beforeRender(ol.animation.rotate({ - rotation: currentRotation, - duration: this.duration_, - easing: ol.easing.easeOut - })); - } - view.setRotation(0); - } +goog.style.getClientPositionForElement_ = function(el) { + var box = goog.style.getBoundingClientRect_(el); + return new goog.math.Coordinate(box.left, box.top); }; /** - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.Rotate} - * @api + * Returns the position of the event or the element's border box relative to + * the client viewport. + * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event. + * @return {!goog.math.Coordinate} The position. */ -ol.control.Rotate.render = function(mapEvent) { - var frameState = mapEvent.frameState; - if (goog.isNull(frameState)) { - return; - } - var rotation = frameState.viewState.rotation; - if (rotation != this.rotation_) { - var transform = 'rotate(' + goog.math.toDegrees(rotation) + 'deg)'; - if (this.autoHide_) { - goog.dom.classlist.enable( - this.element, ol.css.CLASS_HIDDEN, rotation === 0); +goog.style.getClientPosition = function(el) { + goog.asserts.assert(el); + if (el.nodeType == goog.dom.NodeType.ELEMENT) { + return goog.style.getClientPositionForElement_( + /** @type {!Element} */ (el)); + } else { + var isAbstractedEvent = goog.isFunction(el.getBrowserEvent); + var be = /** @type {!goog.events.BrowserEvent} */ (el); + var targetEvent = el; + + if (el.targetTouches && el.targetTouches.length) { + targetEvent = el.targetTouches[0]; + } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches && + be.getBrowserEvent().targetTouches.length) { + targetEvent = be.getBrowserEvent().targetTouches[0]; } - this.label_.style.msTransform = transform; - this.label_.style.webkitTransform = transform; - this.label_.style.transform = transform; + + return new goog.math.Coordinate( + targetEvent.clientX, + targetEvent.clientY); } - this.rotation_ = rotation; }; -goog.provide('ol.control.Zoom'); - -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.animation'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.easing'); - - /** - * @classdesc - * A control with 2 buttons, one for zoom in and one for zoom out. - * This control is one of the default controls of a map. To style this control - * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. - * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.ZoomOptions=} opt_options Zoom options. - * @api stable + * Moves an element to the given coordinates relative to the client viewport. + * @param {Element} el Absolutely positioned element to set page offset for. + * It must be in the document. + * @param {number|goog.math.Coordinate} x Left position of the element's margin + * box or a coordinate object. + * @param {number=} opt_y Top position of the element's margin box. */ -ol.control.Zoom = function(opt_options) { +goog.style.setPageOffset = function(el, x, opt_y) { + // Get current pageoffset + var cur = goog.style.getPageOffset(el); - var options = goog.isDef(opt_options) ? opt_options : {}; + if (x instanceof goog.math.Coordinate) { + opt_y = x.y; + x = x.x; + } - var className = goog.isDef(options.className) ? options.className : 'ol-zoom'; + // NOTE(arv): We cannot allow strings for x and y. We could but that would + // require us to manually transform between different units - var delta = goog.isDef(options.delta) ? options.delta : 1; + // Work out deltas + var dx = x - cur.x; + var dy = opt_y - cur.y; - var zoomInLabel = goog.isDef(options.zoomInLabel) ? - options.zoomInLabel : '+'; - var zoomOutLabel = goog.isDef(options.zoomOutLabel) ? - options.zoomOutLabel : '\u2212'; + // Set position to current left/top + delta + goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy); +}; - var zoomInTipLabel = goog.isDef(options.zoomInTipLabel) ? - options.zoomInTipLabel : 'Zoom in'; - var zoomOutTipLabel = goog.isDef(options.zoomOutTipLabel) ? - options.zoomOutTipLabel : 'Zoom out'; - var inElement = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'class': className + '-in', - 'type' : 'button', - 'title': zoomInTipLabel - }, zoomInLabel); - - goog.events.listen(inElement, - goog.events.EventType.CLICK, goog.partial( - ol.control.Zoom.prototype.handleClick_, delta), false, this); +/** + * Sets the width/height values of an element. If an argument is numeric, + * or a goog.math.Size is passed, it is assumed to be pixels and will add + * 'px' after converting it to an integer in string form. (This just sets the + * CSS width and height properties so it might set content-box or border-box + * size depending on the box model the browser is using.) + * + * @param {Element} element Element to set the size of. + * @param {string|number|goog.math.Size} w Width of the element, or a + * size object. + * @param {string|number=} opt_h Height of the element. Required if w is not a + * size object. + */ +goog.style.setSize = function(element, w, opt_h) { + var h; + if (w instanceof goog.math.Size) { + h = w.height; + w = w.width; + } else { + if (opt_h == undefined) { + throw Error('missing height argument'); + } + h = opt_h; + } - goog.events.listen(inElement, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); + goog.style.setWidth(element, /** @type {string|number} */ (w)); + goog.style.setHeight(element, /** @type {string|number} */ (h)); +}; - var outElement = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'class': className + '-out', - 'type' : 'button', - 'title': zoomOutTipLabel - }, zoomOutLabel); - goog.events.listen(outElement, - goog.events.EventType.CLICK, goog.partial( - ol.control.Zoom.prototype.handleClick_, -delta), false, this); +/** + * Helper function to create a string to be set into a pixel-value style + * property of an element. Can round to the nearest integer value. + * + * @param {string|number} value The style value to be used. If a number, + * 'px' will be appended, otherwise the value will be applied directly. + * @param {boolean} round Whether to round the nearest integer (if property + * is a number). + * @return {string} The string value for the property. + * @private + */ +goog.style.getPixelStyleValue_ = function(value, round) { + if (typeof value == 'number') { + value = (round ? Math.round(value) : value) + 'px'; + } - goog.events.listen(outElement, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); + return value; +}; - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL; - var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, inElement, - outElement); - goog.base(this, { - element: element, - target: options.target - }); +/** + * Set the height of an element. Sets the element's style property. + * @param {Element} element Element to set the height of. + * @param {string|number} height The height value to set. If a number, 'px' + * will be appended, otherwise the value will be applied directly. + */ +goog.style.setHeight = function(element, height) { + element.style.height = goog.style.getPixelStyleValue_(height, true); +}; - /** - * @type {number} - * @private - */ - this.duration_ = goog.isDef(options.duration) ? options.duration : 250; +/** + * Set the width of an element. Sets the element's style property. + * @param {Element} element Element to set the width of. + * @param {string|number} width The width value to set. If a number, 'px' + * will be appended, otherwise the value will be applied directly. + */ +goog.style.setWidth = function(element, width) { + element.style.width = goog.style.getPixelStyleValue_(width, true); }; -goog.inherits(ol.control.Zoom, ol.control.Control); /** - * @param {number} delta Zoom delta. - * @param {goog.events.BrowserEvent} event The event to handle - * @private + * Gets the height and width of an element, even if its display is none. + * + * Specifically, this returns the height and width of the border box, + * irrespective of the box model in effect. + * + * Note that this function does not take CSS transforms into account. Please see + * {@code goog.style.getTransformedSize}. + * @param {Element} element Element to get size of. + * @return {!goog.math.Size} Object with width/height properties. */ -ol.control.Zoom.prototype.handleClick_ = function(delta, event) { - event.preventDefault(); - this.zoomByDelta_(delta); +goog.style.getSize = function(element) { + return goog.style.evaluateWithTemporaryDisplay_( + goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element)); }; /** - * @param {number} delta Zoom delta. + * Call {@code fn} on {@code element} such that {@code element}'s dimensions are + * accurate when it's passed to {@code fn}. + * @param {function(!Element): T} fn Function to call with {@code element} as + * an argument after temporarily changing {@code element}'s display such + * that its dimensions are accurate. + * @param {!Element} element Element (which may have display none) to use as + * argument to {@code fn}. + * @return {T} Value returned by calling {@code fn} with {@code element}. + * @template T * @private */ -ol.control.Zoom.prototype.zoomByDelta_ = function(delta) { - var map = this.getMap(); - var view = map.getView(); - if (goog.isNull(view)) { - // the map does not have a view, so we can't act - // upon it - return; - } - var currentResolution = view.getResolution(); - if (goog.isDef(currentResolution)) { - if (this.duration_ > 0) { - map.beforeRender(ol.animation.zoom({ - resolution: currentResolution, - duration: this.duration_, - easing: ol.easing.easeOut - })); - } - var newResolution = view.constrainResolution(currentResolution, delta); - view.setResolution(newResolution); +goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) { + if (goog.style.getStyle_(element, 'display') != 'none') { + return fn(element); } -}; -goog.provide('ol.control'); + var style = element.style; + var originalDisplay = style.display; + var originalVisibility = style.visibility; + var originalPosition = style.position; -goog.require('ol.Collection'); -goog.require('ol.control.Attribution'); -goog.require('ol.control.Rotate'); -goog.require('ol.control.Zoom'); + style.visibility = 'hidden'; + style.position = 'absolute'; + style.display = 'inline'; + var retVal = fn(element); -/** - * Set of controls included in maps by default. Unless configured otherwise, - * this returns a collection containing an instance of each of the following - * controls: - * * {@link ol.control.Zoom} - * * {@link ol.control.Rotate} - * * {@link ol.control.Attribution} - * - * @param {olx.control.DefaultsOptions=} opt_options Defaults options. - * @return {ol.Collection.<ol.control.Control>} Controls. - * @api stable - */ -ol.control.defaults = function(opt_options) { + style.display = originalDisplay; + style.position = originalPosition; + style.visibility = originalVisibility; - var options = goog.isDef(opt_options) ? opt_options : {}; + return retVal; +}; - var controls = new ol.Collection(); - var zoomControl = goog.isDef(options.zoom) ? - options.zoom : true; - if (zoomControl) { - controls.push(new ol.control.Zoom(options.zoomOptions)); +/** + * Gets the height and width of an element when the display is not none. + * @param {Element} element Element to get size of. + * @return {!goog.math.Size} Object with width/height properties. + * @private + */ +goog.style.getSizeWithDisplay_ = function(element) { + var offsetWidth = element.offsetWidth; + var offsetHeight = element.offsetHeight; + var webkitOffsetsZero = + goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight; + if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) && + element.getBoundingClientRect) { + // Fall back to calling getBoundingClientRect when offsetWidth or + // offsetHeight are not defined, or when they are zero in WebKit browsers. + // This makes sure that we return for the correct size for SVG elements, but + // will still return 0 on Webkit prior to 534.8, see + // http://trac.webkit.org/changeset/67252. + var clientRect = goog.style.getBoundingClientRect_(element); + return new goog.math.Size(clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top); } + return new goog.math.Size(offsetWidth, offsetHeight); +}; - var rotateControl = goog.isDef(options.rotate) ? - options.rotate : true; - if (rotateControl) { - controls.push(new ol.control.Rotate(options.rotateOptions)); - } - var attributionControl = goog.isDef(options.attribution) ? - options.attribution : true; - if (attributionControl) { - controls.push(new ol.control.Attribution(options.attributionOptions)); +/** + * Gets the height and width of an element, post transform, even if its display + * is none. + * + * This is like {@code goog.style.getSize}, except: + * <ol> + * <li>Takes webkitTransforms such as rotate and scale into account. + * <li>Will return null if {@code element} doesn't respond to + * {@code getBoundingClientRect}. + * <li>Currently doesn't make sense on non-WebKit browsers which don't support + * webkitTransforms. + * </ol> + * @param {!Element} element Element to get size of. + * @return {goog.math.Size} Object with width/height properties. + */ +goog.style.getTransformedSize = function(element) { + if (!element.getBoundingClientRect) { + return null; } - return controls; - + var clientRect = goog.style.evaluateWithTemporaryDisplay_( + goog.style.getBoundingClientRect_, element); + return new goog.math.Size(clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top); }; -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Functions for managing full screen status of the DOM. - * + * Returns a bounding rectangle for a given element in page space. + * @param {Element} element Element to get bounds of. Must not be display none. + * @return {!goog.math.Rect} Bounding rectangle for the element. */ +goog.style.getBounds = function(element) { + var o = goog.style.getPageOffset(element); + var s = goog.style.getSize(element); + return new goog.math.Rect(o.x, o.y, s.width, s.height); +}; -goog.provide('goog.dom.fullscreen'); -goog.provide('goog.dom.fullscreen.EventType'); -goog.require('goog.dom'); -goog.require('goog.userAgent'); +/** + * Converts a CSS selector in the form style-property to styleProperty. + * @param {*} selector CSS Selector. + * @return {string} Camel case selector. + * @deprecated Use goog.string.toCamelCase instead. + */ +goog.style.toCamelCase = function(selector) { + return goog.string.toCamelCase(String(selector)); +}; /** - * Event types for full screen. - * @enum {string} + * Converts a CSS selector in the form styleProperty to style-property. + * @param {string} selector Camel case selector. + * @return {string} Selector cased. + * @deprecated Use goog.string.toSelectorCase instead. */ -goog.dom.fullscreen.EventType = { - /** Dispatched by the Document when the fullscreen status changes. */ - CHANGE: (function() { - if (goog.userAgent.WEBKIT) { - return 'webkitfullscreenchange'; - } - if (goog.userAgent.GECKO) { - return 'mozfullscreenchange'; - } - if (goog.userAgent.IE) { - return 'MSFullscreenChange'; - } - // Opera 12-14, and W3C standard (Draft): - // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html - return 'fullscreenchange'; - })() +goog.style.toSelectorCase = function(selector) { + return goog.string.toSelectorCase(selector); }; /** - * Determines if full screen is supported. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {boolean} True iff full screen is supported. + * Gets the opacity of a node (x-browser). This gets the inline style opacity + * of the node, and does not take into account the cascaded or the computed + * style for this node. + * @param {Element} el Element whose opacity has to be found. + * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} + * if the opacity is not set. */ -goog.dom.fullscreen.isSupported = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - var body = doc.body; - return !!(body.webkitRequestFullscreen || - (body.mozRequestFullScreen && doc.mozFullScreenEnabled) || - (body.msRequestFullscreen && doc.msFullscreenEnabled) || - (body.requestFullscreen && doc.fullscreenEnabled)); +goog.style.getOpacity = function(el) { + var style = el.style; + var result = ''; + if ('opacity' in style) { + result = style.opacity; + } else if ('MozOpacity' in style) { + result = style.MozOpacity; + } else if ('filter' in style) { + var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); + if (match) { + result = String(match[1] / 100); + } + } + return result == '' ? result : Number(result); }; /** - * Requests putting the element in full screen. - * @param {!Element} element The element to put full screen. + * Sets the opacity of a node (x-browser). + * @param {Element} el Elements whose opacity has to be set. + * @param {number|string} alpha Opacity between 0 and 1 or an empty string + * {@code ''} to clear the opacity. */ -goog.dom.fullscreen.requestFullScreen = function(element) { - if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); - } else if (element.mozRequestFullScreen) { - element.mozRequestFullScreen(); - } else if (element.msRequestFullscreen) { - element.msRequestFullscreen(); - } else if (element.requestFullscreen) { - element.requestFullscreen(); +goog.style.setOpacity = function(el, alpha) { + var style = el.style; + if ('opacity' in style) { + style.opacity = alpha; + } else if ('MozOpacity' in style) { + style.MozOpacity = alpha; + } else if ('filter' in style) { + // TODO(arv): Overwriting the filter might have undesired side effects. + if (alpha === '') { + style.filter = ''; + } else { + style.filter = 'alpha(opacity=' + alpha * 100 + ')'; + } } }; /** - * Requests putting the element in full screen with full keyboard access. - * @param {!Element} element The element to put full screen. + * Sets the background of an element to a transparent image in a browser- + * independent manner. + * + * This function does not support repeating backgrounds or alternate background + * positions to match the behavior of Internet Explorer. It also does not + * support sizingMethods other than crop since they cannot be replicated in + * browsers other than Internet Explorer. + * + * @param {Element} el The element to set background on. + * @param {string} src The image source URL. */ -goog.dom.fullscreen.requestFullScreenWithKeys = function( - element) { - if (element.mozRequestFullScreenWithKeys) { - element.mozRequestFullScreenWithKeys(); - } else if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); +goog.style.setTransparentBackgroundImage = function(el, src) { + var style = el.style; + // It is safe to use the style.filter in IE only. In Safari 'filter' is in + // style object but access to style.filter causes it to throw an exception. + // Note: IE8 supports images with an alpha channel. + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + // See TODO in setOpacity. + style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + + 'src="' + src + '", sizingMethod="crop")'; } else { - goog.dom.fullscreen.requestFullScreen(element); + // Set style properties individually instead of using background shorthand + // to prevent overwriting a pre-existing background color. + style.backgroundImage = 'url(' + src + ')'; + style.backgroundPosition = 'top left'; + style.backgroundRepeat = 'no-repeat'; } }; /** - * Exits full screen. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. + * Clears the background image of an element in a browser independent manner. + * @param {Element} el The element to clear background image for. */ -goog.dom.fullscreen.exitFullScreen = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - if (doc.webkitCancelFullScreen) { - doc.webkitCancelFullScreen(); - } else if (doc.mozCancelFullScreen) { - doc.mozCancelFullScreen(); - } else if (doc.msExitFullscreen) { - doc.msExitFullscreen(); - } else if (doc.exitFullscreen) { - doc.exitFullscreen(); +goog.style.clearTransparentBackgroundImage = function(el) { + var style = el.style; + if ('filter' in style) { + // See TODO in setOpacity. + style.filter = ''; + } else { + // Set style properties individually instead of using background shorthand + // to prevent overwriting a pre-existing background color. + style.backgroundImage = 'none'; } }; /** - * Determines if the document is full screen. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {boolean} Whether the document is full screen. + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules.) + * + * Caveat 1: if the inherited display property for the element is set to "none" + * by the stylesheets, that is the property that will be restored by a call to + * showElement(), effectively toggling the display between "none" and "none". + * + * Caveat 2: if the element display style is set inline (by setting either + * element.style.display or a style attribute in the HTML), a call to + * showElement will clear that setting and defer to the inherited style in the + * stylesheet. + * @param {Element} el Element to show or hide. + * @param {*} display True to render the element in its default style, + * false to disable rendering the element. + * @deprecated Use goog.style.setElementShown instead. */ -goog.dom.fullscreen.isFullScreen = function(opt_domHelper) { - var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); - // IE 11 doesn't have similar boolean property, so check whether - // document.msFullscreenElement is null instead. - return !!(doc.webkitIsFullScreen || doc.mozFullScreen || - doc.msFullscreenElement || doc.fullscreenElement); +goog.style.showElement = function(el, display) { + goog.style.setElementShown(el, display); }; /** - * Gets the document object of the dom. - * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being - * queried. If not provided, use the current DOM. - * @return {!Document} The dom document. - * @private + * Shows or hides an element from the page. Hiding the element is done by + * setting the display property to "none", removing the element from the + * rendering hierarchy so it takes up no space. To show the element, the default + * inherited display property is restored (defined either in stylesheets or by + * the browser's default style rules). + * + * Caveat 1: if the inherited display property for the element is set to "none" + * by the stylesheets, that is the property that will be restored by a call to + * setElementShown(), effectively toggling the display between "none" and + * "none". + * + * Caveat 2: if the element display style is set inline (by setting either + * element.style.display or a style attribute in the HTML), a call to + * setElementShown will clear that setting and defer to the inherited style in + * the stylesheet. + * @param {Element} el Element to show or hide. + * @param {*} isShown True to render the element in its default style, + * false to disable rendering the element. */ -goog.dom.fullscreen.getDocument_ = function(opt_domHelper) { - return opt_domHelper ? - opt_domHelper.getDocument() : - goog.dom.getDomHelper().getDocument(); +goog.style.setElementShown = function(el, isShown) { + el.style.display = isShown ? '' : 'none'; }; -goog.provide('ol.control.FullScreen'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.classlist'); -goog.require('goog.dom.fullscreen'); -goog.require('goog.dom.fullscreen.EventType'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.control.Control'); -goog.require('ol.css'); - - /** - * @classdesc - * Provides a button that when clicked fills up the full screen with the map. - * When in full screen mode, a close button is shown to exit full screen mode. - * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to - * toggle the map in full screen mode. + * Test whether the given element has been shown or hidden via a call to + * {@link #setElementShown}. * + * Note this is strictly a companion method for a call + * to {@link #setElementShown} and the same caveats apply; in particular, this + * method does not guarantee that the return value will be consistent with + * whether or not the element is actually visible. * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.FullScreenOptions=} opt_options Options. - * @api stable + * @param {Element} el The element to test. + * @return {boolean} Whether the element has been shown. + * @see #setElementShown */ -ol.control.FullScreen = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {string} - */ - this.cssClassName_ = goog.isDef(options.className) ? - options.className : 'ol-full-screen'; - - var tipLabel = goog.isDef(options.tipLabel) ? - options.tipLabel : 'Toggle full-screen'; - var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(), - 'type': 'button', - 'title': tipLabel - }); - - goog.events.listen(button, goog.events.EventType.CLICK, - this.handleClick_, false, this); - - goog.events.listen(button, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); - - goog.events.listen(goog.global.document, - goog.dom.fullscreen.EventType.CHANGE, - this.handleFullScreenChange_, false, this); +goog.style.isElementShown = function(el) { + return el.style.display != 'none'; +}; - var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + - ' ' + ol.css.CLASS_CONTROL + ' ' + - (!goog.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : ''); - var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); - goog.base(this, { - element: element, - target: options.target - }); +/** + * Installs the styles string into the window that contains opt_element. If + * opt_element is null, the main window is used. + * @param {string} stylesString The style string to install. + * @param {Node=} opt_node Node whose parent document should have the + * styles installed. + * @return {Element|StyleSheet} The style element created. + */ +goog.style.installStyles = function(stylesString, opt_node) { + var dh = goog.dom.getDomHelper(opt_node); + var styleSheet = null; - /** - * @private - * @type {boolean} - */ - this.keys_ = goog.isDef(options.keys) ? options.keys : false; + // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be + // undefined as of IE 11. + var doc = dh.getDocument(); + if (goog.userAgent.IE && doc.createStyleSheet) { + styleSheet = doc.createStyleSheet(); + goog.style.setStyles(styleSheet, stylesString); + } else { + var head = dh.getElementsByTagNameAndClass('head')[0]; + // In opera documents are not guaranteed to have a head element, thus we + // have to make sure one exists before using it. + if (!head) { + var body = dh.getElementsByTagNameAndClass('body')[0]; + head = dh.createDom('head'); + body.parentNode.insertBefore(head, body); + } + styleSheet = dh.createDom('style'); + // NOTE(user): Setting styles after the style element has been appended + // to the head results in a nasty Webkit bug in certain scenarios. Please + // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional + // details. + goog.style.setStyles(styleSheet, stylesString); + dh.appendChild(head, styleSheet); + } + return styleSheet; }; -goog.inherits(ol.control.FullScreen, ol.control.Control); /** - * @param {goog.events.BrowserEvent} event The event to handle - * @private + * Removes the styles added by {@link #installStyles}. + * @param {Element|StyleSheet} styleSheet The value returned by + * {@link #installStyles}. */ -ol.control.FullScreen.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.handleFullScreen_(); +goog.style.uninstallStyles = function(styleSheet) { + var node = styleSheet.ownerNode || styleSheet.owningElement || + /** @type {Element} */ (styleSheet); + goog.dom.removeNode(node); }; /** - * @private + * Sets the content of a style element. The style element can be any valid + * style element. This element will have its content completely replaced by + * the new stylesString. + * @param {Element|StyleSheet} element A stylesheet element as returned by + * installStyles. + * @param {string} stylesString The new content of the stylesheet. */ -ol.control.FullScreen.prototype.handleFullScreen_ = function() { - if (!goog.dom.fullscreen.isSupported()) { - return; - } - var map = this.getMap(); - if (goog.isNull(map)) { - return; - } - if (goog.dom.fullscreen.isFullScreen()) { - goog.dom.fullscreen.exitFullScreen(); +goog.style.setStyles = function(element, stylesString) { + if (goog.userAgent.IE && goog.isDef(element.cssText)) { + // Adding the selectors individually caused the browser to hang if the + // selector was invalid or there were CSS comments. Setting the cssText of + // the style node works fine and ignores CSS that IE doesn't understand. + // However IE >= 11 doesn't support cssText any more, so we make sure that + // cssText is a defined property and otherwise fall back to innerHTML. + element.cssText = stylesString; } else { - var target = map.getTarget(); - goog.asserts.assert(goog.isDefAndNotNull(target)); - var element = goog.dom.getElement(target); - goog.asserts.assert(goog.isDefAndNotNull(element)); - if (this.keys_) { - goog.dom.fullscreen.requestFullScreenWithKeys(element); - } else { - goog.dom.fullscreen.requestFullScreen(element); - } + element.innerHTML = stylesString; } }; /** - * @private + * Sets 'white-space: pre-wrap' for a node (x-browser). + * + * There are as many ways of specifying pre-wrap as there are browsers. + * + * CSS3/IE8: white-space: pre-wrap; + * Mozilla: white-space: -moz-pre-wrap; + * Opera: white-space: -o-pre-wrap; + * IE6/7: white-space: pre; word-wrap: break-word; + * + * @param {Element} el Element to enable pre-wrap for. */ -ol.control.FullScreen.prototype.handleFullScreenChange_ = function() { - var opened = this.cssClassName_ + '-true'; - var closed = this.cssClassName_ + '-false'; - var anchor = goog.dom.getFirstElementChild(this.element); - var map = this.getMap(); - if (goog.dom.fullscreen.isFullScreen()) { - goog.dom.classlist.swap(anchor, closed, opened); +goog.style.setPreWrap = function(el) { + var style = el.style; + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + style.whiteSpace = 'pre'; + style.wordWrap = 'break-word'; + } else if (goog.userAgent.GECKO) { + style.whiteSpace = '-moz-pre-wrap'; } else { - goog.dom.classlist.swap(anchor, opened, closed); - } - if (!goog.isNull(map)) { - map.updateSize(); + style.whiteSpace = 'pre-wrap'; } }; -goog.provide('ol.Pixel'); - /** - * An array with two elements, representing a pixel. The first element is the - * x-coordinate, the second the y-coordinate of the pixel. - * @typedef {Array.<number>} - * @api stable + * Sets 'display: inline-block' for an element (cross-browser). + * @param {Element} el Element to which the inline-block display style is to be + * applied. + * @see ../demos/inline_block_quirks.html + * @see ../demos/inline_block_standards.html */ -ol.Pixel; - -// FIXME should listen on appropriate pane, once it is defined - -goog.provide('ol.control.MousePosition'); +goog.style.setInlineBlock = function(el) { + var style = el.style; + // Without position:relative, weirdness ensues. Just accept it and move on. + style.position = 'relative'; -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.CoordinateFormatType'); -goog.require('ol.Object'); -goog.require('ol.Pixel'); -goog.require('ol.TransformFunction'); -goog.require('ol.control.Control'); -goog.require('ol.proj'); -goog.require('ol.proj.Projection'); + if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) { + // IE8 supports inline-block so fall through to the else + // Zoom:1 forces hasLayout, display:inline gives inline behavior. + style.zoom = '1'; + style.display = 'inline'; + } else { + // Opera, Webkit, and Safari seem to do OK with the standard inline-block + // style. + style.display = 'inline-block'; + } +}; /** - * @enum {string} + * Returns true if the element is using right to left (rtl) direction. + * @param {Element} el The element to test. + * @return {boolean} True for right to left, false for left to right. */ -ol.control.MousePositionProperty = { - PROJECTION: 'projection', - COORDINATE_FORMAT: 'coordinateFormat' +goog.style.isRightToLeft = function(el) { + return 'rtl' == goog.style.getStyle_(el, 'direction'); }; - /** - * @classdesc - * A control to show the 2D coordinates of the mouse cursor. By default, these - * are in the view projection, but can be in any supported projection. - * By default the control is shown in the top right corner of the map, but this - * can be changed by using the css selector `.ol-mouse-position`. - * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.MousePositionOptions=} opt_options Mouse position - * options. - * @api stable + * The CSS style property corresponding to an element being + * unselectable on the current browser platform (null if none). + * Opera and IE instead use a DOM attribute 'unselectable'. + * @type {?string} + * @private */ -ol.control.MousePosition = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - var className = goog.isDef(options.className) ? - options.className : 'ol-mouse-position'; - - var element = goog.dom.createDom(goog.dom.TagName.DIV, className); - - var render = goog.isDef(options.render) ? - options.render : ol.control.MousePosition.render; - - goog.base(this, { - element: element, - render: render, - target: options.target - }); +goog.style.unselectableStyle_ = + goog.userAgent.GECKO ? 'MozUserSelect' : + goog.userAgent.WEBKIT ? 'WebkitUserSelect' : + null; - goog.events.listen(this, - ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION), - this.handleProjectionChanged_, false, this); - if (goog.isDef(options.coordinateFormat)) { - this.setCoordinateFormat(options.coordinateFormat); - } - if (goog.isDef(options.projection)) { - this.setProjection(ol.proj.get(options.projection)); +/** + * Returns true if the element is set to be unselectable, false otherwise. + * Note that on some platforms (e.g. Mozilla), even if an element isn't set + * to be unselectable, it will behave as such if any of its ancestors is + * unselectable. + * @param {Element} el Element to check. + * @return {boolean} Whether the element is set to be unselectable. + */ +goog.style.isUnselectable = function(el) { + if (goog.style.unselectableStyle_) { + return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none'; + } else if (goog.userAgent.IE || goog.userAgent.OPERA) { + return el.getAttribute('unselectable') == 'on'; } - - /** - * @private - * @type {string} - */ - this.undefinedHTML_ = goog.isDef(options.undefinedHTML) ? - options.undefinedHTML : ''; - - /** - * @private - * @type {string} - */ - this.renderedHTML_ = element.innerHTML; - - /** - * @private - * @type {ol.proj.Projection} - */ - this.mapProjection_ = null; - - /** - * @private - * @type {?ol.TransformFunction} - */ - this.transform_ = null; - - /** - * @private - * @type {ol.Pixel} - */ - this.lastMouseMovePixel_ = null; - + return false; }; -goog.inherits(ol.control.MousePosition, ol.control.Control); /** - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.MousePosition} - * @api + * Makes the element and its descendants selectable or unselectable. Note + * that on some platforms (e.g. Mozilla), even if an element isn't set to + * be unselectable, it will behave as such if any of its ancestors is + * unselectable. + * @param {Element} el The element to alter. + * @param {boolean} unselectable Whether the element and its descendants + * should be made unselectable. + * @param {boolean=} opt_noRecurse Whether to only alter the element's own + * selectable state, and leave its descendants alone; defaults to false. */ -ol.control.MousePosition.render = function(mapEvent) { - var frameState = mapEvent.frameState; - if (goog.isNull(frameState)) { - this.mapProjection_ = null; - } else { - if (this.mapProjection_ != frameState.viewState.projection) { - this.mapProjection_ = frameState.viewState.projection; - this.transform_ = null; +goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) { + // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure? + var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null; + var name = goog.style.unselectableStyle_; + if (name) { + // Add/remove the appropriate CSS style to/from the element and its + // descendants. + var value = unselectable ? 'none' : ''; + el.style[name] = value; + if (descendants) { + for (var i = 0, descendant; descendant = descendants[i]; i++) { + descendant.style[name] = value; + } + } + } else if (goog.userAgent.IE || goog.userAgent.OPERA) { + // Toggle the 'unselectable' attribute on the element and its descendants. + var value = unselectable ? 'on' : ''; + el.setAttribute('unselectable', value); + if (descendants) { + for (var i = 0, descendant; descendant = descendants[i]; i++) { + descendant.setAttribute('unselectable', value); + } } } - this.updateHTML_(this.lastMouseMovePixel_); }; /** - * @private + * Gets the border box size for an element. + * @param {Element} element The element to get the size for. + * @return {!goog.math.Size} The border box size. */ -ol.control.MousePosition.prototype.handleProjectionChanged_ = function() { - this.transform_ = null; +goog.style.getBorderBoxSize = function(element) { + return new goog.math.Size(element.offsetWidth, element.offsetHeight); }; /** - * @return {ol.CoordinateFormatType|undefined} The format to render the current - * position in. - * @observable - * @api stable + * Sets the border box size of an element. This is potentially expensive in IE + * if the document is CSS1Compat mode + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size. */ -ol.control.MousePosition.prototype.getCoordinateFormat = function() { - return /** @type {ol.CoordinateFormatType|undefined} */ ( - this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT)); -}; -goog.exportProperty( - ol.control.MousePosition.prototype, - 'getCoordinateFormat', - ol.control.MousePosition.prototype.getCoordinateFormat); - +goog.style.setBorderBoxSize = function(element, size) { + var doc = goog.dom.getOwnerDocument(element); + var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); -/** - * @return {ol.proj.Projection|undefined} The projection to report mouse - * position in. - * @observable - * @api stable - */ -ol.control.MousePosition.prototype.getProjection = function() { - return /** @type {ol.proj.Projection|undefined} */ ( - this.get(ol.control.MousePositionProperty.PROJECTION)); + if (goog.userAgent.IE && + !goog.userAgent.isVersionOrHigher('10') && + (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { + var style = element.style; + if (isCss1CompatMode) { + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + style.pixelWidth = size.width - borderBox.left - paddingBox.left - + paddingBox.right - borderBox.right; + style.pixelHeight = size.height - borderBox.top - paddingBox.top - + paddingBox.bottom - borderBox.bottom; + } else { + style.pixelWidth = size.width; + style.pixelHeight = size.height; + } + } else { + goog.style.setBoxSizingSize_(element, size, 'border-box'); + } }; -goog.exportProperty( - ol.control.MousePosition.prototype, - 'getProjection', - ol.control.MousePosition.prototype.getProjection); /** - * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @protected + * Gets the content box size for an element. This is potentially expensive in + * all browsers. + * @param {Element} element The element to get the size for. + * @return {!goog.math.Size} The content box size. */ -ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) { - var map = this.getMap(); - this.lastMouseMovePixel_ = map.getEventPixel(browserEvent.getBrowserEvent()); - this.updateHTML_(this.lastMouseMovePixel_); +goog.style.getContentBoxSize = function(element) { + var doc = goog.dom.getOwnerDocument(element); + var ieCurrentStyle = goog.userAgent.IE && element.currentStyle; + if (ieCurrentStyle && + goog.dom.getDomHelper(doc).isCss1CompatMode() && + ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' && + !ieCurrentStyle.boxSizing) { + // If IE in CSS1Compat mode than just use the width and height. + // If we have a boxSizing then fall back on measuring the borders etc. + var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width, + 'width', 'pixelWidth'); + var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height, + 'height', 'pixelHeight'); + return new goog.math.Size(width, height); + } else { + var borderBoxSize = goog.style.getBorderBoxSize(element); + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + return new goog.math.Size(borderBoxSize.width - + borderBox.left - paddingBox.left - + paddingBox.right - borderBox.right, + borderBoxSize.height - + borderBox.top - paddingBox.top - + paddingBox.bottom - borderBox.bottom); + } }; /** - * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @protected + * Sets the content box size of an element. This is potentially expensive in IE + * if the document is BackCompat mode. + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size. */ -ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) { - this.updateHTML_(null); - this.lastMouseMovePixel_ = null; +goog.style.setContentBoxSize = function(element, size) { + var doc = goog.dom.getOwnerDocument(element); + var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); + if (goog.userAgent.IE && + !goog.userAgent.isVersionOrHigher('10') && + (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) { + var style = element.style; + if (isCss1CompatMode) { + style.pixelWidth = size.width; + style.pixelHeight = size.height; + } else { + var paddingBox = goog.style.getPaddingBox(element); + var borderBox = goog.style.getBorderBox(element); + style.pixelWidth = size.width + borderBox.left + paddingBox.left + + paddingBox.right + borderBox.right; + style.pixelHeight = size.height + borderBox.top + paddingBox.top + + paddingBox.bottom + borderBox.bottom; + } + } else { + goog.style.setBoxSizingSize_(element, size, 'content-box'); + } }; /** - * @inheritDoc - * @api stable + * Helper function that sets the box sizing as well as the width and height + * @param {Element} element The element to set the size on. + * @param {goog.math.Size} size The new size to set. + * @param {string} boxSizing The box-sizing value. + * @private */ -ol.control.MousePosition.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); - if (!goog.isNull(map)) { - var viewport = map.getViewport(); - this.listenerKeys.push( - goog.events.listen(viewport, goog.events.EventType.MOUSEMOVE, - this.handleMouseMove, false, this), - goog.events.listen(viewport, goog.events.EventType.MOUSEOUT, - this.handleMouseOut, false, this) - ); +goog.style.setBoxSizingSize_ = function(element, size, boxSizing) { + var style = element.style; + if (goog.userAgent.GECKO) { + style.MozBoxSizing = boxSizing; + } else if (goog.userAgent.WEBKIT) { + style.WebkitBoxSizing = boxSizing; + } else { + // Includes IE8 and Opera 9.50+ + style.boxSizing = boxSizing; } -}; - -/** - * @param {ol.CoordinateFormatType} format The format to render the current - * position in. - * @observable - * @api stable - */ -ol.control.MousePosition.prototype.setCoordinateFormat = function(format) { - this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format); + // Setting this to a negative value will throw an exception on IE + // (and doesn't do anything different than setting it to 0). + style.width = Math.max(size.width, 0) + 'px'; + style.height = Math.max(size.height, 0) + 'px'; }; -goog.exportProperty( - ol.control.MousePosition.prototype, - 'setCoordinateFormat', - ol.control.MousePosition.prototype.setCoordinateFormat); /** - * @param {ol.proj.Projection} projection The projection to report mouse - * position in. - * @observable - * @api stable + * IE specific function that converts a non pixel unit to pixels. + * @param {Element} element The element to convert the value for. + * @param {string} value The current value as a string. The value must not be + * ''. + * @param {string} name The CSS property name to use for the converstion. This + * should be 'left', 'top', 'width' or 'height'. + * @param {string} pixelName The CSS pixel property name to use to get the + * value in pixels. + * @return {number} The value in pixels. + * @private */ -ol.control.MousePosition.prototype.setProjection = function(projection) { - this.set(ol.control.MousePositionProperty.PROJECTION, projection); +goog.style.getIePixelValue_ = function(element, value, name, pixelName) { + // Try if we already have a pixel value. IE does not do half pixels so we + // only check if it matches a number followed by 'px'. + if (/^\d+px?$/.test(value)) { + return parseInt(value, 10); + } else { + var oldStyleValue = element.style[name]; + var oldRuntimeValue = element.runtimeStyle[name]; + // set runtime style to prevent changes + element.runtimeStyle[name] = element.currentStyle[name]; + element.style[name] = value; + var pixelValue = element.style[pixelName]; + // restore + element.style[name] = oldStyleValue; + element.runtimeStyle[name] = oldRuntimeValue; + return pixelValue; + } }; -goog.exportProperty( - ol.control.MousePosition.prototype, - 'setProjection', - ol.control.MousePosition.prototype.setProjection); /** - * @param {?ol.Pixel} pixel Pixel. + * Helper function for getting the pixel padding or margin for IE. + * @param {Element} element The element to get the padding for. + * @param {string} propName The property name. + * @return {number} The pixel padding. * @private */ -ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { - var html = this.undefinedHTML_; - if (!goog.isNull(pixel) && !goog.isNull(this.mapProjection_)) { - if (goog.isNull(this.transform_)) { - var projection = this.getProjection(); - if (goog.isDef(projection)) { - this.transform_ = ol.proj.getTransformFromProjections( - this.mapProjection_, projection); - } else { - this.transform_ = ol.proj.identityTransform; - } - } - var map = this.getMap(); - var coordinate = map.getCoordinateFromPixel(pixel); - if (!goog.isNull(coordinate)) { - this.transform_(coordinate, coordinate); - var coordinateFormat = this.getCoordinateFormat(); - if (goog.isDef(coordinateFormat)) { - html = coordinateFormat(coordinate); - } else { - html = coordinate.toString(); - } - } - } - if (!goog.isDef(this.renderedHTML_) || html != this.renderedHTML_) { - this.element.innerHTML = html; - this.renderedHTML_ = html; - } +goog.style.getIePixelDistance_ = function(element, propName) { + var value = goog.style.getCascadedStyle(element, propName); + return value ? + goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0; }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Generics method for collection-like classes and objects. - * - * @author arv@google.com (Erik Arvidsson) - * - * This file contains functions to work with collections. It supports using - * Map, Set, Array and Object and other classes that implement collection-like - * methods. + * Gets the computed paddings or margins (on all sides) in pixels. + * @param {Element} element The element to get the padding for. + * @param {string} stylePrefix Pass 'padding' to retrieve the padding box, + * or 'margin' to retrieve the margin box. + * @return {!goog.math.Box} The computed paddings or margins. + * @private */ +goog.style.getBox_ = function(element, stylePrefix) { + if (goog.userAgent.IE) { + var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left'); + var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right'); + var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top'); + var bottom = goog.style.getIePixelDistance_( + element, stylePrefix + 'Bottom'); + return new goog.math.Box(top, right, bottom, left); + } else { + // On non-IE browsers, getComputedStyle is always non-null. + var left = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Left')); + var right = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Right')); + var top = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Top')); + var bottom = /** @type {string} */ ( + goog.style.getComputedStyle(element, stylePrefix + 'Bottom')); - -goog.provide('goog.structs'); - -goog.require('goog.array'); -goog.require('goog.object'); + // NOTE(arv): Gecko can return floating point numbers for the computed + // style values. + return new goog.math.Box(parseFloat(top), + parseFloat(right), + parseFloat(bottom), + parseFloat(left)); + } +}; -// We treat an object as a dictionary if it has getKeys or it is an object that -// isn't arrayLike. +/** + * Gets the computed paddings (on all sides) in pixels. + * @param {Element} element The element to get the padding for. + * @return {!goog.math.Box} The computed paddings. + */ +goog.style.getPaddingBox = function(element) { + return goog.style.getBox_(element, 'padding'); +}; /** - * Returns the number of values in the collection-like object. - * @param {Object} col The collection-like object. - * @return {number} The number of values in the collection-like object. + * Gets the computed margins (on all sides) in pixels. + * @param {Element} element The element to get the margins for. + * @return {!goog.math.Box} The computed margins. */ -goog.structs.getCount = function(col) { - if (typeof col.getCount == 'function') { - return col.getCount(); - } - if (goog.isArrayLike(col) || goog.isString(col)) { - return col.length; - } - return goog.object.getCount(col); +goog.style.getMarginBox = function(element) { + return goog.style.getBox_(element, 'margin'); }; /** - * Returns the values of the collection-like object. - * @param {Object} col The collection-like object. - * @return {!Array<?>} The values in the collection-like object. + * A map used to map the border width keywords to a pixel width. + * @type {Object} + * @private */ -goog.structs.getValues = function(col) { - if (typeof col.getValues == 'function') { - return col.getValues(); - } - if (goog.isString(col)) { - return col.split(''); - } - if (goog.isArrayLike(col)) { - var rv = []; - var l = col.length; - for (var i = 0; i < l; i++) { - rv.push(col[i]); - } - return rv; - } - return goog.object.getValues(col); +goog.style.ieBorderWidthKeywords_ = { + 'thin': 2, + 'medium': 4, + 'thick': 6 }; /** - * Returns the keys of the collection. Some collections have no notion of - * keys/indexes and this function will return undefined in those cases. - * @param {Object} col The collection-like object. - * @return {!Array|undefined} The keys in the collection. + * Helper function for IE to get the pixel border. + * @param {Element} element The element to get the pixel border for. + * @param {string} prop The part of the property name. + * @return {number} The value in pixels. + * @private */ -goog.structs.getKeys = function(col) { - if (typeof col.getKeys == 'function') { - return col.getKeys(); - } - // if we have getValues but no getKeys we know this is a key-less collection - if (typeof col.getValues == 'function') { - return undefined; +goog.style.getIePixelBorder_ = function(element, prop) { + if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') { + return 0; } - if (goog.isArrayLike(col) || goog.isString(col)) { - var rv = []; - var l = col.length; - for (var i = 0; i < l; i++) { - rv.push(i); - } - return rv; + var width = goog.style.getCascadedStyle(element, prop + 'Width'); + if (width in goog.style.ieBorderWidthKeywords_) { + return goog.style.ieBorderWidthKeywords_[width]; } - - return goog.object.getKeys(col); + return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft'); }; /** - * Whether the collection contains the given value. This is O(n) and uses - * equals (==) to test the existence. - * @param {Object} col The collection-like object. - * @param {*} val The value to check for. - * @return {boolean} True if the map contains the value. + * Gets the computed border widths (on all sides) in pixels + * @param {Element} element The element to get the border widths for. + * @return {!goog.math.Box} The computed border widths. */ -goog.structs.contains = function(col, val) { - if (typeof col.contains == 'function') { - return col.contains(val); - } - if (typeof col.containsValue == 'function') { - return col.containsValue(val); - } - if (goog.isArrayLike(col) || goog.isString(col)) { - return goog.array.contains(/** @type {!Array<?>} */ (col), val); +goog.style.getBorderBox = function(element) { + if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) { + var left = goog.style.getIePixelBorder_(element, 'borderLeft'); + var right = goog.style.getIePixelBorder_(element, 'borderRight'); + var top = goog.style.getIePixelBorder_(element, 'borderTop'); + var bottom = goog.style.getIePixelBorder_(element, 'borderBottom'); + return new goog.math.Box(top, right, bottom, left); + } else { + // On non-IE browsers, getComputedStyle is always non-null. + var left = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderLeftWidth')); + var right = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderRightWidth')); + var top = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderTopWidth')); + var bottom = /** @type {string} */ ( + goog.style.getComputedStyle(element, 'borderBottomWidth')); + + return new goog.math.Box(parseFloat(top), + parseFloat(right), + parseFloat(bottom), + parseFloat(left)); } - return goog.object.containsValue(col, val); }; /** - * Whether the collection is empty. - * @param {Object} col The collection-like object. - * @return {boolean} True if empty. + * Returns the font face applied to a given node. Opera and IE should return + * the font actually displayed. Firefox returns the author's most-preferred + * font (whether the browser is capable of displaying it or not.) + * @param {Element} el The element whose font family is returned. + * @return {string} The font family applied to el. */ -goog.structs.isEmpty = function(col) { - if (typeof col.isEmpty == 'function') { - return col.isEmpty(); +goog.style.getFontFamily = function(el) { + var doc = goog.dom.getOwnerDocument(el); + var font = ''; + // The moveToElementText method from the TextRange only works if the element + // is attached to the owner document. + if (doc.body.createTextRange && goog.dom.contains(doc, el)) { + var range = doc.body.createTextRange(); + range.moveToElementText(el); + /** @preserveTry */ + try { + font = range.queryCommandValue('FontName'); + } catch (e) { + // This is a workaround for a awkward exception. + // On some IE, there is an exception coming from it. + // The error description from this exception is: + // This window has already been registered as a drop target + // This is bogus description, likely due to a bug in ie. + font = ''; + } + } + if (!font) { + // Note if for some reason IE can't derive FontName with a TextRange, we + // fallback to using currentStyle + font = goog.style.getStyle_(el, 'fontFamily'); } - // We do not use goog.string.isEmpty because here we treat the string as - // collection and as such even whitespace matters + // Firefox returns the applied font-family string (author's list of + // preferred fonts.) We want to return the most-preferred font, in lieu of + // the *actually* applied font. + var fontsArray = font.split(','); + if (fontsArray.length > 1) font = fontsArray[0]; - if (goog.isArrayLike(col) || goog.isString(col)) { - return goog.array.isEmpty(/** @type {!Array<?>} */ (col)); - } - return goog.object.isEmpty(col); + // Sanitize for x-browser consistency: + // Strip quotes because browsers aren't consistent with how they're + // applied; Opera always encloses, Firefox sometimes, and IE never. + return goog.string.stripQuotes(font, '"\''); }; /** - * Removes all the elements from the collection. - * @param {Object} col The collection-like object. + * Regular expression used for getLengthUnits. + * @type {RegExp} + * @private */ -goog.structs.clear = function(col) { - // NOTE(arv): This should not contain strings because strings are immutable - if (typeof col.clear == 'function') { - col.clear(); - } else if (goog.isArrayLike(col)) { - goog.array.clear(/** @type {goog.array.ArrayLike} */ (col)); - } else { - goog.object.clear(col); - } -}; +goog.style.lengthUnitRegex_ = /[^\d]+$/; /** - * Calls a function for each value in a collection. The function takes - * three arguments; the value, the key and the collection. - * - * NOTE: This will be deprecated soon! Please use a more specific method if - * possible, e.g. goog.array.forEach, goog.object.forEach, etc. - * - * @param {S} col The collection-like object. - * @param {function(this:T,?,?,S):?} f The function to call for every value. - * This function takes - * 3 arguments (the value, the key or undefined if the collection has no - * notion of keys, and the collection) and the return value is irrelevant. - * @param {T=} opt_obj The object to be used as the value of 'this' - * within {@code f}. - * @template T,S + * Returns the units used for a CSS length measurement. + * @param {string} value A CSS length quantity. + * @return {?string} The units of measurement. */ -goog.structs.forEach = function(col, f, opt_obj) { - if (typeof col.forEach == 'function') { - col.forEach(f, opt_obj); - } else if (goog.isArrayLike(col) || goog.isString(col)) { - goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj); - } else { - var keys = goog.structs.getKeys(col); - var values = goog.structs.getValues(col); - var l = values.length; - for (var i = 0; i < l; i++) { - f.call(opt_obj, values[i], keys && keys[i], col); - } - } +goog.style.getLengthUnits = function(value) { + var units = value.match(goog.style.lengthUnitRegex_); + return units && units[0] || null; }; /** - * Calls a function for every value in the collection. When a call returns true, - * adds the value to a new collection (Array is returned by default). - * - * @param {S} col The collection-like object. - * @param {function(this:T,?,?,S):boolean} f The function to call for every - * value. This function takes - * 3 arguments (the value, the key or undefined if the collection has no - * notion of keys, and the collection) and should return a Boolean. If the - * return value is true the value is added to the result collection. If it - * is false the value is not included. - * @param {T=} opt_obj The object to be used as the value of 'this' - * within {@code f}. - * @return {!Object|!Array<?>} A new collection where the passed values are - * present. If col is a key-less collection an array is returned. If col - * has keys and values a plain old JS object is returned. - * @template T,S + * Map of absolute CSS length units + * @type {Object} + * @private */ -goog.structs.filter = function(col, f, opt_obj) { - if (typeof col.filter == 'function') { - return col.filter(f, opt_obj); - } - if (goog.isArrayLike(col) || goog.isString(col)) { - return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj); - } +goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = { + 'cm' : 1, + 'in' : 1, + 'mm' : 1, + 'pc' : 1, + 'pt' : 1 +}; - var rv; - var keys = goog.structs.getKeys(col); - var values = goog.structs.getValues(col); - var l = values.length; - if (keys) { - rv = {}; - for (var i = 0; i < l; i++) { - if (f.call(opt_obj, values[i], keys[i], col)) { - rv[keys[i]] = values[i]; - } - } - } else { - // We should not use goog.array.filter here since we want to make sure that - // the index is undefined as well as make sure that col is passed to the - // function. - rv = []; - for (var i = 0; i < l; i++) { - if (f.call(opt_obj, values[i], undefined, col)) { - rv.push(values[i]); - } - } - } - return rv; + +/** + * Map of relative CSS length units that can be accurately converted to px + * font-size values using getIePixelValue_. Only units that are defined in + * relation to a font size are convertible (%, small, etc. are not). + * @type {Object} + * @private + */ +goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = { + 'em' : 1, + 'ex' : 1 }; /** - * Calls a function for every value in the collection and adds the result into a - * new collection (defaults to creating a new Array). - * - * @param {S} col The collection-like object. - * @param {function(this:T,?,?,S):V} f The function to call for every value. - * This function takes 3 arguments (the value, the key or undefined if the - * collection has no notion of keys, and the collection) and should return - * something. The result will be used as the value in the new collection. - * @param {T=} opt_obj The object to be used as the value of 'this' - * within {@code f}. - * @return {!Object<V>|!Array<V>} A new collection with the new values. If - * col is a key-less collection an array is returned. If col has keys and - * values a plain old JS object is returned. - * @template T,S,V + * Returns the font size, in pixels, of text in an element. + * @param {Element} el The element whose font size is returned. + * @return {number} The font size (in pixels). */ -goog.structs.map = function(col, f, opt_obj) { - if (typeof col.map == 'function') { - return col.map(f, opt_obj); - } - if (goog.isArrayLike(col) || goog.isString(col)) { - return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj); +goog.style.getFontSize = function(el) { + var fontSize = goog.style.getStyle_(el, 'fontSize'); + var sizeUnits = goog.style.getLengthUnits(fontSize); + if (fontSize && 'px' == sizeUnits) { + // NOTE(user): This could be parseFloat instead, but IE doesn't return + // decimal fractions in getStyle_ and Firefox reports the fractions, but + // ignores them when rendering. Interestingly enough, when we force the + // issue and size something to e.g., 50% of 25px, the browsers round in + // opposite directions with Firefox reporting 12px and IE 13px. I punt. + return parseInt(fontSize, 10); } - var rv; - var keys = goog.structs.getKeys(col); - var values = goog.structs.getValues(col); - var l = values.length; - if (keys) { - rv = {}; - for (var i = 0; i < l; i++) { - rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col); - } - } else { - // We should not use goog.array.map here since we want to make sure that - // the index is undefined as well as make sure that col is passed to the - // function. - rv = []; - for (var i = 0; i < l; i++) { - rv[i] = f.call(opt_obj, values[i], undefined, col); + // In IE, we can convert absolute length units to a px value using + // goog.style.getIePixelValue_. Units defined in relation to a font size + // (em, ex) are applied relative to the element's parentNode and can also + // be converted. + if (goog.userAgent.IE) { + if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) { + return goog.style.getIePixelValue_(el, + fontSize, + 'left', + 'pixelLeft'); + } else if (el.parentNode && + el.parentNode.nodeType == goog.dom.NodeType.ELEMENT && + sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) { + // Check the parent size - if it is the same it means the relative size + // value is inherited and we therefore don't want to count it twice. If + // it is different, this element either has explicit style or has a CSS + // rule applying to it. + var parentElement = /** @type {!Element} */ (el.parentNode); + var parentSize = goog.style.getStyle_(parentElement, 'fontSize'); + return goog.style.getIePixelValue_(parentElement, + fontSize == parentSize ? + '1em' : fontSize, + 'left', + 'pixelLeft'); } } - return rv; + + // Sometimes we can't cleanly find the font size (some units relative to a + // node's parent's font size are difficult: %, smaller et al), so we create + // an invisible, absolutely-positioned span sized to be the height of an 'M' + // rendered in its parent's (i.e., our target element's) font size. This is + // the definition of CSS's font size attribute. + var sizeElement = goog.dom.createDom( + 'span', + {'style': 'visibility:hidden;position:absolute;' + + 'line-height:0;padding:0;margin:0;border:0;height:1em;'}); + goog.dom.appendChild(el, sizeElement); + fontSize = sizeElement.offsetHeight; + goog.dom.removeNode(sizeElement); + + return fontSize; }; /** - * Calls f for each value in a collection. If any call returns true this returns - * true (without checking the rest). If all returns false this returns false. - * - * @param {S} col The collection-like object. - * @param {function(this:T,?,?,S):boolean} f The function to call for every - * value. This function takes 3 arguments (the value, the key or undefined - * if the collection has no notion of keys, and the collection) and should - * return a boolean. - * @param {T=} opt_obj The object to be used as the value of 'this' - * within {@code f}. - * @return {boolean} True if any value passes the test. - * @template T,S + * Parses a style attribute value. Converts CSS property names to camel case. + * @param {string} value The style attribute value. + * @return {!Object} Map of CSS properties to string values. */ -goog.structs.some = function(col, f, opt_obj) { - if (typeof col.some == 'function') { - return col.some(f, opt_obj); - } - if (goog.isArrayLike(col) || goog.isString(col)) { - return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj); - } - var keys = goog.structs.getKeys(col); - var values = goog.structs.getValues(col); - var l = values.length; - for (var i = 0; i < l; i++) { - if (f.call(opt_obj, values[i], keys && keys[i], col)) { - return true; +goog.style.parseStyleAttribute = function(value) { + var result = {}; + goog.array.forEach(value.split(/\s*;\s*/), function(pair) { + var keyValue = pair.split(/\s*:\s*/); + if (keyValue.length == 2) { + result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1]; } - } - return false; + }); + return result; }; /** - * Calls f for each value in a collection. If all calls return true this return - * true this returns true. If any returns false this returns false at this point - * and does not continue to check the remaining values. - * - * @param {S} col The collection-like object. - * @param {function(this:T,?,?,S):boolean} f The function to call for every - * value. This function takes 3 arguments (the value, the key or - * undefined if the collection has no notion of keys, and the collection) - * and should return a boolean. - * @param {T=} opt_obj The object to be used as the value of 'this' - * within {@code f}. - * @return {boolean} True if all key-value pairs pass the test. - * @template T,S + * Reverse of parseStyleAttribute; that is, takes a style object and returns the + * corresponding attribute value. Converts camel case property names to proper + * CSS selector names. + * @param {Object} obj Map of CSS properties to values. + * @return {string} The style attribute value. */ -goog.structs.every = function(col, f, opt_obj) { - if (typeof col.every == 'function') { - return col.every(f, opt_obj); - } - if (goog.isArrayLike(col) || goog.isString(col)) { - return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj); - } - var keys = goog.structs.getKeys(col); - var values = goog.structs.getValues(col); - var l = values.length; - for (var i = 0; i < l; i++) { - if (!f.call(opt_obj, values[i], keys && keys[i], col)) { - return false; - } - } - return true; +goog.style.toStyleAttribute = function(obj) { + var buffer = []; + goog.object.forEach(obj, function(value, key) { + buffer.push(goog.string.toSelectorCase(key), ':', value, ';'); + }); + return buffer.join(''); }; -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Python style iteration utilities. - * @author arv@google.com (Erik Arvidsson) + * Sets CSS float property on an element. + * @param {Element} el The element to set float property on. + * @param {string} value The value of float CSS property to set on this element. */ - - -goog.provide('goog.iter'); -goog.provide('goog.iter.Iterable'); -goog.provide('goog.iter.Iterator'); -goog.provide('goog.iter.StopIteration'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.functions'); -goog.require('goog.math'); +goog.style.setFloat = function(el, value) { + el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value; +}; /** - * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}} + * Gets value of explicitly-set float CSS property on an element. + * @param {Element} el The element to get float property of. + * @return {string} The value of explicitly-set float CSS property on this + * element. */ -goog.iter.Iterable; +goog.style.getFloat = function(el) { + return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || ''; +}; /** - * Singleton Error object that is used to terminate iterations. - * @const {!Error} + * Returns the scroll bar width (represents the width of both horizontal + * and vertical scroll). + * + * @param {string=} opt_className An optional class name (or names) to apply + * to the invisible div created to measure the scrollbar. This is necessary + * if some scrollbars are styled differently than others. + * @return {number} The scroll bar width in px. */ -goog.iter.StopIteration = ('StopIteration' in goog.global) ? - // For script engines that support legacy iterators. - goog.global['StopIteration'] : - Error('StopIteration'); - +goog.style.getScrollbarWidth = function(opt_className) { + // Add two hidden divs. The child div is larger than the parent and + // forces scrollbars to appear on it. + // Using overflow:scroll does not work consistently with scrollbars that + // are styled with ::-webkit-scrollbar. + var outerDiv = goog.dom.createElement('div'); + if (opt_className) { + outerDiv.className = opt_className; + } + outerDiv.style.cssText = 'overflow:auto;' + + 'position:absolute;top:0;width:100px;height:100px'; + var innerDiv = goog.dom.createElement('div'); + goog.style.setSize(innerDiv, '200px', '200px'); + outerDiv.appendChild(innerDiv); + goog.dom.appendChild(goog.dom.getDocument().body, outerDiv); + var width = outerDiv.offsetWidth - outerDiv.clientWidth; + goog.dom.removeNode(outerDiv); + return width; +}; /** - * Class/interface for iterators. An iterator needs to implement a {@code next} - * method and it needs to throw a {@code goog.iter.StopIteration} when the - * iteration passes beyond the end. Iterators have no {@code hasNext} method. - * It is recommended to always use the helper functions to iterate over the - * iterator or in case you are only targeting JavaScript 1.7 for in loops. - * @constructor - * @template VALUE + * Regular expression to extract x and y translation components from a CSS + * transform Matrix representation. + * + * @type {!RegExp} + * @const + * @private */ -goog.iter.Iterator = function() {}; +goog.style.MATRIX_TRANSLATION_REGEX_ = + new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' + + '[0-9\\.\\-]+, [0-9\\.\\-]+, ' + + '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)'); /** - * Returns the next value of the iteration. This will throw the object - * {@see goog.iter#StopIteration} when the iteration passes the end. - * @return {VALUE} Any object or value. + * Returns the x,y translation component of any CSS transforms applied to the + * element, in pixels. + * + * @param {!Element} element The element to get the translation of. + * @return {!goog.math.Coordinate} The CSS translation of the element in px. */ -goog.iter.Iterator.prototype.next = function() { - throw goog.iter.StopIteration; +goog.style.getCssTranslation = function(element) { + var transform = goog.style.getComputedTransform(element); + if (!transform) { + return new goog.math.Coordinate(0, 0); + } + var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_); + if (!matches) { + return new goog.math.Coordinate(0, 0); + } + return new goog.math.Coordinate(parseFloat(matches[1]), + parseFloat(matches[2])); }; +goog.provide('ol.MapEvent'); +goog.provide('ol.MapEventType'); + +goog.require('goog.events.Event'); + /** - * Returns the {@code Iterator} object itself. This is used to implement - * the iterator protocol in JavaScript 1.7 - * @param {boolean=} opt_keys Whether to return the keys or values. Default is - * to only return the values. This is being used by the for-in loop (true) - * and the for-each-in loop (false). Even though the param gives a hint - * about what the iterator will return there is no guarantee that it will - * return the keys when true is passed. - * @return {!goog.iter.Iterator<VALUE>} The object itself. + * @enum {string} */ -goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) { - return this; +ol.MapEventType = { + + /** + * Triggered after a map frame is rendered. + * @event ol.MapEvent#postrender + * @api + */ + POSTRENDER: 'postrender', + + /** + * Triggered after the map is moved. + * @event ol.MapEvent#moveend + * @api stable + */ + MOVEEND: 'moveend' + }; + /** - * Returns an iterator that knows how to iterate over the values in the object. - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable If the - * object is an iterator it will be returned as is. If the object has an - * {@code __iterator__} method that will be called to get the value - * iterator. If the object is an array-like object we create an iterator - * for that. - * @return {!goog.iter.Iterator<VALUE>} An iterator that knows how to iterate - * over the values in {@code iterable}. - * @template VALUE + * @classdesc + * Events emitted as map events are instances of this type. + * See {@link ol.Map} for which events trigger a map event. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.MapEvent} + * @param {string} type Event type. + * @param {ol.Map} map Map. + * @param {?olx.FrameState=} opt_frameState Frame state. */ -goog.iter.toIterator = function(iterable) { - if (iterable instanceof goog.iter.Iterator) { - return iterable; - } - if (typeof iterable.__iterator__ == 'function') { - return iterable.__iterator__(false); - } - if (goog.isArrayLike(iterable)) { - var i = 0; - var newIter = new goog.iter.Iterator; - newIter.next = function() { - while (true) { - if (i >= iterable.length) { - throw goog.iter.StopIteration; - } - // Don't include deleted elements. - if (!(i in iterable)) { - i++; - continue; - } - return iterable[i++]; - } - }; - return newIter; - } +ol.MapEvent = function(type, map, opt_frameState) { + goog.base(this, type); + + /** + * The map where the event occurred. + * @type {ol.Map} + * @api stable + */ + this.map = map; + + /** + * The frame state at the time of the event. + * @type {?olx.FrameState} + * @api + */ + this.frameState = goog.isDef(opt_frameState) ? opt_frameState : null; - // TODO(arv): Should we fall back on goog.structs.getValues()? - throw Error('Not implemented'); }; +goog.inherits(ol.MapEvent, goog.events.Event); + +goog.provide('ol.control.Control'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('ol.MapEventType'); +goog.require('ol.Object'); + /** - * Calls a function for each element in the iterator with the element of the - * iterator passed as argument. + * @classdesc + * A control is a visible widget with a DOM element in a fixed position on the + * screen. They can involve user input (buttons), or be informational only; + * the position is determined using CSS. By default these are placed in the + * container with CSS class name `ol-overlaycontainer-stopevent`, but can use + * any outside DOM element. * - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * to iterate over. If the iterable is an object {@code toIterator} will be - * called on it. - * @param {function(this:THIS,VALUE,?,!goog.iter.Iterator<VALUE>)} f - * The function to call for every element. This function takes 3 arguments - * (the element, undefined, and the iterator) and the return value is - * irrelevant. The reason for passing undefined as the second argument is - * so that the same function can be used in {@see goog.array#forEach} as - * well as others. The third parameter is of type "number" for - * arraylike objects, undefined, otherwise. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @template THIS, VALUE + * This is the base class for controls. You can use it for simple custom + * controls by creating the element with listeners, creating an instance: + * ```js + * var myControl = new ol.control.Control({element: myElement}); + * ``` + * and then adding this to the map. + * + * The main advantage of having this as a control rather than a simple separate + * DOM element is that preventing propagation is handled for you. Controls + * will also be `ol.Object`s in a `ol.Collection`, so you can use their + * methods. + * + * You can also extend this base for your own control class. See + * examples/custom-controls for an example of how to do this. + * + * @constructor + * @extends {ol.Object} + * @implements {oli.control.Control} + * @param {olx.control.ControlOptions} options Control options. + * @api stable */ -goog.iter.forEach = function(iterable, f, opt_obj) { - if (goog.isArrayLike(iterable)) { - /** @preserveTry */ - try { - // NOTES: this passes the index number to the second parameter - // of the callback contrary to the documentation above. - goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f, - opt_obj); - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - } - } else { - iterable = goog.iter.toIterator(iterable); - /** @preserveTry */ - try { - while (true) { - f.call(opt_obj, iterable.next(), undefined, iterable); - } - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - } +ol.control.Control = function(options) { + + goog.base(this); + + /** + * @protected + * @type {Element} + */ + this.element = goog.isDef(options.element) ? options.element : null; + + /** + * @private + * @type {Element} + */ + this.target_ = null; + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @protected + * @type {!Array.<?number>} + */ + this.listenerKeys = []; + + /** + * @type {function(ol.MapEvent)} + */ + this.render = goog.isDef(options.render) ? options.render : goog.nullFunction; + + if (goog.isDef(options.target)) { + this.setTarget(options.target); } + }; +goog.inherits(ol.control.Control, ol.Object); /** - * Calls a function for every element in the iterator, and if the function - * returns true adds the element to a new iterator. - * - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * to iterate over. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f - * The function to call for every element. This function takes 3 arguments - * (the element, undefined, and the iterator) and should return a boolean. - * If the return value is true the element will be included in the returned - * iterator. If it is false the element is not included. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements - * that passed the test are present. - * @template THIS, VALUE + * @inheritDoc */ -goog.iter.filter = function(iterable, f, opt_obj) { - var iterator = goog.iter.toIterator(iterable); - var newIter = new goog.iter.Iterator; - newIter.next = function() { - while (true) { - var val = iterator.next(); - if (f.call(opt_obj, val, undefined, iterator)) { - return val; - } - } - }; - return newIter; +ol.control.Control.prototype.disposeInternal = function() { + goog.dom.removeNode(this.element); + goog.base(this, 'disposeInternal'); }; /** - * Calls a function for every element in the iterator, and if the function - * returns false adds the element to a new iterator. - * - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * to iterate over. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f - * The function to call for every element. This function takes 3 arguments - * (the element, undefined, and the iterator) and should return a boolean. - * If the return value is false the element will be included in the returned - * iterator. If it is true the element is not included. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements - * that did not pass the test are present. - * @template THIS, VALUE + * Get the map associated with this control. + * @return {ol.Map} Map. + * @api stable */ -goog.iter.filterFalse = function(iterable, f, opt_obj) { - return goog.iter.filter(iterable, goog.functions.not(f), opt_obj); +ol.control.Control.prototype.getMap = function() { + return this.map_; }; /** - * Creates a new iterator that returns the values in a range. This function - * can take 1, 2 or 3 arguments: - * <pre> - * range(5) same as range(0, 5, 1) - * range(2, 5) same as range(2, 5, 1) - * </pre> - * - * @param {number} startOrStop The stop value if only one argument is provided. - * The start value if 2 or more arguments are provided. If only one - * argument is used the start value is 0. - * @param {number=} opt_stop The stop value. If left out then the first - * argument is used as the stop value. - * @param {number=} opt_step The number to increment with between each call to - * next. This can be negative. - * @return {!goog.iter.Iterator<number>} A new iterator that returns the values - * in the range. + * Remove the control from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. + * @api stable */ -goog.iter.range = function(startOrStop, opt_stop, opt_step) { - var start = 0; - var stop = startOrStop; - var step = opt_step || 1; - if (arguments.length > 1) { - start = startOrStop; - stop = opt_stop; +ol.control.Control.prototype.setMap = function(map) { + if (!goog.isNull(this.map_)) { + goog.dom.removeNode(this.element); } - if (step == 0) { - throw Error('Range step argument must not be zero'); + if (!goog.array.isEmpty(this.listenerKeys)) { + goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); + this.listenerKeys.length = 0; } - - var newIter = new goog.iter.Iterator; - newIter.next = function() { - if (step > 0 && start >= stop || step < 0 && start <= stop) { - throw goog.iter.StopIteration; + this.map_ = map; + if (!goog.isNull(this.map_)) { + var target = !goog.isNull(this.target_) ? + this.target_ : map.getOverlayContainerStopEvent(); + goog.dom.appendChild(target, this.element); + if (this.render !== goog.nullFunction) { + this.listenerKeys.push(goog.events.listen(map, + ol.MapEventType.POSTRENDER, this.render, false, this)); } - var rv = start; - start += step; - return rv; - }; - return newIter; + map.render(); + } }; /** - * Joins the values in a iterator with a delimiter. - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * to get the values from. - * @param {string} deliminator The text to put between the values. - * @return {string} The joined value string. - * @template VALUE + * This function is used to set a target element for the control. It has no + * effect if it is called after the control has been added to the map (i.e. + * after `setMap` is called on the control). If no `target` is set in the + * options passed to the control constructor and if `setTarget` is not called + * then the control is added to the map's overlay container. + * @param {Element|string} target Target. + * @api */ -goog.iter.join = function(iterable, deliminator) { - return goog.iter.toArray(iterable).join(deliminator); +ol.control.Control.prototype.setTarget = function(target) { + this.target_ = goog.dom.getElement(target); }; +goog.provide('ol.css'); + /** - * For every element in the iterator call a function and return a new iterator - * with that value. + * The CSS class for hidden feature. * - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterator to iterate over. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):RESULT} f - * The function to call for every element. This function takes 3 arguments - * (the element, undefined, and the iterator) and should return a new value. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the - * results of applying the function to each element in the original - * iterator. - * @template THIS, VALUE, RESULT + * @const + * @type {string} */ -goog.iter.map = function(iterable, f, opt_obj) { - var iterator = goog.iter.toIterator(iterable); - var newIter = new goog.iter.Iterator; - newIter.next = function() { - var val = iterator.next(); - return f.call(opt_obj, val, undefined, iterator); - }; - return newIter; -}; +ol.css.CLASS_HIDDEN = 'ol-hidden'; /** - * Passes every element of an iterator into a function and accumulates the - * result. + * The CSS class that we'll give the DOM elements to have them unselectable. * - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * to iterate over. - * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for - * every element. This function takes 2 arguments (the function's previous - * result or the initial value, and the value of the current element). - * function(previousValue, currentElement) : newValue. - * @param {VALUE} val The initial value to pass into the function on the first - * call. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * f. - * @return {VALUE} Result of evaluating f repeatedly across the values of - * the iterator. - * @template THIS, VALUE + * @const + * @type {string} */ -goog.iter.reduce = function(iterable, f, val, opt_obj) { - var rval = val; - goog.iter.forEach(iterable, function(val) { - rval = f.call(opt_obj, rval, val); - }); - return rval; -}; +ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; /** - * Goes through the values in the iterator. Calls f for each of these, and if - * any of them returns true, this returns true (without checking the rest). If - * all return false this will return false. + * The CSS class for unsupported feature. * - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * object. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f - * The function to call for every value. This function takes 3 arguments - * (the value, undefined, and the iterator) and should return a boolean. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {boolean} true if any value passes the test. - * @template THIS, VALUE + * @const + * @type {string} */ -goog.iter.some = function(iterable, f, opt_obj) { - iterable = goog.iter.toIterator(iterable); - /** @preserveTry */ - try { - while (true) { - if (f.call(opt_obj, iterable.next(), undefined, iterable)) { - return true; - } - } - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - } - return false; -}; +ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; /** - * Goes through the values in the iterator. Calls f for each of these and if any - * of them returns false this returns false (without checking the rest). If all - * return true this will return true. + * The CSS class for controls. * - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * object. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f - * The function to call for every value. This function takes 3 arguments - * (the value, undefined, and the iterator) and should return a boolean. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {boolean} true if every value passes the test. - * @template THIS, VALUE + * @const + * @type {string} */ -goog.iter.every = function(iterable, f, opt_obj) { - iterable = goog.iter.toIterator(iterable); - /** @preserveTry */ - try { - while (true) { - if (!f.call(opt_obj, iterable.next(), undefined, iterable)) { - return false; - } - } - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - } - return true; -}; +ol.css.CLASS_CONTROL = 'ol-control'; + +goog.provide('ol.structs.LRUCache'); + +goog.require('goog.asserts'); +goog.require('goog.object'); + /** - * Takes zero or more iterables and returns one iterator that will iterate over - * them in the order chained. - * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any - * number of iterable objects. - * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will - * iterate over all the given iterables' contents. - * @template VALUE + * Implements a Least-Recently-Used cache where the keys do not conflict with + * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring + * items from the cache is the responsibility of the user. + * @constructor + * @struct + * @template T */ -goog.iter.chain = function(var_args) { - return goog.iter.chainFromIterable(arguments); +ol.structs.LRUCache = function() { + + /** + * @private + * @type {number} + */ + this.count_ = 0; + + /** + * @private + * @type {Object.<string, ol.structs.LRUCacheEntry>} + */ + this.entries_ = {}; + + /** + * @private + * @type {?ol.structs.LRUCacheEntry} + */ + this.oldest_ = null; + + /** + * @private + * @type {?ol.structs.LRUCacheEntry} + */ + this.newest_ = null; + }; /** - * Takes a single iterable containing zero or more iterables and returns one - * iterator that will iterate over each one in the order given. - * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable - * @param {goog.iter.Iterable} iterable The iterable of iterables to chain. - * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will - * iterate over all the contents of the iterables contained within - * {@code iterable}. - * @template VALUE + * FIXME empty description for jsdoc */ -goog.iter.chainFromIterable = function(iterable) { - var iterator = goog.iter.toIterator(iterable); - var iter = new goog.iter.Iterator(); - var current = null; - - iter.next = function() { - while (true) { - if (current == null) { - var it = iterator.next(); - current = goog.iter.toIterator(it); - } - try { - return current.next(); - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - current = null; - } +ol.structs.LRUCache.prototype.assertValid = function() { + if (this.count_ === 0) { + goog.asserts.assert(goog.object.isEmpty(this.entries_), + 'entries must be an empty object (count = 0)'); + goog.asserts.assert(goog.isNull(this.oldest_), + 'oldest must be null (count = 0)'); + goog.asserts.assert(goog.isNull(this.newest_), + 'newest must be null (count = 0)'); + } else { + goog.asserts.assert(goog.object.getCount(this.entries_) == this.count_, + 'number of entries matches count'); + goog.asserts.assert(!goog.isNull(this.oldest_), + 'we have an oldest entry'); + goog.asserts.assert(goog.isNull(this.oldest_.older), + 'no entry is older than oldest'); + goog.asserts.assert(!goog.isNull(this.newest_), + 'we have a newest entry'); + goog.asserts.assert(goog.isNull(this.newest_.newer), + 'no entry is newer than newest'); + var i, entry; + var older = null; + i = 0; + for (entry = this.oldest_; !goog.isNull(entry); entry = entry.newer) { + goog.asserts.assert(entry.older === older, + 'entry.older links to correct older'); + older = entry; + ++i; } - }; + goog.asserts.assert(i == this.count_, 'iterated correct amount of times'); + var newer = null; + i = 0; + for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) { + goog.asserts.assert(entry.newer === newer, + 'entry.newer links to correct newer'); + newer = entry; + ++i; + } + goog.asserts.assert(i == this.count_, 'iterated correct amount of times'); + } +}; - return iter; + +/** + * FIXME empty description for jsdoc + */ +ol.structs.LRUCache.prototype.clear = function() { + this.count_ = 0; + this.entries_ = {}; + this.oldest_ = null; + this.newest_ = null; }; /** - * Builds a new iterator that iterates over the original, but skips elements as - * long as a supplied function returns true. - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * object. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f - * The function to call for every value. This function takes 3 arguments - * (the value, undefined, and the iterator) and should return a boolean. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {!goog.iter.Iterator<VALUE>} A new iterator that drops elements from - * the original iterator as long as {@code f} is true. - * @template THIS, VALUE + * @param {string} key Key. + * @return {boolean} Contains key. */ -goog.iter.dropWhile = function(iterable, f, opt_obj) { - var iterator = goog.iter.toIterator(iterable); - var newIter = new goog.iter.Iterator; - var dropping = true; - newIter.next = function() { - while (true) { - var val = iterator.next(); - if (dropping && f.call(opt_obj, val, undefined, iterator)) { - continue; - } else { - dropping = false; - } - return val; - } - }; - return newIter; +ol.structs.LRUCache.prototype.containsKey = function(key) { + return this.entries_.hasOwnProperty(key); }; /** - * Builds a new iterator that iterates over the original, but only as long as a - * supplied function returns true. - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * object. - * @param { - * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f - * The function to call for every value. This function takes 3 arguments - * (the value, undefined, and the iterator) and should return a boolean. - * @param {THIS=} opt_obj This is used as the 'this' object in f when called. - * @return {!goog.iter.Iterator<VALUE>} A new iterator that keeps elements in - * the original iterator as long as the function is true. - * @template THIS, VALUE + * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function + * to call for every entry from the oldest to the newer. This function takes + * 3 arguments (the entry value, the entry key and the LRUCache object). + * The return value is ignored. + * @param {S=} opt_this The object to use as `this` in `f`. + * @template S */ -goog.iter.takeWhile = function(iterable, f, opt_obj) { - var iterator = goog.iter.toIterator(iterable); - var iter = new goog.iter.Iterator(); - iter.next = function() { - var val = iterator.next(); - if (f.call(opt_obj, val, undefined, iterator)) { - return val; - } - throw goog.iter.StopIteration; - }; - return iter; +ol.structs.LRUCache.prototype.forEach = function(f, opt_this) { + var entry = this.oldest_; + while (!goog.isNull(entry)) { + f.call(opt_this, entry.value_, entry.key_, this); + entry = entry.newer; + } }; /** - * Converts the iterator to an array - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator - * to convert to an array. - * @return {!Array<VALUE>} An array of the elements the iterator iterates over. - * @template VALUE + * @param {string} key Key. + * @return {T} Value. */ -goog.iter.toArray = function(iterable) { - // Fast path for array-like. - if (goog.isArrayLike(iterable)) { - return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable)); +ol.structs.LRUCache.prototype.get = function(key) { + var entry = this.entries_[key]; + goog.asserts.assert(goog.isDef(entry), 'an entry exists for key %s', key); + if (entry === this.newest_) { + return entry.value_; + } else if (entry === this.oldest_) { + this.oldest_ = this.oldest_.newer; + this.oldest_.older = null; + } else { + entry.newer.older = entry.older; + entry.older.newer = entry.newer; } - iterable = goog.iter.toIterator(iterable); - var array = []; - goog.iter.forEach(iterable, function(val) { - array.push(val); - }); - return array; + entry.newer = null; + entry.older = this.newest_; + this.newest_.newer = entry; + this.newest_ = entry; + return entry.value_; }; /** - * Iterates over two iterables and returns true if they contain the same - * sequence of elements and have the same length. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable1 The first - * iterable object. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable2 The second - * iterable object. - * @param {function(VALUE,VALUE):boolean=} opt_equalsFn Optional comparison - * function. - * Should take two arguments to compare, and return true if the arguments - * are equal. Defaults to {@link goog.array.defaultCompareEquality} which - * compares the elements using the built-in '===' operator. - * @return {boolean} true if the iterables contain the same sequence of elements - * and have the same length. - * @template VALUE + * @return {number} Count. */ -goog.iter.equals = function(iterable1, iterable2, opt_equalsFn) { - var fillValue = {}; - var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2); - var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality; - return goog.iter.every(pairs, function(pair) { - return equalsFn(pair[0], pair[1]); - }); +ol.structs.LRUCache.prototype.getCount = function() { + return this.count_; }; /** - * Advances the iterator to the next position, returning the given default value - * instead of throwing an exception if the iterator has no more entries. - * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterable - * object. - * @param {VALUE} defaultValue The value to return if the iterator is empty. - * @return {VALUE} The next item in the iteration, or defaultValue if the - * iterator was empty. - * @template VALUE + * @return {Array.<string>} Keys. */ -goog.iter.nextOrValue = function(iterable, defaultValue) { - try { - return goog.iter.toIterator(iterable).next(); - } catch (e) { - if (e != goog.iter.StopIteration) { - throw e; - } - return defaultValue; +ol.structs.LRUCache.prototype.getKeys = function() { + var keys = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) { + keys[i++] = entry.key_; } + goog.asserts.assert(i == this.count_, 'iterated correct number of times'); + return keys; }; /** - * Cartesian product of zero or more sets. Gives an iterator that gives every - * combination of one element chosen from each set. For example, - * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]). - * @see http://docs.python.org/library/itertools.html#itertools.product - * @param {...!goog.array.ArrayLike<VALUE>} var_args Zero or more sets, as - * arrays. - * @return {!goog.iter.Iterator<!Array<VALUE>>} An iterator that gives each - * n-tuple (as an array). - * @template VALUE + * @return {Array.<T>} Values. */ -goog.iter.product = function(var_args) { - var someArrayEmpty = goog.array.some(arguments, function(arr) { - return !arr.length; - }); - - // An empty set in a cartesian product gives an empty set. - if (someArrayEmpty || !arguments.length) { - return new goog.iter.Iterator(); +ol.structs.LRUCache.prototype.getValues = function() { + var values = new Array(this.count_); + var i = 0; + var entry; + for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) { + values[i++] = entry.value_; } + goog.asserts.assert(i == this.count_, 'iterated correct number of times'); + return values; +}; - var iter = new goog.iter.Iterator(); - var arrays = arguments; - // The first indices are [0, 0, ...] - var indicies = goog.array.repeat(0, arrays.length); +/** + * @return {T} Last value. + */ +ol.structs.LRUCache.prototype.peekLast = function() { + goog.asserts.assert(!goog.isNull(this.oldest_), 'oldest must not be null'); + return this.oldest_.value_; +}; - iter.next = function() { - if (indicies) { - var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) { - return arrays[arrayIndex][valueIndex]; - }); +/** + * @return {string} Last key. + */ +ol.structs.LRUCache.prototype.peekLastKey = function() { + goog.asserts.assert(!goog.isNull(this.oldest_), 'oldest must not be null'); + return this.oldest_.key_; +}; - // Generate the next-largest indices for the next call. - // Increase the rightmost index. If it goes over, increase the next - // rightmost (like carry-over addition). - for (var i = indicies.length - 1; i >= 0; i--) { - // Assertion prevents compiler warning below. - goog.asserts.assert(indicies); - if (indicies[i] < arrays[i].length - 1) { - indicies[i]++; - break; - } - // We're at the last indices (the last element of every array), so - // the iteration is over on the next call. - if (i == 0) { - indicies = null; - break; - } - // Reset the index in this column and loop back to increment the - // next one. - indicies[i] = 0; - } - return retVal; - } +/** + * @return {T} value Value. + */ +ol.structs.LRUCache.prototype.pop = function() { + goog.asserts.assert(!goog.isNull(this.oldest_), 'oldest must not be null'); + goog.asserts.assert(!goog.isNull(this.newest_), 'newest must not be null'); + var entry = this.oldest_; + goog.asserts.assert(entry.key_ in this.entries_, + 'oldest is indexed in entries'); + delete this.entries_[entry.key_]; + if (!goog.isNull(entry.newer)) { + entry.newer.older = null; + } + this.oldest_ = entry.newer; + if (goog.isNull(this.oldest_)) { + this.newest_ = null; + } + --this.count_; + return entry.value_; +}; - throw goog.iter.StopIteration; - }; - return iter; +/** + * @param {string} key Key. + * @param {T} value Value. + */ +ol.structs.LRUCache.prototype.set = function(key, value) { + goog.asserts.assert(!(key in {}), + 'key is not a standard property of objects (e.g. "__proto__")'); + goog.asserts.assert(!(key in this.entries_), + 'key is not used already'); + var entry = { + key_: key, + newer: null, + older: this.newest_, + value_: value + }; + if (goog.isNull(this.newest_)) { + this.oldest_ = entry; + } else { + this.newest_.newer = entry; + } + this.newest_ = entry; + this.entries_[key] = entry; + ++this.count_; }; /** - * Create an iterator to cycle over the iterable's elements indefinitely. - * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ... - * @see: http://docs.python.org/library/itertools.html#itertools.cycle. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable object. - * @return {!goog.iter.Iterator<VALUE>} An iterator that iterates indefinitely - * over the values in {@code iterable}. - * @template VALUE + * @typedef {{key_: string, + * newer: ol.structs.LRUCacheEntry, + * older: ol.structs.LRUCacheEntry, + * value_: *}} */ -goog.iter.cycle = function(iterable) { - var baseIterator = goog.iter.toIterator(iterable); +ol.structs.LRUCacheEntry; - // We maintain a cache to store the iterable elements as we iterate - // over them. The cache is used to return elements once we have - // iterated over the iterable once. - var cache = []; - var cacheIndex = 0; +goog.provide('ol.TileCache'); - var iter = new goog.iter.Iterator(); +goog.require('ol'); +goog.require('ol.TileRange'); +goog.require('ol.structs.LRUCache'); +goog.require('ol.tilecoord'); - // This flag is set after the iterable is iterated over once - var useCache = false; - iter.next = function() { - var returnElement = null; - // Pull elements off the original iterator if not using cache - if (!useCache) { - try { - // Return the element from the iterable - returnElement = baseIterator.next(); - cache.push(returnElement); - return returnElement; - } catch (e) { - // If an exception other than StopIteration is thrown - // or if there are no elements to iterate over (the iterable was empty) - // throw an exception - if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) { - throw e; - } - // set useCache to true after we know that a 'StopIteration' exception - // was thrown and the cache is not empty (to handle the 'empty iterable' - // use case) - useCache = true; - } - } +/** + * @constructor + * @extends {ol.structs.LRUCache.<ol.Tile>} + * @param {number=} opt_highWaterMark High water mark. + * @struct + */ +ol.TileCache = function(opt_highWaterMark) { - returnElement = cache[cacheIndex]; - cacheIndex = (cacheIndex + 1) % cache.length; + goog.base(this); - return returnElement; - }; + /** + * @private + * @type {number} + */ + this.highWaterMark_ = goog.isDef(opt_highWaterMark) ? + opt_highWaterMark : ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK; - return iter; }; +goog.inherits(ol.TileCache, ol.structs.LRUCache); /** - * Creates an iterator that counts indefinitely from a starting value. - * @see http://docs.python.org/2/library/itertools.html#itertools.count - * @param {number=} opt_start The starting value. Default is 0. - * @param {number=} opt_step The number to increment with between each call to - * next. Negative and floating point numbers are allowed. Default is 1. - * @return {!goog.iter.Iterator<number>} A new iterator that returns the values - * in the series. + * @return {boolean} Can expire cache. */ -goog.iter.count = function(opt_start, opt_step) { - var counter = opt_start || 0; - var step = goog.isDef(opt_step) ? opt_step : 1; - var iter = new goog.iter.Iterator(); +ol.TileCache.prototype.canExpireCache = function() { + return this.getCount() > this.highWaterMark_; +}; - iter.next = function() { - var returnValue = counter; - counter += step; - return returnValue; - }; - return iter; +/** + * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. + */ +ol.TileCache.prototype.expireCache = function(usedTiles) { + var tile, zKey; + while (this.canExpireCache()) { + tile = /** @type {ol.Tile} */ (this.peekLast()); + zKey = tile.tileCoord[0].toString(); + if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) { + break; + } else { + this.pop().dispose(); + } + } }; /** - * Creates an iterator that returns the same object or value repeatedly. - * @param {VALUE} value Any object or value to repeat. - * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the - * repeated value. - * @template VALUE + * Remove a tile range from the cache, e.g. to invalidate tiles. + * @param {ol.TileRange} tileRange The tile range to prune. */ -goog.iter.repeat = function(value) { - var iter = new goog.iter.Iterator(); +ol.TileCache.prototype.pruneTileRange = function(tileRange) { + var i = this.getCount(), + key; + while (i--) { + key = this.peekLastKey(); + if (tileRange.contains(ol.tilecoord.createFromString(key))) { + this.pop().dispose(); + } else { + this.get(key); + } + } +}; - iter.next = goog.functions.constant(value); +goog.provide('ol.Tile'); +goog.provide('ol.TileState'); - return iter; +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('ol.TileCoord'); + + +/** + * @enum {number} + */ +ol.TileState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3, + EMPTY: 4 }; + /** - * Creates an iterator that returns running totals from the numbers in - * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields - * {@code 1 -> 3 -> 6 -> 10 -> 15}. - * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate - * @param {!goog.iter.Iterable<number>} iterable The iterable of numbers to - * accumulate. - * @return {!goog.iter.Iterator<number>} A new iterator that returns the - * numbers in the series. + * @classdesc + * Base class for tiles. + * + * @constructor + * @extends {goog.events.EventTarget} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileState} state State. */ -goog.iter.accumulate = function(iterable) { - var iterator = goog.iter.toIterator(iterable); - var total = 0; - var iter = new goog.iter.Iterator(); +ol.Tile = function(tileCoord, state) { - iter.next = function() { - total += iterator.next(); - return total; - }; + goog.base(this); + + /** + * @type {ol.TileCoord} + */ + this.tileCoord = tileCoord; + + /** + * @protected + * @type {ol.TileState} + */ + this.state = state; - return iter; }; +goog.inherits(ol.Tile, goog.events.EventTarget); /** - * Creates an iterator that returns arrays containing the ith elements from the - * provided iterables. The returned arrays will be the same size as the number - * of iterables given in {@code var_args}. Once the shortest iterable is - * exhausted, subsequent calls to {@code next()} will throw - * {@code goog.iter.StopIteration}. - * @see http://docs.python.org/2/library/itertools.html#itertools.izip - * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any - * number of iterable objects. - * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns - * arrays of elements from the provided iterables. - * @template VALUE + * @protected */ -goog.iter.zip = function(var_args) { - var args = arguments; - var iter = new goog.iter.Iterator(); +ol.Tile.prototype.changed = function() { + this.dispatchEvent(goog.events.EventType.CHANGE); +}; - if (args.length > 0) { - var iterators = goog.array.map(args, goog.iter.toIterator); - iter.next = function() { - var arr = goog.array.map(iterators, function(it) { - return it.next(); - }); - return arr; - }; - } - return iter; -}; +/** + * Get the HTML image element for this tile (may be a Canvas, Image, or Video). + * @function + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. + */ +ol.Tile.prototype.getImage = goog.abstractMethod; /** - * Creates an iterator that returns arrays containing the ith elements from the - * provided iterables. The returned arrays will be the same size as the number - * of iterables given in {@code var_args}. Shorter iterables will be extended - * with {@code fillValue}. Once the longest iterable is exhausted, subsequent - * calls to {@code next()} will throw {@code goog.iter.StopIteration}. - * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest - * @param {VALUE} fillValue The object or value used to fill shorter iterables. - * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any - * number of iterable objects. - * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns - * arrays of elements from the provided iterables. - * @template VALUE + * @return {string} Key. */ -goog.iter.zipLongest = function(fillValue, var_args) { - var args = goog.array.slice(arguments, 1); - var iter = new goog.iter.Iterator(); +ol.Tile.prototype.getKey = function() { + return goog.getUid(this).toString(); +}; - if (args.length > 0) { - var iterators = goog.array.map(args, goog.iter.toIterator); - iter.next = function() { - var iteratorsHaveValues = false; // false when all iterators are empty. - var arr = goog.array.map(iterators, function(it) { - var returnValue; - try { - returnValue = it.next(); - // Iterator had a value, so we've not exhausted the iterators. - // Set flag accordingly. - iteratorsHaveValues = true; - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - returnValue = fillValue; - } - return returnValue; - }); +/** + * Get the tile coordinate for this tile. + * @return {ol.TileCoord} + * @api + */ +ol.Tile.prototype.getTileCoord = function() { + return this.tileCoord; +}; - if (!iteratorsHaveValues) { - throw goog.iter.StopIteration; - } - return arr; - }; - } - return iter; +/** + * @return {ol.TileState} State. + */ +ol.Tile.prototype.getState = function() { + return this.state; }; /** - * Creates an iterator that filters {@code iterable} based on a series of - * {@code selectors}. On each call to {@code next()}, one item is taken from - * both the {@code iterable} and {@code selectors} iterators. If the item from - * {@code selectors} evaluates to true, the item from {@code iterable} is given. - * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors} - * is exhausted, subsequent calls to {@code next()} will throw - * {@code goog.iter.StopIteration}. - * @see http://docs.python.org/2/library/itertools.html#itertools.compress - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to filter. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} selectors An - * iterable of items to be evaluated in a boolean context to determine if - * the corresponding element in {@code iterable} should be included in the - * result. - * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the - * filtered values. - * @template VALUE + * FIXME empty description for jsdoc */ -goog.iter.compress = function(iterable, selectors) { - var selectorIterator = goog.iter.toIterator(selectors); +ol.Tile.prototype.load = goog.abstractMethod; - return goog.iter.filter(iterable, function() { - return !!selectorIterator.next(); - }); +goog.provide('ol.source.Source'); +goog.provide('ol.source.State'); + +goog.require('goog.events.EventType'); +goog.require('ol.Attribution'); +goog.require('ol.Object'); +goog.require('ol.proj'); + + +/** + * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. + * @enum {string} + * @api + */ +ol.source.State = { + UNDEFINED: 'undefined', + LOADING: 'loading', + READY: 'ready', + ERROR: 'error' }; +/** + * @typedef {{attributions: (Array.<ol.Attribution>|undefined), + * logo: (string|olx.LogoOptions|undefined), + * projection: ol.proj.ProjectionLike, + * state: (ol.source.State|undefined), + * wrapX: (boolean|undefined)}} + */ +ol.source.SourceOptions; + + /** - * Implements the {@code goog.iter.groupBy} iterator. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to group. - * @param {function(...[VALUE]): KEY=} opt_keyFunc Optional function for - * determining the key value for each group in the {@code iterable}. Default - * is the identity function. + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for {@link ol.layer.Layer} sources. + * + * A generic `change` event is triggered when the state of the source changes. + * * @constructor - * @extends {goog.iter.Iterator<!Array<?>>} - * @template KEY, VALUE - * @private + * @extends {ol.Object} + * @param {ol.source.SourceOptions} options Source options. + * @api stable */ -goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) { +ol.source.Source = function(options) { + + goog.base(this); /** - * The iterable to group, coerced to an iterator. - * @type {!goog.iter.Iterator} + * @private + * @type {ol.proj.Projection} */ - this.iterator = goog.iter.toIterator(iterable); + this.projection_ = ol.proj.get(options.projection); /** - * A function for determining the key value for each element in the iterable. - * If no function is provided, the identity function is used and returns the - * element unchanged. - * @type {function(...[VALUE]): KEY} + * @private + * @type {Array.<ol.Attribution>} */ - this.keyFunc = opt_keyFunc || goog.functions.identity; + this.attributions_ = goog.isDef(options.attributions) ? + options.attributions : null; /** - * The target key for determining the start of a group. - * @type {KEY} + * @private + * @type {string|olx.LogoOptions|undefined} */ - this.targetKey; + this.logo_ = options.logo; /** - * The current key visited during iteration. - * @type {KEY} + * @private + * @type {ol.source.State} */ - this.currentKey; + this.state_ = goog.isDef(options.state) ? + options.state : ol.source.State.READY; /** - * The current value being added to the group. - * @type {VALUE} + * @private + * @type {boolean} */ - this.currentValue; -}; -goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator); + this.wrapX_ = goog.isDef(options.wrapX) ? options.wrapX : false; - -/** @override */ -goog.iter.GroupByIterator_.prototype.next = function() { - while (this.currentKey == this.targetKey) { - this.currentValue = this.iterator.next(); // Exits on StopIteration - this.currentKey = this.keyFunc(this.currentValue); - } - this.targetKey = this.currentKey; - return [this.currentKey, this.groupItems_(this.targetKey)]; }; +goog.inherits(ol.source.Source, ol.Object); /** - * Performs the grouping of objects using the given key. - * @param {KEY} targetKey The target key object for the group. - * @return {!Array<VALUE>} An array of grouped objects. - * @private + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids. + * @param {function(ol.Feature): T} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T */ -goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) { - var arr = []; - while (this.currentKey == targetKey) { - arr.push(this.currentValue); - try { - this.currentValue = this.iterator.next(); - } catch (ex) { - if (ex !== goog.iter.StopIteration) { - throw ex; - } - break; - } - this.currentKey = this.keyFunc(this.currentValue); - } - return arr; -}; +ol.source.Source.prototype.forEachFeatureAtCoordinate = + goog.nullFunction; /** - * Creates an iterator that returns arrays containing elements from the - * {@code iterable} grouped by a key value. For iterables with repeated - * elements (i.e. sorted according to a particular key function), this function - * has a {@code uniq}-like effect. For example, grouping the array: - * {@code [A, B, B, C, C, A]} produces - * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}. - * @see http://docs.python.org/2/library/itertools.html#itertools.groupby - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to group. - * @param {function(...[VALUE]): KEY=} opt_keyFunc Optional function for - * determining the key value for each group in the {@code iterable}. Default - * is the identity function. - * @return {!goog.iter.Iterator<!Array<?>>} A new iterator that returns - * arrays of consecutive key and groups. - * @template KEY, VALUE + * Get the attributions of the source. + * @return {Array.<ol.Attribution>} Attributions. + * @api stable */ -goog.iter.groupBy = function(iterable, opt_keyFunc) { - return new goog.iter.GroupByIterator_(iterable, opt_keyFunc); +ol.source.Source.prototype.getAttributions = function() { + return this.attributions_; }; /** - * Gives an iterator that gives the result of calling the given function - * <code>f</code> with the arguments taken from the next element from - * <code>iterable</code> (the elements are expected to also be iterables). - * - * Similar to {@see goog.iter#map} but allows the function to accept multiple - * arguments from the iterable. - * - * @param {!goog.iter.Iterable<!goog.iter.Iterable>} iterable The iterable of - * iterables to iterate over. - * @param {function(this:THIS,...[*]):RESULT} f The function to call for every - * element. This function takes N+2 arguments, where N represents the - * number of items from the next element of the iterable. The two - * additional arguments passed to the function are undefined and the - * iterator itself. The function should return a new value. - * @param {THIS=} opt_obj The object to be used as the value of 'this' within - * {@code f}. - * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the - * results of applying the function to each element in the original - * iterator. - * @template THIS, RESULT + * Get the logo of the source. + * @return {string|olx.LogoOptions|undefined} Logo. + * @api stable */ -goog.iter.starMap = function(iterable, f, opt_obj) { - var iterator = goog.iter.toIterator(iterable); - var iter = new goog.iter.Iterator(); +ol.source.Source.prototype.getLogo = function() { + return this.logo_; +}; - iter.next = function() { - var args = goog.iter.toArray(iterator.next()); - return f.apply(opt_obj, goog.array.concat(args, undefined, iterator)); - }; - return iter; +/** + * Get the projection of the source. + * @return {ol.proj.Projection} Projection. + * @api + */ +ol.source.Source.prototype.getProjection = function() { + return this.projection_; }; /** - * Returns an array of iterators each of which can iterate over the values in - * {@code iterable} without advancing the others. - * @see http://docs.python.org/2/library/itertools.html#itertools.tee - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to tee. - * @param {number=} opt_num The number of iterators to create. Default is 2. - * @return {!Array<goog.iter.Iterator<VALUE>>} An array of iterators. - * @template VALUE + * @return {Array.<number>|undefined} Resolutions. */ -goog.iter.tee = function(iterable, opt_num) { - var iterator = goog.iter.toIterator(iterable); - var num = goog.isNumber(opt_num) ? opt_num : 2; - var buffers = goog.array.map(goog.array.range(num), function() { - return []; - }); - - var addNextIteratorValueToBuffers = function() { - var val = iterator.next(); - goog.array.forEach(buffers, function(buffer) { - buffer.push(val); - }); - }; +ol.source.Source.prototype.getResolutions = goog.abstractMethod; - var createIterator = function(buffer) { - // Each tee'd iterator has an associated buffer (initially empty). When a - // tee'd iterator's buffer is empty, it calls - // addNextIteratorValueToBuffers(), adding the next value to all tee'd - // iterators' buffers, and then returns that value. This allows each - // iterator to be advanced independently. - var iter = new goog.iter.Iterator(); - iter.next = function() { - if (goog.array.isEmpty(buffer)) { - addNextIteratorValueToBuffers(); - } - goog.asserts.assert(!goog.array.isEmpty(buffer)); - return buffer.shift(); - }; +/** + * Get the state of the source, see {@link ol.source.State} for possible states. + * @return {ol.source.State} State. + * @api + */ +ol.source.Source.prototype.getState = function() { + return this.state_; +}; - return iter; - }; - return goog.array.map(buffers, createIterator); +/** + * @return {boolean|undefined} Wrap X. + */ +ol.source.Source.prototype.getWrapX = function() { + return this.wrapX_; }; /** - * Creates an iterator that returns arrays containing a count and an element - * obtained from the given {@code iterable}. - * @see http://docs.python.org/2/library/functions.html#enumerate - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to enumerate. - * @param {number=} opt_start Optional starting value. Default is 0. - * @return {!goog.iter.Iterator<!Array<?>>} A new iterator containing - * count/item pairs. - * @template VALUE + * Set the attributions of the source. + * @param {Array.<ol.Attribution>} attributions Attributions. */ -goog.iter.enumerate = function(iterable, opt_start) { - return goog.iter.zip(goog.iter.count(opt_start), iterable); +ol.source.Source.prototype.setAttributions = function(attributions) { + this.attributions_ = attributions; }; /** - * Creates an iterator that returns the first {@code limitSize} elements from an - * iterable. If this number is greater than the number of elements in the - * iterable, all the elements are returned. - * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to limit. - * @param {number} limitSize The maximum number of elements to return. - * @return {!goog.iter.Iterator<VALUE>} A new iterator containing - * {@code limitSize} elements. - * @template VALUE - */ -goog.iter.limit = function(iterable, limitSize) { - goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0); - - var iterator = goog.iter.toIterator(iterable); - - var iter = new goog.iter.Iterator(); - var remaining = limitSize; - - iter.next = function() { - if (remaining-- > 0) { - return iterator.next(); - } - throw goog.iter.StopIteration; - }; - - return iter; -}; - - -/** - * Creates an iterator that is advanced {@code count} steps ahead. Consumed - * values are silently discarded. If {@code count} is greater than the number - * of elements in {@code iterable}, an empty iterator is returned. Subsequent - * calls to {@code next()} will throw {@code goog.iter.StopIteration}. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to consume. - * @param {number} count The number of elements to consume from the iterator. - * @return {!goog.iter.Iterator<VALUE>} An iterator advanced zero or more steps - * ahead. - * @template VALUE + * Set the logo of the source. + * @param {string|olx.LogoOptions|undefined} logo Logo. */ -goog.iter.consume = function(iterable, count) { - goog.asserts.assert(goog.math.isInt(count) && count >= 0); - - var iterator = goog.iter.toIterator(iterable); - - while (count-- > 0) { - goog.iter.nextOrValue(iterator, null); - } - - return iterator; +ol.source.Source.prototype.setLogo = function(logo) { + this.logo_ = logo; }; /** - * Creates an iterator that returns a range of elements from an iterable. - * Similar to {@see goog.array#slice} but does not support negative indexes. - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to slice. - * @param {number} start The index of the first element to return. - * @param {number=} opt_end The index after the last element to return. If - * defined, must be greater than or equal to {@code start}. - * @return {!goog.iter.Iterator<VALUE>} A new iterator containing a slice of - * the original. - * @template VALUE + * Set the state of the source. + * @param {ol.source.State} state State. + * @protected */ -goog.iter.slice = function(iterable, start, opt_end) { - goog.asserts.assert(goog.math.isInt(start) && start >= 0); - - var iterator = goog.iter.consume(iterable, start); - - if (goog.isNumber(opt_end)) { - goog.asserts.assert( - goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start); - iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */); - } - - return iterator; +ol.source.Source.prototype.setState = function(state) { + this.state_ = state; + this.changed(); }; /** - * Checks an array for duplicate elements. - * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to check for - * duplicates. - * @return {boolean} True, if the array contains duplicates, false otherwise. - * @private - * @template VALUE + * Set the projection of the source. + * @param {ol.proj.Projection} projection Projection. */ -// TODO(user): Consider moving this into goog.array as a public function. -goog.iter.hasDuplicates_ = function(arr) { - var deduped = []; - goog.array.removeDuplicates(arr, deduped); - return arr.length != deduped.length; +ol.source.Source.prototype.setProjection = function(projection) { + this.projection_ = projection; }; +goog.provide('ol.tilegrid.TileGrid'); -/** - * Creates an iterator that returns permutations of elements in - * {@code iterable}. - * - * Permutations are obtained by taking the Cartesian product of - * {@code opt_length} iterables and filtering out those with repeated - * elements. For example, the permutations of {@code [1,2,3]} are - * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}. - * @see http://docs.python.org/2/library/itertools.html#itertools.permutations - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable from which to generate permutations. - * @param {number=} opt_length Length of each permutation. If omitted, defaults - * to the length of {@code iterable}. - * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing the - * permutations of {@code iterable}. - * @template VALUE - */ -goog.iter.permutations = function(iterable, opt_length) { - var elements = goog.iter.toArray(iterable); - var length = goog.isNumber(opt_length) ? opt_length : elements.length; - - var sets = goog.array.repeat(elements, length); - var product = goog.iter.product.apply(undefined, sets); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('goog.object'); +goog.require('ol'); +goog.require('ol.Coordinate'); +goog.require('ol.TileCoord'); +goog.require('ol.TileRange'); +goog.require('ol.array'); +goog.require('ol.extent'); +goog.require('ol.extent.Corner'); +goog.require('ol.proj'); +goog.require('ol.proj.METERS_PER_UNIT'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); - return goog.iter.filter(product, function(arr) { - return !goog.iter.hasDuplicates_(arr); - }); -}; /** - * Creates an iterator that returns combinations of elements from - * {@code iterable}. + * @classdesc + * Base class for setting the grid pattern for sources accessing tiled-image + * servers. * - * Combinations are obtained by taking the {@see goog.iter#permutations} of - * {@code iterable} and filtering those whose elements appear in the order they - * are encountered in {@code iterable}. For example, the 3-length combinations - * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}. - * @see http://docs.python.org/2/library/itertools.html#itertools.combinations - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable from which to generate combinations. - * @param {number} length The length of each combination. - * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing - * combinations from the {@code iterable}. - * @template VALUE + * @constructor + * @param {olx.tilegrid.TileGridOptions} options Tile grid options. + * @struct + * @api stable */ -goog.iter.combinations = function(iterable, length) { - var elements = goog.iter.toArray(iterable); - var indexes = goog.iter.range(elements.length); - var indexIterator = goog.iter.permutations(indexes, length); - // sortedIndexIterator will now give arrays of with the given length that - // indicate what indexes into "elements" should be returned on each iteration. - var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) { - return goog.array.isSorted(arr); - }); - - var iter = new goog.iter.Iterator(); - - function getIndexFromElements(index) { - return elements[index]; - } - - iter.next = function() { - return goog.array.map( - /** @type {!Array<number>} */ - (sortedIndexIterator.next()), getIndexFromElements); - }; +ol.tilegrid.TileGrid = function(options) { - return iter; -}; + /** + * @protected + * @type {number} + */ + this.minZoom = goog.isDef(options.minZoom) ? options.minZoom : 0; + /** + * @private + * @type {!Array.<number>} + */ + this.resolutions_ = options.resolutions; + goog.asserts.assert(goog.array.isSorted(this.resolutions_, function(a, b) { + return b - a; + }, true), 'resolutions must be sorted in descending order'); -/** - * Creates an iterator that returns combinations of elements from - * {@code iterable}, with repeated elements possible. - * - * Combinations are obtained by taking the Cartesian product of {@code length} - * iterables and filtering those whose elements appear in the order they are - * encountered in {@code iterable}. For example, the 2-length combinations of - * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}. - * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement - * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition - * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The - * iterable to combine. - * @param {number} length The length of each combination. - * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing - * combinations from the {@code iterable}. - * @template VALUE - */ -goog.iter.combinationsWithReplacement = function(iterable, length) { - var elements = goog.iter.toArray(iterable); - var indexes = goog.array.range(elements.length); - var sets = goog.array.repeat(indexes, length); - var indexIterator = goog.iter.product.apply(undefined, sets); - // sortedIndexIterator will now give arrays of with the given length that - // indicate what indexes into "elements" should be returned on each iteration. - var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) { - return goog.array.isSorted(arr); - }); + /** + * @protected + * @type {number} + */ + this.maxZoom = this.resolutions_.length - 1; - var iter = new goog.iter.Iterator(); + /** + * @private + * @type {ol.Coordinate} + */ + this.origin_ = goog.isDef(options.origin) ? options.origin : null; - function getIndexFromElements(index) { - return elements[index]; + /** + * @private + * @type {Array.<ol.Coordinate>} + */ + this.origins_ = null; + if (goog.isDef(options.origins)) { + this.origins_ = options.origins; + goog.asserts.assert(this.origins_.length == this.resolutions_.length, + 'number of origins and resolutions must be equal'); } - iter.next = function() { - return goog.array.map( - /** @type {!Array<number>} */ - (sortedIndexIterator.next()), getIndexFromElements); - }; - - return iter; -}; - -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Datastructure: Hash Map. - * - * @author arv@google.com (Erik Arvidsson) - * - * This file contains an implementation of a Map structure. It implements a lot - * of the methods used in goog.structs so those functions work on hashes. This - * is best suited for complex key types. For simple keys such as numbers and - * strings, and where special names like __proto__ are not a concern, consider - * using the lighter-weight utilities in goog.object. - */ - - -goog.provide('goog.structs.Map'); - -goog.require('goog.iter.Iterator'); -goog.require('goog.iter.StopIteration'); -goog.require('goog.object'); - + var extent = options.extent; + if (goog.isDef(extent) && + goog.isNull(this.origin_) && goog.isNull(this.origins_)) { + this.origin_ = ol.extent.getTopLeft(extent); + } -/** - * Class for Hash Map datastructure. - * @param {*=} opt_map Map or Object to initialize the map with. - * @param {...*} var_args If 2 or more arguments are present then they - * will be used as key-value pairs. - * @constructor - * @template K, V - */ -goog.structs.Map = function(opt_map, var_args) { + goog.asserts.assert( + (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) || + (!goog.isNull(this.origin_) && goog.isNull(this.origins_)), + 'either origin or origins must be configured, never both'); /** - * Underlying JS object used to implement the map. - * @private {!Object} + * @private + * @type {Array.<number|ol.Size>} */ - this.map_ = {}; + this.tileSizes_ = null; + if (goog.isDef(options.tileSizes)) { + this.tileSizes_ = options.tileSizes; + goog.asserts.assert(this.tileSizes_.length == this.resolutions_.length, + 'number of tileSizes and resolutions must be equal'); + } /** - * An array of keys. This is necessary for two reasons: - * 1. Iterating the keys using for (var key in this.map_) allocates an - * object for every key in IE which is really bad for IE6 GC perf. - * 2. Without a side data structure, we would need to escape all the keys - * as that would be the only way we could tell during iteration if the - * key was an internal key or a property of the object. - * - * This array can contain deleted keys so it's necessary to check the map - * as well to see if the key is still in the map (this doesn't require a - * memory allocation in IE). - * @private {!Array<string>} + * @private + * @type {number|ol.Size} */ - this.keys_ = []; + this.tileSize_ = goog.isDef(options.tileSize) ? + options.tileSize : + goog.isNull(this.tileSizes_) ? ol.DEFAULT_TILE_SIZE : null; + goog.asserts.assert( + (goog.isNull(this.tileSize_) && !goog.isNull(this.tileSizes_)) || + (!goog.isNull(this.tileSize_) && goog.isNull(this.tileSizes_)), + 'either tileSize or tileSizes must be configured, never both'); /** - * The number of key value pairs in the map. - * @private {number} + * @private + * @type {ol.Extent} */ - this.count_ = 0; + this.extent_ = goog.isDef(extent) ? extent : null; + /** - * Version used to detect changes while iterating. - * @private {number} + * @private + * @type {Array.<ol.TileRange>} */ - this.version_ = 0; - - var argLength = arguments.length; + this.fullTileRanges_ = null; - if (argLength > 1) { - if (argLength % 2) { - throw Error('Uneven number of arguments'); - } - for (var i = 0; i < argLength; i += 2) { - this.set(arguments[i], arguments[i + 1]); - } - } else if (opt_map) { - this.addAll(/** @type {Object} */ (opt_map)); + if (goog.isDef(options.sizes)) { + goog.asserts.assert(options.sizes.length == this.resolutions_.length, + 'number of sizes and resolutions must be equal'); + this.fullTileRanges_ = goog.array.map(options.sizes, function(size, z) { + goog.asserts.assert(size[0] !== 0, 'width must not be 0'); + goog.asserts.assert(size[1] !== 0, 'height must not be 0'); + var tileRange = new ol.TileRange( + Math.min(0, size[0]), Math.max(size[0] - 1, -1), + Math.min(0, size[1]), Math.max(size[1] - 1, -1)); + if (this.minZoom <= z && z <= this.maxZoom && goog.isDef(extent)) { + goog.asserts.assert(tileRange.containsTileRange( + this.getTileRangeForExtentAndZ(extent, z)), + 'extent tile range must not exceed tilegrid width and height'); + } + return tileRange; + }, this); + } else if (goog.isDefAndNotNull(extent)) { + this.calculateTileRanges_(extent); } + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + }; /** - * @return {number} The number of key-value pairs in the map. + * @private + * @type {ol.TileCoord} */ -goog.structs.Map.prototype.getCount = function() { - return this.count_; -}; +ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0]; /** - * Returns the values of the map. - * @return {!Array<V>} The values in the map. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {function(this: T, number, ol.TileRange): boolean} callback Callback. + * @param {T=} opt_this The object to use as `this` in `callback`. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {boolean} Callback succeeded. + * @template T */ -goog.structs.Map.prototype.getValues = function() { - this.cleanupKeysArray_(); - - var rv = []; - for (var i = 0; i < this.keys_.length; i++) { - var key = this.keys_[i]; - rv.push(this.map_[key]); +ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = + function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + var z = tileCoord[0] - 1; + while (z >= this.minZoom) { + if (callback.call(opt_this, z, + this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) { + return true; + } + --z; } - return rv; + return false; }; /** - * Returns the keys of the map. - * @return {!Array<string>} Array of string values. + * Get the extent for this tile grid, if it was configured. + * @return {ol.Extent} Extent. */ -goog.structs.Map.prototype.getKeys = function() { - this.cleanupKeysArray_(); - return /** @type {!Array<string>} */ (this.keys_.concat()); +ol.tilegrid.TileGrid.prototype.getExtent = function() { + return this.extent_; }; /** - * Whether the map contains the given key. - * @param {*} key The key to check for. - * @return {boolean} Whether the map contains the key. + * Get the maximum zoom level for the grid. + * @return {number} Max zoom. + * @api */ -goog.structs.Map.prototype.containsKey = function(key) { - return goog.structs.Map.hasKey_(this.map_, key); +ol.tilegrid.TileGrid.prototype.getMaxZoom = function() { + return this.maxZoom; }; /** - * Whether the map contains the given value. This is O(n). - * @param {V} val The value to check for. - * @return {boolean} Whether the map contains the value. + * Get the minimum zoom level for the grid. + * @return {number} Min zoom. + * @api */ -goog.structs.Map.prototype.containsValue = function(val) { - for (var i = 0; i < this.keys_.length; i++) { - var key = this.keys_[i]; - if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) { - return true; - } - } - return false; +ol.tilegrid.TileGrid.prototype.getMinZoom = function() { + return this.minZoom; }; /** - * Whether this map is equal to the argument map. - * @param {goog.structs.Map} otherMap The map against which to test equality. - * @param {function(V, V): boolean=} opt_equalityFn Optional equality function - * to test equality of values. If not specified, this will test whether - * the values contained in each map are identical objects. - * @return {boolean} Whether the maps are equal. + * Get the origin for the grid at the given zoom level. + * @param {number} z Z. + * @return {ol.Coordinate} Origin. + * @api stable */ -goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) { - if (this === otherMap) { - return true; - } - - if (this.count_ != otherMap.getCount()) { - return false; - } - - var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals; - - this.cleanupKeysArray_(); - for (var key, i = 0; key = this.keys_[i]; i++) { - if (!equalityFn(this.get(key), otherMap.get(key))) { - return false; - } +ol.tilegrid.TileGrid.prototype.getOrigin = function(z) { + if (!goog.isNull(this.origin_)) { + return this.origin_; + } else { + goog.asserts.assert(!goog.isNull(this.origins_), + 'origins cannot be null if origin is null'); + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.origins_[z]; } - - return true; }; /** - * Default equality test for values. - * @param {*} a The first value. - * @param {*} b The second value. - * @return {boolean} Whether a and b reference the same object. + * Get the resolution for the given zoom level. + * @param {number} z Z. + * @return {number} Resolution. + * @api stable */ -goog.structs.Map.defaultEquals = function(a, b) { - return a === b; +ol.tilegrid.TileGrid.prototype.getResolution = function(z) { + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'given z is not in allowed range (%s <= %s <= %s)', + this.minZoom, z, this.maxZoom); + return this.resolutions_[z]; }; /** - * @return {boolean} Whether the map is empty. + * Get the list of resolutions for the tile grid. + * @return {Array.<number>} Resolutions. + * @api stable */ -goog.structs.Map.prototype.isEmpty = function() { - return this.count_ == 0; +ol.tilegrid.TileGrid.prototype.getResolutions = function() { + return this.resolutions_; }; /** - * Removes all key-value pairs from the map. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.TileRange} Tile range. */ -goog.structs.Map.prototype.clear = function() { - this.map_ = {}; - this.keys_.length = 0; - this.count_ = 0; - this.version_ = 0; +ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = + function(tileCoord, opt_tileRange, opt_extent) { + if (tileCoord[0] < this.maxZoom) { + var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); + return this.getTileRangeForExtentAndZ( + tileCoordExtent, tileCoord[0] + 1, opt_tileRange); + } else { + return null; + } }; /** - * Removes a key-value pair based on the key. This is O(logN) amortized due to - * updating the keys array whenever the count becomes half the size of the keys - * in the keys array. - * @param {*} key The key to remove. - * @return {boolean} Whether object was removed. + * @param {number} z Z. + * @param {ol.TileRange} tileRange Tile range. + * @param {ol.Extent=} opt_extent Temporary ol.Extent object. + * @return {ol.Extent} Extent. */ -goog.structs.Map.prototype.remove = function(key) { - if (goog.structs.Map.hasKey_(this.map_, key)) { - delete this.map_[key]; - this.count_--; - this.version_++; - - // clean up the keys array if the threshhold is hit - if (this.keys_.length > 2 * this.count_) { - this.cleanupKeysArray_(); - } - - return true; - } - return false; +ol.tilegrid.TileGrid.prototype.getTileRangeExtent = + function(z, tileRange, opt_extent) { + var origin = this.getOrigin(z); + var resolution = this.getResolution(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + var minX = origin[0] + tileRange.minX * tileSize[0] * resolution; + var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; + var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; + var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; /** - * Cleans up the temp keys array by removing entries that are no longer in the - * map. - * @private - */ -goog.structs.Map.prototype.cleanupKeysArray_ = function() { - if (this.count_ != this.keys_.length) { - // First remove keys that are no longer in the map. - var srcIndex = 0; - var destIndex = 0; - while (srcIndex < this.keys_.length) { - var key = this.keys_[srcIndex]; - if (goog.structs.Map.hasKey_(this.map_, key)) { - this.keys_[destIndex++] = key; - } - srcIndex++; - } - this.keys_.length = destIndex; - } + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = + function(extent, resolution, opt_tileRange) { + var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_; + this.getTileCoordForXYAndResolution_( + extent[0], extent[1], resolution, false, tileCoord); + var minX = tileCoord[1]; + var minY = tileCoord[2]; + this.getTileCoordForXYAndResolution_( + extent[2], extent[3], resolution, true, tileCoord); + return ol.TileRange.createOrUpdate( + minX, tileCoord[1], minY, tileCoord[2], opt_tileRange); +}; - if (this.count_ != this.keys_.length) { - // If the count still isn't correct, that means we have duplicates. This can - // happen when the same key is added and removed multiple times. Now we have - // to allocate one extra Object to remove the duplicates. This could have - // been done in the first pass, but in the common case, we can avoid - // allocating an extra object by only doing this when necessary. - var seen = {}; - var srcIndex = 0; - var destIndex = 0; - while (srcIndex < this.keys_.length) { - var key = this.keys_[srcIndex]; - if (!(goog.structs.Map.hasKey_(seen, key))) { - this.keys_[destIndex++] = key; - seen[key] = 1; - } - srcIndex++; - } - this.keys_.length = destIndex; - } + +/** + * @param {ol.Extent} extent Extent. + * @param {number} z Z. + * @param {ol.TileRange=} opt_tileRange Temporary tile range object. + * @return {ol.TileRange} Tile range. + */ +ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = + function(extent, z, opt_tileRange) { + var resolution = this.getResolution(z); + return this.getTileRangeForExtentAndResolution( + extent, resolution, opt_tileRange); }; /** - * Returns the value for the given key. If the key is not found and the default - * value is not given this will return {@code undefined}. - * @param {*} key The key to get the value for. - * @param {DEFAULT=} opt_val The value to return if no item is found for the - * given key, defaults to undefined. - * @return {V|DEFAULT} The value for the given key. - * @template DEFAULT + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {ol.Coordinate} Tile center. */ -goog.structs.Map.prototype.get = function(key, opt_val) { - if (goog.structs.Map.hasKey_(this.map_, key)) { - return this.map_[key]; - } - return opt_val; +ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + return [ + origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution, + origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution + ]; }; /** - * Adds a key-value pair to the map. - * @param {*} key The key. - * @param {V} value The value to add. - * @return {*} Some subclasses return a value. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Extent=} opt_extent Temporary extent object. + * @return {ol.Extent} Extent. */ -goog.structs.Map.prototype.set = function(key, value) { - if (!(goog.structs.Map.hasKey_(this.map_, key))) { - this.count_++; - this.keys_.push(key); - // Only change the version if we add a new key. - this.version_++; - } - this.map_[key] = value; +ol.tilegrid.TileGrid.prototype.getTileCoordExtent = + function(tileCoord, opt_extent) { + var origin = this.getOrigin(tileCoord[0]); + var resolution = this.getResolution(tileCoord[0]); + var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_); + var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution; + var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; + var maxY = minY + tileSize[1] * resolution; + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; /** - * Adds multiple key-value pairs from another goog.structs.Map or Object. - * @param {Object} map Object containing the data to add. + * Get the tile coordinate for the given map coordinate and resolution. This + * method considers that coordinates that intersect tile boundaries should be + * assigned the higher tile coordinate. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api */ -goog.structs.Map.prototype.addAll = function(map) { - var keys, values; - if (map instanceof goog.structs.Map) { - keys = map.getKeys(); - values = map.getValues(); +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = + function(coordinate, resolution, opt_tileCoord) { + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; + + +/** + * @param {number} x X. + * @param {number} y Y. + * @param {number} resolution Resolution. + * @param {boolean} reverseIntersectionPolicy Instead of letting edge + * intersections go to the higher tile coordinate, let edge intersections + * go to the lower tile coordinate. + * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @private + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( + x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) { + var z = this.getZForResolution(resolution); + var scale = resolution / this.getResolution(z); + var origin = this.getOrigin(z); + var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_); + + var adjustX = reverseIntersectionPolicy ? 0.5 : 0; + var adjustY = reverseIntersectionPolicy ? 0 : 0.5; + var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); + var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY); + var tileCoordX = scale * xFromOrigin / tileSize[0]; + var tileCoordY = scale * yFromOrigin / tileSize[1]; + + if (reverseIntersectionPolicy) { + tileCoordX = Math.ceil(tileCoordX) - 1; + tileCoordY = Math.ceil(tileCoordY) - 1; } else { - keys = goog.object.getKeys(map); - values = goog.object.getValues(map); + tileCoordX = Math.floor(tileCoordX); + tileCoordY = Math.floor(tileCoordY); } - // we could use goog.array.forEach here but I don't want to introduce that - // dependency just for this. - for (var i = 0; i < keys.length; i++) { - this.set(keys[i], values[i]); + + return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord); +}; + + +/** + * Get a tile coordinate given a map coordinate and zoom level. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} z Zoom level. + * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. + * @return {ol.TileCoord} Tile coordinate. + * @api + */ +ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = + function(coordinate, z, opt_tileCoord) { + var resolution = this.getResolution(z); + return this.getTileCoordForXYAndResolution_( + coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {number} Tile resolution. + */ +ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { + goog.asserts.assert( + this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom, + 'z of given tilecoord is not in allowed range (%s <= %s <= %s', + this.minZoom, tileCoord[0], this.maxZoom); + return this.resolutions_[tileCoord[0]]; +}; + + +/** + * Get the tile size for a zoom level. The type of the return value matches the + * `tileSize` or `tileSizes` that the tile grid was configured with. To always + * get an `ol.Size`, run the result through `ol.size.toSize()`. + * @param {number} z Z. + * @return {number|ol.Size} Tile size. + * @api stable + */ +ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { + if (!goog.isNull(this.tileSize_)) { + return this.tileSize_; + } else { + goog.asserts.assert(!goog.isNull(this.tileSizes_), + 'tileSizes cannot be null if tileSize is null'); + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.tileSizes_[z]; } }; /** - * Calls the given function on each entry in the map. - * @param {function(this:T, V, K, goog.structs.Map<K,V>)} f - * @param {T=} opt_obj The value of "this" inside f. - * @template T + * @param {number} z Zoom level. + * @return {ol.TileRange} Extent tile range for the specified zoom level. */ -goog.structs.Map.prototype.forEach = function(f, opt_obj) { - var keys = this.getKeys(); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var value = this.get(key); - f.call(opt_obj, value, key, this); +ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { + if (goog.isNull(this.fullTileRanges_)) { + return null; + } else { + goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom, + 'z is not in allowed range (%s <= %s <= %s', + this.minZoom, z, this.maxZoom); + return this.fullTileRanges_[z]; } }; /** - * Clones a map and returns a new map. - * @return {!goog.structs.Map} A new map with the same key-value pairs. + * @param {number} resolution Resolution. + * @return {number} Z. */ -goog.structs.Map.prototype.clone = function() { - return new goog.structs.Map(this); +ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) { + var z = ol.array.linearFindNearest(this.resolutions_, resolution, 0); + return goog.math.clamp(z, this.minZoom, this.maxZoom); }; /** - * Returns a new map in which all the keys and values are interchanged - * (keys become values and values become keys). If multiple keys map to the - * same value, the chosen transposed value is implementation-dependent. - * - * It acts very similarly to {goog.object.transpose(Object)}. - * - * @return {!goog.structs.Map} The transposed map. + * @param {!ol.Extent} extent Extent for this tile grid. + * @private */ -goog.structs.Map.prototype.transpose = function() { - var transposed = new goog.structs.Map(); - for (var i = 0; i < this.keys_.length; i++) { - var key = this.keys_[i]; - var value = this.map_[key]; - transposed.set(value, key); +ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) { + var length = this.resolutions_.length; + var fullTileRanges = new Array(length); + for (var z = this.minZoom; z < length; ++z) { + fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z); } - - return transposed; + this.fullTileRanges_ = fullTileRanges; }; /** - * @return {!Object} Object representation of the map. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection. */ -goog.structs.Map.prototype.toObject = function() { - this.cleanupKeysArray_(); - var obj = {}; - for (var i = 0; i < this.keys_.length; i++) { - var key = this.keys_[i]; - obj[key] = this.map_[key]; +ol.tilegrid.getForProjection = function(projection) { + var tileGrid = projection.getDefaultTileGrid(); + if (goog.isNull(tileGrid)) { + tileGrid = ol.tilegrid.createForProjection(projection); + projection.setDefaultTileGrid(tileGrid); } - return obj; + return tileGrid; }; /** - * Returns an iterator that iterates over the keys in the map. Removal of keys - * while iterating might have undesired side effects. - * @return {!goog.iter.Iterator} An iterator over the keys in the map. + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.TOP_LEFT). + * @return {ol.tilegrid.TileGrid} TileGrid instance. */ -goog.structs.Map.prototype.getKeyIterator = function() { - return this.__iterator__(true); +ol.tilegrid.createForExtent = + function(extent, opt_maxZoom, opt_tileSize, opt_corner) { + var corner = goog.isDef(opt_corner) ? + opt_corner : ol.extent.Corner.TOP_LEFT; + + var resolutions = ol.tilegrid.resolutionsFromExtent( + extent, opt_maxZoom, opt_tileSize); + + return new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getCorner(extent, corner), + resolutions: resolutions, + tileSize: opt_tileSize + }); }; /** - * Returns an iterator that iterates over the values in the map. Removal of - * keys while iterating might have undesired side effects. - * @return {!goog.iter.Iterator} An iterator over the values in the map. + * Creates a tile grid with a standard XYZ tiling scheme. + * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. + * @return {ol.tilegrid.TileGrid} Tile grid instance. + * @api */ -goog.structs.Map.prototype.getValueIterator = function() { - return this.__iterator__(false); +ol.tilegrid.createXYZ = function(opt_options) { + var options = /** @type {olx.tilegrid.TileGridOptions} */ ({}); + goog.object.extend(options, goog.isDef(opt_options) ? + opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({})); + if (!goog.isDef(options.extent)) { + options.extent = ol.proj.get('EPSG:3857').getExtent(); + } + options.resolutions = ol.tilegrid.resolutionsFromExtent( + options.extent, options.maxZoom, options.tileSize); + delete options.maxZoom; + + return new ol.tilegrid.TileGrid(options); }; /** - * Returns an iterator that iterates over the values or the keys in the map. - * This throws an exception if the map was mutated since the iterator was - * created. - * @param {boolean=} opt_keys True to iterate over the keys. False to iterate - * over the values. The default value is false. - * @return {!goog.iter.Iterator} An iterator over the values or keys in the map. + * Create a resolutions array from an extent. A zoom factor of 2 is assumed. + * @param {ol.Extent} extent Extent. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). + * @return {!Array.<number>} Resolutions array. */ -goog.structs.Map.prototype.__iterator__ = function(opt_keys) { - // Clean up keys to minimize the risk of iterating over dead keys. - this.cleanupKeysArray_(); +ol.tilegrid.resolutionsFromExtent = + function(extent, opt_maxZoom, opt_tileSize) { + var maxZoom = goog.isDef(opt_maxZoom) ? + opt_maxZoom : ol.DEFAULT_MAX_ZOOM; - var i = 0; - var keys = this.keys_; - var map = this.map_; - var version = this.version_; - var selfObj = this; + var height = ol.extent.getHeight(extent); + var width = ol.extent.getWidth(extent); - var newIter = new goog.iter.Iterator; - newIter.next = function() { - while (true) { - if (version != selfObj.version_) { - throw Error('The map has changed since the iterator was created'); - } - if (i >= keys.length) { - throw goog.iter.StopIteration; - } - var key = keys[i++]; - return opt_keys ? key : map[key]; - } - }; - return newIter; + var tileSize = ol.size.toSize(goog.isDef(opt_tileSize) ? + opt_tileSize : ol.DEFAULT_TILE_SIZE); + var maxResolution = Math.max( + width / tileSize[0], height / tileSize[1]); + + var length = maxZoom + 1; + var resolutions = new Array(length); + for (var z = 0; z < length; ++z) { + resolutions[z] = maxResolution / Math.pow(2, z); + } + return resolutions; }; /** - * Safe way to test for hasOwnProperty. It even allows testing for - * 'hasOwnProperty'. - * @param {Object} obj The object to test for presence of the given key. - * @param {*} key The key to check for. - * @return {boolean} Whether the object has the key. - * @private + * @param {ol.proj.ProjectionLike} projection Projection. + * @param {number=} opt_maxZoom Maximum zoom level (default is + * ol.DEFAULT_MAX_ZOOM). + * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {ol.extent.Corner=} opt_corner Extent corner (default is + * ol.extent.Corner.BOTTOM_LEFT). + * @return {ol.tilegrid.TileGrid} TileGrid instance. */ -goog.structs.Map.hasKey_ = function(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); +ol.tilegrid.createForProjection = + function(projection, opt_maxZoom, opt_tileSize, opt_corner) { + var extent = ol.tilegrid.extentFromProjection(projection); + return ol.tilegrid.createForExtent( + extent, opt_maxZoom, opt_tileSize, opt_corner); }; -// Copyright 2008 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Simple utilities for dealing with URI strings. - * - * This is intended to be a lightweight alternative to constructing goog.Uri - * objects. Whereas goog.Uri adds several kilobytes to the binary regardless - * of how much of its functionality you use, this is designed to be a set of - * mostly-independent utilities so that the compiler includes only what is - * necessary for the task. Estimated savings of porting is 5k pre-gzip and - * 1.5k post-gzip. To ensure the savings remain, future developers should - * avoid adding new functionality to existing functions, but instead create - * new ones and factor out shared code. - * - * Many of these utilities have limited functionality, tailored to common - * cases. The query parameter utilities assume that the parameter keys are - * already encoded, since most keys are compile-time alphanumeric strings. The - * query parameter mutation utilities also do not tolerate fragment identifiers. - * - * By design, these functions can be slower than goog.Uri equivalents. - * Repeated calls to some of functions may be quadratic in behavior for IE, - * although the effect is somewhat limited given the 2kb limit. - * - * One advantage of the limited functionality here is that this approach is - * less sensitive to differences in URI encodings than goog.Uri, since these - * functions modify the strings in place, rather than decoding and - * re-encoding. - * - * Uses features of RFC 3986 for parsing/formatting URIs: - * http://www.ietf.org/rfc/rfc3986.txt - * - * @author gboyer@google.com (Garrett Boyer) - The "lightened" design. + * Generate a tile grid extent from a projection. If the projection has an + * extent, it is used. If not, a global extent is assumed. + * @param {ol.proj.ProjectionLike} projection Projection. + * @return {ol.Extent} Extent. */ +ol.tilegrid.extentFromProjection = function(projection) { + projection = ol.proj.get(projection); + var extent = projection.getExtent(); + if (goog.isNull(extent)) { + var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / + projection.getMetersPerUnit(); + extent = ol.extent.createOrUpdate(-half, -half, half, half); + } + return extent; +}; -goog.provide('goog.uri.utils'); -goog.provide('goog.uri.utils.ComponentIndex'); -goog.provide('goog.uri.utils.QueryArray'); -goog.provide('goog.uri.utils.QueryValue'); -goog.provide('goog.uri.utils.StandardQueryParam'); +goog.provide('ol.source.Tile'); +goog.provide('ol.source.TileEvent'); +goog.provide('ol.source.TileOptions'); goog.require('goog.asserts'); -goog.require('goog.string'); -goog.require('goog.userAgent'); +goog.require('goog.events.Event'); +goog.require('ol.Attribution'); +goog.require('ol.Extent'); +goog.require('ol.TileCache'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.size'); +goog.require('ol.source.Source'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid.TileGrid'); /** - * Character codes inlined to avoid object allocations due to charCode. - * @enum {number} - * @private + * @typedef {{attributions: (Array.<ol.Attribution>|undefined), + * extent: (ol.Extent|undefined), + * logo: (string|olx.LogoOptions|undefined), + * opaque: (boolean|undefined), + * tilePixelRatio: (number|undefined), + * projection: ol.proj.ProjectionLike, + * state: (ol.source.State|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined), + * wrapX: (boolean|undefined)}} */ -goog.uri.utils.CharCode_ = { - AMPERSAND: 38, - EQUAL: 61, - HASH: 35, - QUESTION: 63 -}; +ol.source.TileOptions; + /** - * Builds a URI string from already-encoded parts. - * - * No encoding is performed. Any component may be omitted as either null or - * undefined. + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing images divided into a tile grid. * - * @param {?string=} opt_scheme The scheme such as 'http'. - * @param {?string=} opt_userInfo The user name before the '@'. - * @param {?string=} opt_domain The domain such as 'www.google.com', already - * URI-encoded. - * @param {(string|number|null)=} opt_port The port number. - * @param {?string=} opt_path The path, already URI-encoded. If it is not - * empty, it must begin with a slash. - * @param {?string=} opt_queryData The URI-encoded query data. - * @param {?string=} opt_fragment The URI-encoded fragment identifier. - * @return {string} The fully combined URI. + * @constructor + * @extends {ol.source.Source} + * @param {ol.source.TileOptions} options Tile source options. + * @api */ -goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo, - opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) { - var out = ''; - - if (opt_scheme) { - out += opt_scheme + ':'; - } - - if (opt_domain) { - out += '//'; +ol.source.Tile = function(options) { - if (opt_userInfo) { - out += opt_userInfo + '@'; - } + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state, + wrapX: options.wrapX + }); - out += opt_domain; + /** + * @private + * @type {boolean} + */ + this.opaque_ = goog.isDef(options.opaque) ? options.opaque : false; - if (opt_port) { - out += ':' + opt_port; - } - } + /** + * @private + * @type {number} + */ + this.tilePixelRatio_ = goog.isDef(options.tilePixelRatio) ? + options.tilePixelRatio : 1; - if (opt_path) { - out += opt_path; - } + /** + * @protected + * @type {ol.tilegrid.TileGrid} + */ + this.tileGrid = goog.isDef(options.tileGrid) ? options.tileGrid : null; - if (opt_queryData) { - out += '?' + opt_queryData; - } + /** + * @protected + * @type {ol.TileCache} + */ + this.tileCache = new ol.TileCache(); - if (opt_fragment) { - out += '#' + opt_fragment; - } + /** + * @protected + * @type {ol.Size} + */ + this.tmpSize = [0, 0]; - return out; }; +goog.inherits(ol.source.Tile, ol.source.Source); /** - * A regular expression for breaking a URI into its component parts. - * - * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B - * As the "first-match-wins" algorithm is identical to the "greedy" - * disambiguation method used by POSIX regular expressions, it is natural and - * commonplace to use a regular expression for parsing the potential five - * components of a URI reference. - * - * The following line is the regular expression for breaking-down a - * well-formed URI reference into its components. - * - * <pre> - * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? - * 12 3 4 5 6 7 8 9 - * </pre> - * - * The numbers in the second line above are only to assist readability; they - * indicate the reference points for each subexpression (i.e., each paired - * parenthesis). We refer to the value matched for subexpression <n> as $<n>. - * For example, matching the above expression to - * <pre> - * http://www.ics.uci.edu/pub/ietf/uri/#Related - * </pre> - * results in the following subexpression matches: - * <pre> - * $1 = http: - * $2 = http - * $3 = //www.ics.uci.edu - * $4 = www.ics.uci.edu - * $5 = /pub/ietf/uri/ - * $6 = <undefined> - * $7 = <undefined> - * $8 = #Related - * $9 = Related - * </pre> - * where <undefined> indicates that the component is not present, as is the - * case for the query component in the above example. Therefore, we can - * determine the value of the five components as - * <pre> - * scheme = $2 - * authority = $4 - * path = $5 - * query = $7 - * fragment = $9 - * </pre> - * - * The regular expression has been modified slightly to expose the - * userInfo, domain, and port separately from the authority. - * The modified version yields - * <pre> - * $1 = http scheme - * $2 = <undefined> userInfo -\ - * $3 = www.ics.uci.edu domain | authority - * $4 = <undefined> port -/ - * $5 = /pub/ietf/uri/ path - * $6 = <undefined> query without ? - * $7 = Related fragment without # - * </pre> - * @type {!RegExp} - * @private - */ -goog.uri.utils.splitRe_ = new RegExp( - '^' + - '(?:' + - '([^:/?#.]+)' + // scheme - ignore special characters - // used by other URL parts such as :, - // ?, /, #, and . - ':)?' + - '(?://' + - '(?:([^/?#]*)@)?' + // userInfo - '([^/#?]*?)' + // domain - '(?::([0-9]+))?' + // port - '(?=[/#?]|$)' + // authority-terminating character - ')?' + - '([^?#]+)?' + // path - '(?:\\?([^#]*))?' + // query - '(?:#(.*))?' + // fragment - '$'); - - -/** - * The index of each URI component in the return value of goog.uri.utils.split. - * @enum {number} + * @return {boolean} Can expire cache. */ -goog.uri.utils.ComponentIndex = { - SCHEME: 1, - USER_INFO: 2, - DOMAIN: 3, - PORT: 4, - PATH: 5, - QUERY_DATA: 6, - FRAGMENT: 7 +ol.source.Tile.prototype.canExpireCache = function() { + return this.tileCache.canExpireCache(); }; /** - * Splits a URI into its component parts. - * - * Each component can be accessed via the component indices; for example: - * <pre> - * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA]; - * </pre> - * - * @param {string} uri The URI string to examine. - * @return {!Array<string|undefined>} Each component still URI-encoded. - * Each component that is present will contain the encoded value, whereas - * components that are not present will be undefined or empty, depending - * on the browser's regular expression implementation. Never null, since - * arbitrary strings may still look like path names. + * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. */ -goog.uri.utils.split = function(uri) { - goog.uri.utils.phishingProtection_(); - - // See @return comment -- never null. - return /** @type {!Array<string|undefined>} */ ( - uri.match(goog.uri.utils.splitRe_)); +ol.source.Tile.prototype.expireCache = function(usedTiles) { + this.tileCache.expireCache(usedTiles); }; /** - * Safari has a nasty bug where if you have an http URL with a username, e.g., - * http://evil.com%2F@google.com/ - * Safari will report that window.location.href is - * http://evil.com/google.com/ - * so that anyone who tries to parse the domain of that URL will get - * the wrong domain. We've seen exploits where people use this to trick - * Safari into loading resources from evil domains. - * - * To work around this, we run a little "Safari phishing check", and throw - * an exception if we see this happening. - * - * There is no convenient place to put this check. We apply it to - * anyone doing URI parsing on Webkit. We're not happy about this, but - * it fixes the problem. - * - * This should be removed once Safari fixes their bug. - * - * Exploit reported by Masato Kinugawa. - * - * @type {boolean} - * @private - */ -goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT; - - -/** - * Check to see if the user is being phished. - * @private + * @param {number} z Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @param {function(ol.Tile):(boolean|undefined)} callback Called with each + * loaded tile. If the callback returns `false`, the tile will not be + * considered loaded. + * @return {boolean} The tile range is fully covered with loaded tiles. */ -goog.uri.utils.phishingProtection_ = function() { - if (goog.uri.utils.needsPhishingProtection_) { - // Turn protection off, so that we don't recurse. - goog.uri.utils.needsPhishingProtection_ = false; - - // Use quoted access, just in case the user isn't using location externs. - var location = goog.global['location']; - if (location) { - var href = location['href']; - if (href) { - var domain = goog.uri.utils.getDomain(href); - if (domain && domain != location['hostname']) { - // Phishing attack - goog.uri.utils.needsPhishingProtection_ = true; - throw Error(); +ol.source.Tile.prototype.forEachLoadedTile = function(z, tileRange, callback) { + var covered = true; + var tile, tileCoordKey, loaded; + for (var x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (var y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoordKey = this.getKeyZXY(z, x, y); + loaded = false; + if (this.tileCache.containsKey(tileCoordKey)) { + tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + loaded = tile.getState() === ol.TileState.LOADED; + if (loaded) { + loaded = (callback(tile) !== false); } } + if (!loaded) { + covered = false; + } } } + return covered; }; /** - * @param {?string} uri A possibly null string. - * @param {boolean=} opt_preserveReserved If true, percent-encoding of RFC-3986 - * reserved characters will not be removed. - * @return {?string} The string URI-decoded, or null if uri is null. - * @private + * @return {number} Gutter. */ -goog.uri.utils.decodeIfPossible_ = function(uri, opt_preserveReserved) { - if (!uri) { - return uri; - } - - return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri); +ol.source.Tile.prototype.getGutter = function() { + return 0; }; /** - * Gets a URI component by index. - * - * It is preferred to use the getPathEncoded() variety of functions ahead, - * since they are more readable. - * - * @param {goog.uri.utils.ComponentIndex} componentIndex The component index. - * @param {string} uri The URI to examine. - * @return {?string} The still-encoded component, or null if the component - * is not present. - * @private + * @param {number} z Z. + * @param {number} x X. + * @param {number} y Y. + * @return {string} Key. + * @protected */ -goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) { - // Convert undefined, null, and empty string into null. - return goog.uri.utils.split(uri)[componentIndex] || null; -}; +ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY; /** - * @param {string} uri The URI to examine. - * @return {?string} The protocol or scheme, or null if none. Does not - * include trailing colons or slashes. + * @return {boolean} Opaque. */ -goog.uri.utils.getScheme = function(uri) { - return goog.uri.utils.getComponentByIndex_( - goog.uri.utils.ComponentIndex.SCHEME, uri); +ol.source.Tile.prototype.getOpaque = function() { + return this.opaque_; }; /** - * Gets the effective scheme for the URL. If the URL is relative then the - * scheme is derived from the page's location. - * @param {string} uri The URI to examine. - * @return {string} The protocol or scheme, always lower case. + * @inheritDoc */ -goog.uri.utils.getEffectiveScheme = function(uri) { - var scheme = goog.uri.utils.getScheme(uri); - if (!scheme && self.location) { - var protocol = self.location.protocol; - scheme = protocol.substr(0, protocol.length - 1); - } - // NOTE: When called from a web worker in Firefox 3.5, location maybe null. - // All other browsers with web workers support self.location from the worker. - return scheme ? scheme.toLowerCase() : ''; +ol.source.Tile.prototype.getResolutions = function() { + return this.tileGrid.getResolutions(); }; /** - * @param {string} uri The URI to examine. - * @return {?string} The user name still encoded, or null if none. + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection=} opt_projection Projection. + * @return {!ol.Tile} Tile. */ -goog.uri.utils.getUserInfoEncoded = function(uri) { - return goog.uri.utils.getComponentByIndex_( - goog.uri.utils.ComponentIndex.USER_INFO, uri); -}; +ol.source.Tile.prototype.getTile = goog.abstractMethod; /** - * @param {string} uri The URI to examine. - * @return {?string} The decoded user info, or null if none. + * Return the tile grid of the tile source. + * @return {ol.tilegrid.TileGrid} Tile grid. + * @api stable */ -goog.uri.utils.getUserInfo = function(uri) { - return goog.uri.utils.decodeIfPossible_( - goog.uri.utils.getUserInfoEncoded(uri)); +ol.source.Tile.prototype.getTileGrid = function() { + return this.tileGrid; }; /** - * @param {string} uri The URI to examine. - * @return {?string} The domain name still encoded, or null if none. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.tilegrid.TileGrid} Tile grid. */ -goog.uri.utils.getDomainEncoded = function(uri) { - return goog.uri.utils.getComponentByIndex_( - goog.uri.utils.ComponentIndex.DOMAIN, uri); +ol.source.Tile.prototype.getTileGridForProjection = function(projection) { + if (goog.isNull(this.tileGrid)) { + return ol.tilegrid.getForProjection(projection); + } else { + return this.tileGrid; + } }; /** - * @param {string} uri The URI to examine. - * @return {?string} The decoded domain, or null if none. + * @param {number} z Z. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.Size} Tile size. */ -goog.uri.utils.getDomain = function(uri) { - return goog.uri.utils.decodeIfPossible_( - goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */); +ol.source.Tile.prototype.getTilePixelSize = + function(z, pixelRatio, projection) { + var tileGrid = this.getTileGridForProjection(projection); + return ol.size.scale(ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize), + this.tilePixelRatio_, this.tmpSize); }; /** - * @param {string} uri The URI to examine. - * @return {?number} The port number, or null if none. + * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate + * is outside the resolution and extent range of the tile grid, `null` will be + * returned. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection=} opt_projection Projection. + * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or + * null if no tile URL should be created for the passed `tileCoord`. */ -goog.uri.utils.getPort = function(uri) { - // Coerce to a number. If the result of getComponentByIndex_ is null or - // non-numeric, the number coersion yields NaN. This will then return - // null for all non-numeric cases (though also zero, which isn't a relevant - // port number). - return Number(goog.uri.utils.getComponentByIndex_( - goog.uri.utils.ComponentIndex.PORT, uri)) || null; +ol.source.Tile.prototype.getTileCoordForTileUrlFunction = + function(tileCoord, opt_projection) { + var projection = goog.isDef(opt_projection) ? + opt_projection : this.getProjection(); + var tileGrid = this.getTileGridForProjection(projection); + goog.asserts.assert(!goog.isNull(tileGrid), 'tile grid needed'); + if (this.getWrapX() && projection.isGlobal()) { + tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); + } + return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null; }; /** - * @param {string} uri The URI to examine. - * @return {?string} The path still encoded, or null if none. Includes the - * leading slash, if any. + * Marks a tile coord as being used, without triggering a load. + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. */ -goog.uri.utils.getPathEncoded = function(uri) { - return goog.uri.utils.getComponentByIndex_( - goog.uri.utils.ComponentIndex.PATH, uri); -}; - +ol.source.Tile.prototype.useTile = goog.nullFunction; -/** - * @param {string} uri The URI to examine. - * @return {?string} The decoded path, or null if none. Includes the leading - * slash, if any. - */ -goog.uri.utils.getPath = function(uri) { - return goog.uri.utils.decodeIfPossible_( - goog.uri.utils.getPathEncoded(uri), true /* opt_preserveReserved */); -}; /** - * @param {string} uri The URI to examine. - * @return {?string} The query data still encoded, or null if none. Does not - * include the question mark itself. + * @classdesc + * Events emitted by {@link ol.source.Tile} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.source.TileEvent} + * @param {string} type Type. + * @param {ol.Tile} tile The tile. */ -goog.uri.utils.getQueryData = function(uri) { - return goog.uri.utils.getComponentByIndex_( - goog.uri.utils.ComponentIndex.QUERY_DATA, uri); -}; +ol.source.TileEvent = function(type, tile) { + goog.base(this, type); + + /** + * The tile related to the event. + * @type {ol.Tile} + * @api + */ + this.tile = tile; -/** - * @param {string} uri The URI to examine. - * @return {?string} The fragment identifier, or null if none. Does not - * include the hash mark itself. - */ -goog.uri.utils.getFragmentEncoded = function(uri) { - // The hash mark may not appear in any other part of the URL. - var hashIndex = uri.indexOf('#'); - return hashIndex < 0 ? null : uri.substr(hashIndex + 1); }; +goog.inherits(ol.source.TileEvent, goog.events.Event); /** - * @param {string} uri The URI to examine. - * @param {?string} fragment The encoded fragment identifier, or null if none. - * Does not include the hash mark itself. - * @return {string} The URI with the fragment set. + * @enum {string} */ -goog.uri.utils.setFragmentEncoded = function(uri, fragment) { - return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : ''); -}; +ol.source.TileEventType = { + /** + * Triggered when a tile starts loading. + * @event ol.source.TileEvent#tileloadstart + * @api stable + */ + TILELOADSTART: 'tileloadstart', -/** - * @param {string} uri The URI to examine. - * @return {?string} The decoded fragment identifier, or null if none. Does - * not include the hash mark. - */ -goog.uri.utils.getFragment = function(uri) { - return goog.uri.utils.decodeIfPossible_( - goog.uri.utils.getFragmentEncoded(uri)); -}; + /** + * Triggered when a tile finishes loading. + * @event ol.source.TileEvent#tileloadend + * @api stable + */ + TILELOADEND: 'tileloadend', + /** + * Triggered if tile loading results in an error. + * @event ol.source.TileEvent#tileloaderror + * @api stable + */ + TILELOADERROR: 'tileloaderror' -/** - * Extracts everything up to the port of the URI. - * @param {string} uri The URI string. - * @return {string} Everything up to and including the port. - */ -goog.uri.utils.getHost = function(uri) { - var pieces = goog.uri.utils.split(uri); - return goog.uri.utils.buildFromEncodedParts( - pieces[goog.uri.utils.ComponentIndex.SCHEME], - pieces[goog.uri.utils.ComponentIndex.USER_INFO], - pieces[goog.uri.utils.ComponentIndex.DOMAIN], - pieces[goog.uri.utils.ComponentIndex.PORT]); }; +// FIXME handle date line wrap -/** - * Extracts the path of the URL and everything after. - * @param {string} uri The URI string. - * @return {string} The URI, starting at the path and including the query - * parameters and fragment identifier. - */ -goog.uri.utils.getPathAndAfter = function(uri) { - var pieces = goog.uri.utils.split(uri); - return goog.uri.utils.buildFromEncodedParts(null, null, null, null, - pieces[goog.uri.utils.ComponentIndex.PATH], - pieces[goog.uri.utils.ComponentIndex.QUERY_DATA], - pieces[goog.uri.utils.ComponentIndex.FRAGMENT]); -}; +goog.provide('ol.control.Attribution'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('ol.Attribution'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.source.Tile'); -/** - * Gets the URI with the fragment identifier removed. - * @param {string} uri The URI to examine. - * @return {string} Everything preceding the hash mark. - */ -goog.uri.utils.removeFragment = function(uri) { - // The hash mark may not appear in any other part of the URL. - var hashIndex = uri.indexOf('#'); - return hashIndex < 0 ? uri : uri.substr(0, hashIndex); -}; /** - * Ensures that two URI's have the exact same domain, scheme, and port. - * - * Unlike the version in goog.Uri, this checks protocol, and therefore is - * suitable for checking against the browser's same-origin policy. + * @classdesc + * Control to show all the attributions associated with the layer sources + * in the map. This control is one of the default controls included in maps. + * By default it will show in the bottom right portion of the map, but this can + * be changed by using a css selector for `.ol-attribution`. * - * @param {string} uri1 The first URI. - * @param {string} uri2 The second URI. - * @return {boolean} Whether they have the same scheme, domain and port. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.AttributionOptions=} opt_options Attribution options. + * @api stable */ -goog.uri.utils.haveSameDomain = function(uri1, uri2) { - var pieces1 = goog.uri.utils.split(uri1); - var pieces2 = goog.uri.utils.split(uri2); - return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == - pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && - pieces1[goog.uri.utils.ComponentIndex.SCHEME] == - pieces2[goog.uri.utils.ComponentIndex.SCHEME] && - pieces1[goog.uri.utils.ComponentIndex.PORT] == - pieces2[goog.uri.utils.ComponentIndex.PORT]; -}; +ol.control.Attribution = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * Asserts that there are no fragment or query identifiers, only in uncompiled - * mode. - * @param {string} uri The URI to examine. - * @private - */ -goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) { - // NOTE: would use goog.asserts here, but jscompiler doesn't know that - // indexOf has no side effects. - if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) { - throw Error('goog.uri.utils: Fragment or query identifiers are not ' + - 'supported: [' + uri + ']'); - } -}; + /** + * @private + * @type {Element} + */ + this.ulElement_ = goog.dom.createElement(goog.dom.TagName.UL); + /** + * @private + * @type {Element} + */ + this.logoLi_ = goog.dom.createElement(goog.dom.TagName.LI); -/** - * Supported query parameter values by the parameter serializing utilities. - * - * If a value is null or undefined, the key-value pair is skipped, as an easy - * way to omit parameters conditionally. Non-array parameters are converted - * to a string and URI encoded. Array values are expanded into multiple - * &key=value pairs, with each element stringized and URI-encoded. - * - * @typedef {*} - */ -goog.uri.utils.QueryValue; + goog.dom.appendChild(this.ulElement_, this.logoLi_); + goog.style.setElementShown(this.logoLi_, false); + /** + * @private + * @type {boolean} + */ + this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true; -/** - * An array representing a set of query parameters with alternating keys - * and values. - * - * Keys are assumed to be URI encoded already and live at even indices. See - * goog.uri.utils.QueryValue for details on how parameter values are encoded. - * - * Example: - * <pre> - * var data = [ - * // Simple param: ?name=BobBarker - * 'name', 'BobBarker', - * // Conditional param -- may be omitted entirely. - * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null, - * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null - * 'house', ['LosAngeles', 'NewYork', null] - * ]; - * </pre> - * - * @typedef {!Array<string|goog.uri.utils.QueryValue>} - */ -goog.uri.utils.QueryArray; + /** + * @private + * @type {boolean} + */ + this.collapsible_ = goog.isDef(options.collapsible) ? + options.collapsible : true; + if (!this.collapsible_) { + this.collapsed_ = false; + } -/** - * Appends a URI and query data in a string buffer with special preconditions. - * - * Internal implementation utility, performing very few object allocations. - * - * @param {!Array<string|undefined>} buffer A string buffer. The first element - * must be the base URI, and may have a fragment identifier. If the array - * contains more than one element, the second element must be an ampersand, - * and may be overwritten, depending on the base URI. Undefined elements - * are treated as empty-string. - * @return {string} The concatenated URI and query data. - * @private + var className = goog.isDef(options.className) ? + options.className : 'ol-attribution'; + + var tipLabel = goog.isDef(options.tipLabel) ? + options.tipLabel : 'Attributions'; + + var collapseLabel = goog.isDef(options.collapseLabel) ? + options.collapseLabel : '\u00BB'; + + /** + * @private + * @type {Node} + */ + this.collapseLabel_ = /** @type {Node} */ (goog.isString(collapseLabel) ? + goog.dom.createDom(goog.dom.TagName.SPAN, {}, collapseLabel) : + collapseLabel); + + var label = goog.isDef(options.label) ? options.label : 'i'; + + /** + * @private + * @type {Node} + */ + this.label_ = /** @type {Node} */ (goog.isString(label) ? + goog.dom.createDom(goog.dom.TagName.SPAN, {}, label) : + label); + + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'type': 'button', + 'title': tipLabel + }, activeLabel); + + goog.events.listen(button, goog.events.EventType.CLICK, + this.handleClick_, false, this); + + goog.events.listen(button, [ + goog.events.EventType.MOUSEOUT, + goog.events.EventType.FOCUSOUT + ], function() { + this.blur(); + }, false); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = goog.dom.createDom(goog.dom.TagName.DIV, + cssClasses, this.ulElement_, button); + + var render = goog.isDef(options.render) ? + options.render : ol.control.Attribution.render; + + goog.base(this, { + element: element, + render: render, + target: options.target + }); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {Object.<string, Element>} + */ + this.attributionElements_ = {}; + + /** + * @private + * @type {Object.<string, boolean>} + */ + this.attributionElementRenderedVisible_ = {}; + + /** + * @private + * @type {Object.<string, Element>} + */ + this.logoElements_ = {}; + +}; +goog.inherits(ol.control.Attribution, ol.control.Control); + + +/** + * @param {?olx.FrameState} frameState Frame state. + * @return {Array.<Object.<string, ol.Attribution>>} Attributions. */ -goog.uri.utils.appendQueryData_ = function(buffer) { - if (buffer[1]) { - // At least one query parameter was added. We need to check the - // punctuation mark, which is currently an ampersand, and also make sure - // there aren't any interfering fragment identifiers. - var baseUri = /** @type {string} */ (buffer[0]); - var hashIndex = baseUri.indexOf('#'); - if (hashIndex >= 0) { - // Move the fragment off the base part of the URI into the end. - buffer.push(baseUri.substr(hashIndex)); - buffer[0] = baseUri = baseUri.substr(0, hashIndex); +ol.control.Attribution.prototype.getSourceAttributions = function(frameState) { + var i, ii, j, jj, tileRanges, source, sourceAttribution, + sourceAttributionKey, sourceAttributions, sourceKey; + var intersectsTileRange; + var layerStatesArray = frameState.layerStatesArray; + /** @type {Object.<string, ol.Attribution>} */ + var attributions = goog.object.clone(frameState.attributions); + /** @type {Object.<string, ol.Attribution>} */ + var hiddenAttributions = {}; + var projection = frameState.viewState.projection; + goog.asserts.assert(!goog.isNull(projection), 'projection cannot be null'); + for (i = 0, ii = layerStatesArray.length; i < ii; i++) { + source = layerStatesArray[i].layer.getSource(); + if (goog.isNull(source)) { + continue; } - var questionIndex = baseUri.indexOf('?'); - if (questionIndex < 0) { - // No question mark, so we need a question mark instead of an ampersand. - buffer[1] = '?'; - } else if (questionIndex == baseUri.length - 1) { - // Question mark is the very last character of the existing URI, so don't - // append an additional delimiter. - buffer[1] = undefined; + sourceKey = goog.getUid(source).toString(); + sourceAttributions = source.getAttributions(); + if (goog.isNull(sourceAttributions)) { + continue; + } + for (j = 0, jj = sourceAttributions.length; j < jj; j++) { + sourceAttribution = sourceAttributions[j]; + sourceAttributionKey = goog.getUid(sourceAttribution).toString(); + if (sourceAttributionKey in attributions) { + continue; + } + tileRanges = frameState.usedTiles[sourceKey]; + if (goog.isDef(tileRanges)) { + goog.asserts.assertInstanceof(source, ol.source.Tile, + 'source should be an ol.source.Tile'); + var tileGrid = source.getTileGridForProjection(projection); + goog.asserts.assert(!goog.isNull(tileGrid), 'tileGrid cannot be null'); + intersectsTileRange = sourceAttribution.intersectsAnyTileRange( + tileRanges, tileGrid, projection); + } else { + intersectsTileRange = false; + } + if (intersectsTileRange) { + if (sourceAttributionKey in hiddenAttributions) { + delete hiddenAttributions[sourceAttributionKey]; + } + attributions[sourceAttributionKey] = sourceAttribution; + } else { + hiddenAttributions[sourceAttributionKey] = sourceAttribution; + } } } - - return buffer.join(''); + return [attributions, hiddenAttributions]; }; /** - * Appends key=value pairs to an array, supporting multi-valued objects. - * @param {string} key The key prefix. - * @param {goog.uri.utils.QueryValue} value The value to serialize. - * @param {!Array<string>} pairs The array to which the 'key=value' strings - * should be appended. - * @private + * Update the attribution element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Attribution} + * @api */ -goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) { - if (goog.isArray(value)) { - // Convince the compiler it's an array. - goog.asserts.assertArray(value); - for (var j = 0; j < value.length; j++) { - // Convert to string explicitly, to short circuit the null and array - // logic in this function -- this ensures that null and undefined get - // written as literal 'null' and 'undefined', and arrays don't get - // expanded out but instead encoded in the default way. - goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs); - } - } else if (value != null) { - // Skip a top-level null or undefined entirely. - pairs.push('&', key, - // Check for empty string. Zero gets encoded into the url as literal - // strings. For empty string, skip the equal sign, to be consistent - // with UriBuilder.java. - value === '' ? '' : '=', - goog.string.urlEncode(value)); - } +ol.control.Attribution.render = function(mapEvent) { + this.updateElement_(mapEvent.frameState); }; /** - * Builds a buffer of query data from a sequence of alternating keys and values. - * - * @param {!Array<string|undefined>} buffer A string buffer to append to. The - * first element appended will be an '&', and may be replaced by the caller. - * @param {!goog.uri.utils.QueryArray|!Arguments} keysAndValues An array with - * alternating keys and values -- see the typedef. - * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. - * @return {!Array<string|undefined>} The buffer argument. * @private + * @param {?olx.FrameState} frameState Frame state. */ -goog.uri.utils.buildQueryDataBuffer_ = function( - buffer, keysAndValues, opt_startIndex) { - goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0), - 0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.'); +ol.control.Attribution.prototype.updateElement_ = function(frameState) { - for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) { - goog.uri.utils.appendKeyValuePairs_( - keysAndValues[i], keysAndValues[i + 1], buffer); + if (goog.isNull(frameState)) { + if (this.renderedVisible_) { + goog.style.setElementShown(this.element, false); + this.renderedVisible_ = false; + } + return; } - return buffer; -}; + var attributions = this.getSourceAttributions(frameState); + /** @type {Object.<string, ol.Attribution>} */ + var visibleAttributions = attributions[0]; + /** @type {Object.<string, ol.Attribution>} */ + var hiddenAttributions = attributions[1]; + var attributionElement, attributionKey; + for (attributionKey in this.attributionElements_) { + if (attributionKey in visibleAttributions) { + if (!this.attributionElementRenderedVisible_[attributionKey]) { + goog.style.setElementShown( + this.attributionElements_[attributionKey], true); + this.attributionElementRenderedVisible_[attributionKey] = true; + } + delete visibleAttributions[attributionKey]; + } + else if (attributionKey in hiddenAttributions) { + if (this.attributionElementRenderedVisible_[attributionKey]) { + goog.style.setElementShown( + this.attributionElements_[attributionKey], false); + delete this.attributionElementRenderedVisible_[attributionKey]; + } + delete hiddenAttributions[attributionKey]; + } + else { + goog.dom.removeNode(this.attributionElements_[attributionKey]); + delete this.attributionElements_[attributionKey]; + delete this.attributionElementRenderedVisible_[attributionKey]; + } + } + for (attributionKey in visibleAttributions) { + attributionElement = goog.dom.createElement(goog.dom.TagName.LI); + attributionElement.innerHTML = + visibleAttributions[attributionKey].getHTML(); + goog.dom.appendChild(this.ulElement_, attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + this.attributionElementRenderedVisible_[attributionKey] = true; + } + for (attributionKey in hiddenAttributions) { + attributionElement = goog.dom.createElement(goog.dom.TagName.LI); + attributionElement.innerHTML = + hiddenAttributions[attributionKey].getHTML(); + goog.style.setElementShown(attributionElement, false); + goog.dom.appendChild(this.ulElement_, attributionElement); + this.attributionElements_[attributionKey] = attributionElement; + } + + var renderVisible = + !goog.object.isEmpty(this.attributionElementRenderedVisible_) || + !goog.object.isEmpty(frameState.logos); + if (this.renderedVisible_ != renderVisible) { + goog.style.setElementShown(this.element, renderVisible); + this.renderedVisible_ = renderVisible; + } + if (renderVisible && + goog.object.isEmpty(this.attributionElementRenderedVisible_)) { + goog.dom.classlist.add(this.element, 'ol-logo-only'); + } else { + goog.dom.classlist.remove(this.element, 'ol-logo-only'); + } + + this.insertLogos_(frameState); -/** - * Builds a query data string from a sequence of alternating keys and values. - * Currently generates "&key&" for empty args. - * - * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and - * values. See the typedef. - * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. - * @return {string} The encoded query string, in the form 'a=1&b=2'. - */ -goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) { - var buffer = goog.uri.utils.buildQueryDataBuffer_( - [], keysAndValues, opt_startIndex); - buffer[0] = ''; // Remove the leading ampersand. - return buffer.join(''); }; /** - * Builds a buffer of query data from a map. - * - * @param {!Array<string|undefined>} buffer A string buffer to append to. The - * first element appended will be an '&', and may be replaced by the caller. - * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys - * are URI-encoded parameter keys, and the values conform to the contract - * specified in the goog.uri.utils.QueryValue typedef. - * @return {!Array<string|undefined>} The buffer argument. + * @param {?olx.FrameState} frameState Frame state. * @private */ -goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) { - for (var key in map) { - goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer); +ol.control.Attribution.prototype.insertLogos_ = function(frameState) { + + var logo; + var logos = frameState.logos; + var logoElements = this.logoElements_; + + for (logo in logoElements) { + if (!(logo in logos)) { + goog.dom.removeNode(logoElements[logo]); + delete logoElements[logo]; + } } - return buffer; + var image, logoElement, logoKey; + for (logoKey in logos) { + if (!(logoKey in logoElements)) { + image = new Image(); + image.src = logoKey; + var logoValue = logos[logoKey]; + if (logoValue === '') { + logoElement = image; + } else { + logoElement = goog.dom.createDom(goog.dom.TagName.A, { + 'href': logoValue + }); + logoElement.appendChild(image); + } + goog.dom.appendChild(this.logoLi_, logoElement); + logoElements[logoKey] = logoElement; + } + } + + goog.style.setElementShown(this.logoLi_, !goog.object.isEmpty(logos)); + }; /** - * Builds a query data string from a map. - * Currently generates "&key&" for empty args. - * - * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys - * are URI-encoded parameter keys, and the values are arbitrary types - * or arrays. Keys with a null value are dropped. - * @return {string} The encoded query string, in the form 'a=1&b=2'. + * @param {goog.events.BrowserEvent} event The event to handle + * @private */ -goog.uri.utils.buildQueryDataFromMap = function(map) { - var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map); - buffer[0] = ''; - return buffer.join(''); +ol.control.Attribution.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); }; /** - * Appends URI parameters to an existing URI. - * - * The variable arguments may contain alternating keys and values. Keys are - * assumed to be already URI encoded. The values should not be URI-encoded, - * and will instead be encoded by this function. - * <pre> - * appendParams('http://www.foo.com?existing=true', - * 'key1', 'value1', - * 'key2', 'value?willBeEncoded', - * 'key3', ['valueA', 'valueB', 'valueC'], - * 'key4', null); - * result: 'http://www.foo.com?existing=true&' + - * 'key1=value1&' + - * 'key2=value%3FwillBeEncoded&' + - * 'key3=valueA&key3=valueB&key3=valueC' - * </pre> - * - * A single call to this function will not exhibit quadratic behavior in IE, - * whereas multiple repeated calls may, although the effect is limited by - * fact that URL's generally can't exceed 2kb. - * - * @param {string} uri The original URI, which may already have query data. - * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args - * An array or argument list conforming to goog.uri.utils.QueryArray. - * @return {string} The URI with all query parameters added. + * @private */ -goog.uri.utils.appendParams = function(uri, var_args) { - return goog.uri.utils.appendQueryData_( - arguments.length == 2 ? - goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) : - goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1)); +ol.control.Attribution.prototype.handleToggle_ = function() { + goog.dom.classlist.toggle(this.element, 'ol-collapsed'); + if (this.collapsed_) { + goog.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + goog.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; }; /** - * Appends query parameters from a map. - * - * @param {string} uri The original URI, which may already have query data. - * @param {!Object<goog.uri.utils.QueryValue>} map An object where keys are - * URI-encoded parameter keys, and the values are arbitrary types or arrays. - * Keys with a null value are dropped. - * @return {string} The new parameters. + * Return `true` if the attribution is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable */ -goog.uri.utils.appendParamsFromMap = function(uri, map) { - return goog.uri.utils.appendQueryData_( - goog.uri.utils.buildQueryDataBufferFromMap_([uri], map)); +ol.control.Attribution.prototype.getCollapsible = function() { + return this.collapsible_; }; /** - * Appends a single URI parameter. - * - * Repeated calls to this can exhibit quadratic behavior in IE6 due to the - * way string append works, though it should be limited given the 2kb limit. - * - * @param {string} uri The original URI, which may already have query data. - * @param {string} key The key, which must already be URI encoded. - * @param {*=} opt_value The value, which will be stringized and encoded - * (assumed not already to be encoded). If omitted, undefined, or null, the - * key will be added as a valueless parameter. - * @return {string} The URI with the query parameter added. + * Set whether the attribution should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable */ -goog.uri.utils.appendParam = function(uri, key, opt_value) { - var paramArr = [uri, '&', key]; - if (goog.isDefAndNotNull(opt_value)) { - paramArr.push('=', goog.string.urlEncode(opt_value)); +ol.control.Attribution.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + goog.dom.classlist.toggle(this.element, 'ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); } - return goog.uri.utils.appendQueryData_(paramArr); }; /** - * Finds the next instance of a query parameter with the specified name. - * - * Does not instantiate any objects. - * - * @param {string} uri The URI to search. May contain a fragment identifier - * if opt_hashIndex is specified. - * @param {number} startIndex The index to begin searching for the key at. A - * match may be found even if this is one character after the ampersand. - * @param {string} keyEncoded The URI-encoded key. - * @param {number} hashOrEndIndex Index to stop looking at. If a hash - * mark is present, it should be its index, otherwise it should be the - * length of the string. - * @return {number} The position of the first character in the key's name, - * immediately after either a question mark or a dot. - * @private + * Collapse or expand the attribution according to the passed parameter. Will + * not do anything if the attribution isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable */ -goog.uri.utils.findParam_ = function( - uri, startIndex, keyEncoded, hashOrEndIndex) { - var index = startIndex; - var keyLength = keyEncoded.length; - - // Search for the key itself and post-filter for surronuding punctuation, - // rather than expensively building a regexp. - while ((index = uri.indexOf(keyEncoded, index)) >= 0 && - index < hashOrEndIndex) { - var precedingChar = uri.charCodeAt(index - 1); - // Ensure that the preceding character is '&' or '?'. - if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND || - precedingChar == goog.uri.utils.CharCode_.QUESTION) { - // Ensure the following character is '&', '=', '#', or NaN - // (end of string). - var followingChar = uri.charCodeAt(index + keyLength); - if (!followingChar || - followingChar == goog.uri.utils.CharCode_.EQUAL || - followingChar == goog.uri.utils.CharCode_.AMPERSAND || - followingChar == goog.uri.utils.CharCode_.HASH) { - return index; - } - } - index += keyLength + 1; +ol.control.Attribution.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; } - - return -1; + this.handleToggle_(); }; /** - * Regular expression for finding a hash mark or end of string. - * @type {RegExp} - * @private + * Return `true` when the attribution is currently collapsed or `false` + * otherwise. + * @return {boolean} True if the widget is collapsed. + * @api stable */ -goog.uri.utils.hashOrEndRe_ = /#|$/; +ol.control.Attribution.prototype.getCollapsed = function() { + return this.collapsed_; +}; +goog.provide('ol.control.Rotate'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.math'); +goog.require('ol.animation'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); -/** - * Determines if the URI contains a specific key. - * - * Performs no object instantiations. - * - * @param {string} uri The URI to process. May contain a fragment - * identifier. - * @param {string} keyEncoded The URI-encoded key. Case-sensitive. - * @return {boolean} Whether the key is present. - */ -goog.uri.utils.hasParam = function(uri, keyEncoded) { - return goog.uri.utils.findParam_(uri, 0, keyEncoded, - uri.search(goog.uri.utils.hashOrEndRe_)) >= 0; -}; /** - * Gets the first value of a query parameter. - * @param {string} uri The URI to process. May contain a fragment. - * @param {string} keyEncoded The URI-encoded key. Case-sensitive. - * @return {?string} The first value of the parameter (URI-decoded), or null - * if the parameter is not found. + * @classdesc + * A button control to reset rotation to 0. + * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css + * selector is added to the button when the rotation is 0. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.RotateOptions=} opt_options Rotate options. + * @api stable */ -goog.uri.utils.getParamValue = function(uri, keyEncoded) { - var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); - var foundIndex = goog.uri.utils.findParam_( - uri, 0, keyEncoded, hashOrEndIndex); +ol.control.Rotate = function(opt_options) { - if (foundIndex < 0) { - return null; + var options = goog.isDef(opt_options) ? opt_options : {}; + + var className = goog.isDef(options.className) ? + options.className : 'ol-rotate'; + + var label = goog.isDef(options.label) ? + options.label : '\u21E7'; + + /** + * @type {Node} + * @private + */ + this.label_ = null; + + if (goog.isString(label)) { + this.label_ = goog.dom.createDom(goog.dom.TagName.SPAN, + 'ol-compass', label); } else { - var endPosition = uri.indexOf('&', foundIndex); - if (endPosition < 0 || endPosition > hashOrEndIndex) { - endPosition = hashOrEndIndex; - } - // Progress forth to the end of the "key=" or "key&" substring. - foundIndex += keyEncoded.length + 1; - // Use substr, because it (unlike substring) will return empty string - // if foundIndex > endPosition. - return goog.string.urlDecode( - uri.substr(foundIndex, endPosition - foundIndex)); + this.label_ = label; + goog.dom.classlist.add(this.label_, 'ol-compass'); } -}; + var tipLabel = goog.isDef(options.tipLabel) ? + options.tipLabel : 'Reset rotation'; -/** - * Gets all values of a query parameter. - * @param {string} uri The URI to process. May contain a framgnet. - * @param {string} keyEncoded The URI-encoded key. Case-snsitive. - * @return {!Array<string>} All URI-decoded values with the given key. - * If the key is not found, this will have length 0, but never be null. - */ -goog.uri.utils.getParamValues = function(uri, keyEncoded) { - var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); - var position = 0; - var foundIndex; - var result = []; + var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'class': className + '-reset', + 'type' : 'button', + 'title': tipLabel + }, this.label_); - while ((foundIndex = goog.uri.utils.findParam_( - uri, position, keyEncoded, hashOrEndIndex)) >= 0) { - // Find where this parameter ends, either the '&' or the end of the - // query parameters. - position = uri.indexOf('&', foundIndex); - if (position < 0 || position > hashOrEndIndex) { - position = hashOrEndIndex; - } + goog.events.listen(button, goog.events.EventType.CLICK, + ol.control.Rotate.prototype.handleClick_, false, this); - // Progress forth to the end of the "key=" or "key&" substring. - foundIndex += keyEncoded.length + 1; - // Use substr, because it (unlike substring) will return empty string - // if foundIndex > position. - result.push(goog.string.urlDecode(uri.substr( - foundIndex, position - foundIndex))); - } + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); - return result; -}; + var render = goog.isDef(options.render) ? + options.render : ol.control.Rotate.render; + goog.base(this, { + element: element, + render: render, + target: options.target + }); -/** - * Regexp to find trailing question marks and ampersands. - * @type {RegExp} - * @private - */ -goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/; + /** + * @type {number} + * @private + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 250; + /** + * @type {boolean} + * @private + */ + this.autoHide_ = goog.isDef(options.autoHide) ? options.autoHide : true; -/** - * Removes all instances of a query parameter. - * @param {string} uri The URI to process. Must not contain a fragment. - * @param {string} keyEncoded The URI-encoded key. - * @return {string} The URI with all instances of the parameter removed. - */ -goog.uri.utils.removeParam = function(uri, keyEncoded) { - var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); - var position = 0; - var foundIndex; - var buffer = []; + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; - // Look for a query parameter. - while ((foundIndex = goog.uri.utils.findParam_( - uri, position, keyEncoded, hashOrEndIndex)) >= 0) { - // Get the portion of the query string up to, but not including, the ? - // or & starting the parameter. - buffer.push(uri.substring(position, foundIndex)); - // Progress to immediately after the '&'. If not found, go to the end. - // Avoid including the hash mark. - position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex, - hashOrEndIndex); + if (this.autoHide_) { + goog.dom.classlist.add(this.element, ol.css.CLASS_HIDDEN); } - // Append everything that is remaining. - buffer.push(uri.substr(position)); - - // Join the buffer, and remove trailing punctuation that remains. - return buffer.join('').replace( - goog.uri.utils.trailingQueryPunctuationRe_, '$1'); }; +goog.inherits(ol.control.Rotate, ol.control.Control); /** - * Replaces all existing definitions of a parameter with a single definition. - * - * Repeated calls to this can exhibit quadratic behavior due to the need to - * find existing instances and reconstruct the string, though it should be - * limited given the 2kb limit. Consider using appendParams to append multiple - * parameters in bulk. - * - * @param {string} uri The original URI, which may already have query data. - * @param {string} keyEncoded The key, which must already be URI encoded. - * @param {*} value The value, which will be stringized and encoded (assumed - * not already to be encoded). - * @return {string} The URI with the query parameter added. + * @param {goog.events.BrowserEvent} event The event to handle + * @private */ -goog.uri.utils.setParam = function(uri, keyEncoded, value) { - return goog.uri.utils.appendParam( - goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value); +ol.control.Rotate.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.resetNorth_(); }; /** - * Generates a URI path using a given URI and a path with checks to - * prevent consecutive "//". The baseUri passed in must not contain - * query or fragment identifiers. The path to append may not contain query or - * fragment identifiers. - * - * @param {string} baseUri URI to use as the base. - * @param {string} path Path to append. - * @return {string} Updated URI. + * @private */ -goog.uri.utils.appendPath = function(baseUri, path) { - goog.uri.utils.assertNoFragmentsOrQueries_(baseUri); - - // Remove any trailing '/' - if (goog.string.endsWith(baseUri, '/')) { - baseUri = baseUri.substr(0, baseUri.length - 1); +ol.control.Rotate.prototype.resetNorth_ = function() { + var map = this.getMap(); + var view = map.getView(); + if (goog.isNull(view)) { + // the map does not have a view, so we can't act + // upon it + return; } - // Remove any leading '/' - if (goog.string.startsWith(path, '/')) { - path = path.substr(1); + var currentRotation = view.getRotation(); + while (currentRotation < -Math.PI) { + currentRotation += 2 * Math.PI; + } + while (currentRotation > Math.PI) { + currentRotation -= 2 * Math.PI; + } + if (goog.isDef(currentRotation)) { + if (this.duration_ > 0) { + map.beforeRender(ol.animation.rotate({ + rotation: currentRotation, + duration: this.duration_, + easing: ol.easing.easeOut + })); + } + view.setRotation(0); } - return goog.string.buildString(baseUri, '/', path); }; /** - * Replaces the path. - * @param {string} uri URI to use as the base. - * @param {string} path New path. - * @return {string} Updated URI. + * Update the rotate control element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.Rotate} + * @api */ -goog.uri.utils.setPath = function(uri, path) { - // Add any missing '/'. - if (!goog.string.startsWith(path, '/')) { - path = '/' + path; +ol.control.Rotate.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (goog.isNull(frameState)) { + return; } - var parts = goog.uri.utils.split(uri); - return goog.uri.utils.buildFromEncodedParts( - parts[goog.uri.utils.ComponentIndex.SCHEME], - parts[goog.uri.utils.ComponentIndex.USER_INFO], - parts[goog.uri.utils.ComponentIndex.DOMAIN], - parts[goog.uri.utils.ComponentIndex.PORT], - path, - parts[goog.uri.utils.ComponentIndex.QUERY_DATA], - parts[goog.uri.utils.ComponentIndex.FRAGMENT]); + var rotation = frameState.viewState.rotation; + if (rotation != this.rotation_) { + var transform = 'rotate(' + goog.math.toDegrees(rotation) + 'deg)'; + if (this.autoHide_) { + goog.dom.classlist.enable( + this.element, ol.css.CLASS_HIDDEN, rotation === 0); + } + this.label_.style.msTransform = transform; + this.label_.style.webkitTransform = transform; + this.label_.style.transform = transform; + } + this.rotation_ = rotation; }; +goog.provide('ol.control.Zoom'); -/** - * Standard supported query parameters. - * @enum {string} - */ -goog.uri.utils.StandardQueryParam = { - - /** Unused parameter for unique-ifying. */ - RANDOM: 'zx' -}; - +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.animation'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); -/** - * Sets the zx parameter of a URI to a random value. - * @param {string} uri Any URI. - * @return {string} That URI with the "zx" parameter added or replaced to - * contain a random string. - */ -goog.uri.utils.makeUnique = function(uri) { - return goog.uri.utils.setParam(uri, - goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString()); -}; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Class for parsing and formatting URIs. - * - * Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to - * create a new instance of the goog.Uri object from Uri parts. - * - * e.g: <code>var myUri = new goog.Uri(window.location);</code> - * - * Implements RFC 3986 for parsing/formatting URIs. - * http://www.ietf.org/rfc/rfc3986.txt - * - * Some changes have been made to the interface (more like .NETs), though the - * internal representation is now of un-encoded parts, this will change the - * behavior slightly. + * @classdesc + * A control with 2 buttons, one for zoom in and one for zoom out. + * This control is one of the default controls of a map. To style this control + * use css selectors `.ol-zoom-in` and `.ol-zoom-out`. * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomOptions=} opt_options Zoom options. + * @api stable */ +ol.control.Zoom = function(opt_options) { -goog.provide('goog.Uri'); -goog.provide('goog.Uri.QueryData'); + var options = goog.isDef(opt_options) ? opt_options : {}; -goog.require('goog.array'); -goog.require('goog.string'); -goog.require('goog.structs'); -goog.require('goog.structs.Map'); -goog.require('goog.uri.utils'); -goog.require('goog.uri.utils.ComponentIndex'); -goog.require('goog.uri.utils.StandardQueryParam'); + var className = goog.isDef(options.className) ? options.className : 'ol-zoom'; + var delta = goog.isDef(options.delta) ? options.delta : 1; + var zoomInLabel = goog.isDef(options.zoomInLabel) ? + options.zoomInLabel : '+'; + var zoomOutLabel = goog.isDef(options.zoomOutLabel) ? + options.zoomOutLabel : '\u2212'; -/** - * This class contains setters and getters for the parts of the URI. - * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part - * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the - * decoded path, <code>/foo bar</code>. - * - * Reserved characters (see RFC 3986 section 2.2) can be present in - * their percent-encoded form in scheme, domain, and path URI components and - * will not be auto-decoded. For example: - * <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will - * return <code>relative/path%2fto/resource</code>. - * - * The constructor accepts an optional unparsed, raw URI string. The parser - * is relaxed, so special characters that aren't escaped but don't cause - * ambiguities will not cause parse failures. - * - * All setters return <code>this</code> and so may be chained, a la - * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>. - * - * @param {*=} opt_uri Optional string URI to parse - * (use goog.Uri.create() to create a URI from parts), or if - * a goog.Uri is passed, a clone is created. - * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore - * the case of the parameter name. - * - * @constructor - */ -goog.Uri = function(opt_uri, opt_ignoreCase) { - // Parse in the uri string - var m; - if (opt_uri instanceof goog.Uri) { - this.ignoreCase_ = goog.isDef(opt_ignoreCase) ? - opt_ignoreCase : opt_uri.getIgnoreCase(); - this.setScheme(opt_uri.getScheme()); - this.setUserInfo(opt_uri.getUserInfo()); - this.setDomain(opt_uri.getDomain()); - this.setPort(opt_uri.getPort()); - this.setPath(opt_uri.getPath()); - this.setQueryData(opt_uri.getQueryData().clone()); - this.setFragment(opt_uri.getFragment()); - } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) { - this.ignoreCase_ = !!opt_ignoreCase; + var zoomInTipLabel = goog.isDef(options.zoomInTipLabel) ? + options.zoomInTipLabel : 'Zoom in'; + var zoomOutTipLabel = goog.isDef(options.zoomOutTipLabel) ? + options.zoomOutTipLabel : 'Zoom out'; - // Set the parts -- decoding as we do so. - // COMPATABILITY NOTE - In IE, unmatched fields may be empty strings, - // whereas in other browsers they will be undefined. - this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true); - this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true); - this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true); - this.setPort(m[goog.uri.utils.ComponentIndex.PORT]); - this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true); - this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true); - this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true); + var inElement = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'class': className + '-in', + 'type' : 'button', + 'title': zoomInTipLabel + }, zoomInLabel); - } else { - this.ignoreCase_ = !!opt_ignoreCase; - this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_); - } -}; + goog.events.listen(inElement, + goog.events.EventType.CLICK, goog.partial( + ol.control.Zoom.prototype.handleClick_, delta), false, this); + var outElement = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'class': className + '-out', + 'type' : 'button', + 'title': zoomOutTipLabel + }, zoomOutLabel); -/** - * If true, we preserve the type of query parameters set programmatically. - * - * This means that if you set a parameter to a boolean, and then call - * getParameterValue, you will get a boolean back. - * - * If false, we will coerce parameters to strings, just as they would - * appear in real URIs. - * - * TODO(nicksantos): Remove this once people have time to fix all tests. - * - * @type {boolean} - */ -goog.Uri.preserveParameterTypesCompatibilityFlag = false; + goog.events.listen(outElement, + goog.events.EventType.CLICK, goog.partial( + ol.control.Zoom.prototype.handleClick_, -delta), false, this); + goog.events.listen(outElement, [ + goog.events.EventType.MOUSEOUT, + goog.events.EventType.FOCUSOUT + ], function() { + this.blur(); + }, false); -/** - * Parameter name added to stop caching. - * @type {string} - */ -goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM; + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, inElement, + outElement); + goog.base(this, { + element: element, + target: options.target + }); -/** - * Scheme such as "http". - * @type {string} - * @private - */ -goog.Uri.prototype.scheme_ = ''; + /** + * @type {number} + * @private + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 250; + +}; +goog.inherits(ol.control.Zoom, ol.control.Control); /** - * User credentials in the form "username:password". - * @type {string} + * @param {number} delta Zoom delta. + * @param {goog.events.BrowserEvent} event The event to handle * @private */ -goog.Uri.prototype.userInfo_ = ''; +ol.control.Zoom.prototype.handleClick_ = function(delta, event) { + event.preventDefault(); + this.zoomByDelta_(delta); +}; /** - * Domain part, e.g. "www.google.com". - * @type {string} + * @param {number} delta Zoom delta. * @private */ -goog.Uri.prototype.domain_ = ''; +ol.control.Zoom.prototype.zoomByDelta_ = function(delta) { + var map = this.getMap(); + var view = map.getView(); + if (goog.isNull(view)) { + // the map does not have a view, so we can't act + // upon it + return; + } + var currentResolution = view.getResolution(); + if (goog.isDef(currentResolution)) { + if (this.duration_ > 0) { + map.beforeRender(ol.animation.zoom({ + resolution: currentResolution, + duration: this.duration_, + easing: ol.easing.easeOut + })); + } + var newResolution = view.constrainResolution(currentResolution, delta); + view.setResolution(newResolution); + } +}; +goog.provide('ol.control'); -/** - * Port, e.g. 8080. - * @type {?number} - * @private - */ -goog.Uri.prototype.port_ = null; +goog.require('ol.Collection'); +goog.require('ol.control.Attribution'); +goog.require('ol.control.Rotate'); +goog.require('ol.control.Zoom'); /** - * Path, e.g. "/tests/img.png". - * @type {string} - * @private + * Set of controls included in maps by default. Unless configured otherwise, + * this returns a collection containing an instance of each of the following + * controls: + * * {@link ol.control.Zoom} + * * {@link ol.control.Rotate} + * * {@link ol.control.Attribution} + * + * @param {olx.control.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.<ol.control.Control>} Controls. + * @api stable */ -goog.Uri.prototype.path_ = ''; +ol.control.defaults = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * Object representing query data. - * @type {!goog.Uri.QueryData} - * @private - */ -goog.Uri.prototype.queryData_; + var controls = new ol.Collection(); + var zoomControl = goog.isDef(options.zoom) ? + options.zoom : true; + if (zoomControl) { + controls.push(new ol.control.Zoom(options.zoomOptions)); + } -/** - * The fragment without the #. - * @type {string} - * @private - */ -goog.Uri.prototype.fragment_ = ''; + var rotateControl = goog.isDef(options.rotate) ? + options.rotate : true; + if (rotateControl) { + controls.push(new ol.control.Rotate(options.rotateOptions)); + } + var attributionControl = goog.isDef(options.attribution) ? + options.attribution : true; + if (attributionControl) { + controls.push(new ol.control.Attribution(options.attributionOptions)); + } -/** - * Whether or not this Uri should be treated as Read Only. - * @type {boolean} - * @private - */ -goog.Uri.prototype.isReadOnly_ = false; - + return controls; -/** - * Whether or not to ignore case when comparing query params. - * @type {boolean} - * @private - */ -goog.Uri.prototype.ignoreCase_ = false; +}; +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @return {string} The string form of the url. - * @override + * @fileoverview Functions for managing full screen status of the DOM. + * */ -goog.Uri.prototype.toString = function() { - var out = []; - - var scheme = this.getScheme(); - if (scheme) { - out.push(goog.Uri.encodeSpecialChars_( - scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), ':'); - } - var domain = this.getDomain(); - if (domain) { - out.push('//'); +goog.provide('goog.dom.fullscreen'); +goog.provide('goog.dom.fullscreen.EventType'); - var userInfo = this.getUserInfo(); - if (userInfo) { - out.push(goog.Uri.encodeSpecialChars_( - userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), '@'); - } +goog.require('goog.dom'); +goog.require('goog.userAgent'); - out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain))); - var port = this.getPort(); - if (port != null) { - out.push(':', String(port)); +/** + * Event types for full screen. + * @enum {string} + */ +goog.dom.fullscreen.EventType = { + /** Dispatched by the Document when the fullscreen status changes. */ + CHANGE: (function() { + if (goog.userAgent.WEBKIT) { + return 'webkitfullscreenchange'; } - } - - var path = this.getPath(); - if (path) { - if (this.hasDomain() && path.charAt(0) != '/') { - out.push('/'); + if (goog.userAgent.GECKO) { + return 'mozfullscreenchange'; } - out.push(goog.Uri.encodeSpecialChars_( - path, - path.charAt(0) == '/' ? - goog.Uri.reDisallowedInAbsolutePath_ : - goog.Uri.reDisallowedInRelativePath_, - true)); - } - - var query = this.getEncodedQuery(); - if (query) { - out.push('?', query); - } - - var fragment = this.getFragment(); - if (fragment) { - out.push('#', goog.Uri.encodeSpecialChars_( - fragment, goog.Uri.reDisallowedInFragment_)); - } - return out.join(''); + if (goog.userAgent.IE) { + return 'MSFullscreenChange'; + } + // Opera 12-14, and W3C standard (Draft): + // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html + return 'fullscreenchange'; + })() }; /** - * Resolves the given relative URI (a goog.Uri object), using the URI - * represented by this instance as the base URI. - * - * There are several kinds of relative URIs:<br> - * 1. foo - replaces the last part of the path, the whole query and fragment<br> - * 2. /foo - replaces the the path, the query and fragment<br> - * 3. //foo - replaces everything from the domain on. foo is a domain name<br> - * 4. ?foo - replace the query and fragment<br> - * 5. #foo - replace the fragment only - * - * Additionally, if relative URI has a non-empty path, all ".." and "." - * segments will be resolved, as described in RFC 3986. - * - * @param {!goog.Uri} relativeUri The relative URI to resolve. - * @return {!goog.Uri} The resolved URI. + * Determines if full screen is supported. + * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being + * queried. If not provided, use the current DOM. + * @return {boolean} True iff full screen is supported. */ -goog.Uri.prototype.resolve = function(relativeUri) { - - var absoluteUri = this.clone(); - - // we satisfy these conditions by looking for the first part of relativeUri - // that is not blank and applying defaults to the rest - - var overridden = relativeUri.hasScheme(); - - if (overridden) { - absoluteUri.setScheme(relativeUri.getScheme()); - } else { - overridden = relativeUri.hasUserInfo(); - } +goog.dom.fullscreen.isSupported = function(opt_domHelper) { + var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); + var body = doc.body; + return !!(body.webkitRequestFullscreen || + (body.mozRequestFullScreen && doc.mozFullScreenEnabled) || + (body.msRequestFullscreen && doc.msFullscreenEnabled) || + (body.requestFullscreen && doc.fullscreenEnabled)); +}; - if (overridden) { - absoluteUri.setUserInfo(relativeUri.getUserInfo()); - } else { - overridden = relativeUri.hasDomain(); - } - if (overridden) { - absoluteUri.setDomain(relativeUri.getDomain()); - } else { - overridden = relativeUri.hasPort(); +/** + * Requests putting the element in full screen. + * @param {!Element} element The element to put full screen. + */ +goog.dom.fullscreen.requestFullScreen = function(element) { + if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else if (element.requestFullscreen) { + element.requestFullscreen(); } +}; - var path = relativeUri.getPath(); - if (overridden) { - absoluteUri.setPort(relativeUri.getPort()); - } else { - overridden = relativeUri.hasPath(); - if (overridden) { - // resolve path properly - if (path.charAt(0) != '/') { - // path is relative - if (this.hasDomain() && !this.hasPath()) { - // RFC 3986, section 5.2.3, case 1 - path = '/' + path; - } else { - // RFC 3986, section 5.2.3, case 2 - var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/'); - if (lastSlashIndex != -1) { - path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path; - } - } - } - path = goog.Uri.removeDotSegments(path); - } - } - if (overridden) { - absoluteUri.setPath(path); +/** + * Requests putting the element in full screen with full keyboard access. + * @param {!Element} element The element to put full screen. + */ +goog.dom.fullscreen.requestFullScreenWithKeys = function( + element) { + if (element.mozRequestFullScreenWithKeys) { + element.mozRequestFullScreenWithKeys(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); } else { - overridden = relativeUri.hasQuery(); + goog.dom.fullscreen.requestFullScreen(element); } +}; - if (overridden) { - absoluteUri.setQueryData(relativeUri.getDecodedQuery()); - } else { - overridden = relativeUri.hasFragment(); - } - if (overridden) { - absoluteUri.setFragment(relativeUri.getFragment()); +/** + * Exits full screen. + * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being + * queried. If not provided, use the current DOM. + */ +goog.dom.fullscreen.exitFullScreen = function(opt_domHelper) { + var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); + if (doc.webkitCancelFullScreen) { + doc.webkitCancelFullScreen(); + } else if (doc.mozCancelFullScreen) { + doc.mozCancelFullScreen(); + } else if (doc.msExitFullscreen) { + doc.msExitFullscreen(); + } else if (doc.exitFullscreen) { + doc.exitFullscreen(); } - - return absoluteUri; }; /** - * Clones the URI instance. - * @return {!goog.Uri} New instance of the URI object. + * Determines if the document is full screen. + * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being + * queried. If not provided, use the current DOM. + * @return {boolean} Whether the document is full screen. */ -goog.Uri.prototype.clone = function() { - return new goog.Uri(this); +goog.dom.fullscreen.isFullScreen = function(opt_domHelper) { + var doc = goog.dom.fullscreen.getDocument_(opt_domHelper); + // IE 11 doesn't have similar boolean property, so check whether + // document.msFullscreenElement is null instead. + return !!(doc.webkitIsFullScreen || doc.mozFullScreen || + doc.msFullscreenElement || doc.fullscreenElement); }; /** - * @return {string} The encoded scheme/protocol for the URI. + * Gets the document object of the dom. + * @param {!goog.dom.DomHelper=} opt_domHelper The DomHelper for the DOM being + * queried. If not provided, use the current DOM. + * @return {!Document} The dom document. + * @private */ -goog.Uri.prototype.getScheme = function() { - return this.scheme_; +goog.dom.fullscreen.getDocument_ = function(opt_domHelper) { + return opt_domHelper ? + opt_domHelper.getDocument() : + goog.dom.getDomHelper().getDocument(); }; +goog.provide('ol.control.FullScreen'); -/** - * Sets the scheme/protocol. - * @param {string} newScheme New scheme value. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * @return {!goog.Uri} Reference to this URI object. - */ -goog.Uri.prototype.setScheme = function(newScheme, opt_decode) { - this.enforceReadOnly(); - this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) : - newScheme; +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.dom.fullscreen'); +goog.require('goog.dom.fullscreen.EventType'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); - // remove an : at the end of the scheme so somebody can pass in - // window.location.protocol - if (this.scheme_) { - this.scheme_ = this.scheme_.replace(/:$/, ''); - } - return this; -}; /** - * @return {boolean} Whether the scheme has been set. + * @classdesc + * Provides a button that when clicked fills up the full screen with the map. + * When in full screen mode, a close button is shown to exit full screen mode. + * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to + * toggle the map in full screen mode. + * + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.FullScreenOptions=} opt_options Options. + * @api stable */ -goog.Uri.prototype.hasScheme = function() { - return !!this.scheme_; -}; +ol.control.FullScreen = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * @return {string} The decoded user info. - */ -goog.Uri.prototype.getUserInfo = function() { - return this.userInfo_; -}; + /** + * @private + * @type {string} + */ + this.cssClassName_ = goog.isDef(options.className) ? + options.className : 'ol-full-screen'; + var label = goog.isDef(options.label) ? options.label : '\u2194'; -/** - * Sets the userInfo. - * @param {string} newUserInfo New userInfo value. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * @return {!goog.Uri} Reference to this URI object. - */ -goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) { - this.enforceReadOnly(); - this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) : - newUserInfo; - return this; -}; + /** + * @private + * @type {Node} + */ + this.labelNode_ = /** @type {Node} */ (goog.isString(label) ? + goog.dom.createTextNode(label) : label); + var labelActive = goog.isDef(options.labelActive) ? + options.labelActive : '\u00d7'; -/** - * @return {boolean} Whether the user info has been set. - */ -goog.Uri.prototype.hasUserInfo = function() { - return !!this.userInfo_; -}; + /** + * @private + * @type {Node} + */ + this.labelActiveNode_ = /** @type {Node} */ (goog.isString(labelActive) ? + goog.dom.createTextNode(labelActive) : labelActive); + var tipLabel = goog.isDef(options.tipLabel) ? + options.tipLabel : 'Toggle full-screen'; + var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'class': this.cssClassName_ + '-' + goog.dom.fullscreen.isFullScreen(), + 'type': 'button', + 'title': tipLabel + }, this.labelNode_); -/** - * @return {string} The decoded domain. - */ -goog.Uri.prototype.getDomain = function() { - return this.domain_; -}; + goog.events.listen(button, goog.events.EventType.CLICK, + this.handleClick_, false, this); + goog.events.listen(goog.global.document, + goog.dom.fullscreen.EventType.CHANGE, + this.handleFullScreenChange_, false, this); -/** - * Sets the domain. - * @param {string} newDomain New domain value. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * @return {!goog.Uri} Reference to this URI object. - */ -goog.Uri.prototype.setDomain = function(newDomain, opt_decode) { - this.enforceReadOnly(); - this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) : - newDomain; - return this; -}; + var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + + ' ' + ol.css.CLASS_CONTROL + ' ' + + (!goog.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : ''); + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); + + goog.base(this, { + element: element, + target: options.target + }); + /** + * @private + * @type {boolean} + */ + this.keys_ = goog.isDef(options.keys) ? options.keys : false; -/** - * @return {boolean} Whether the domain has been set. - */ -goog.Uri.prototype.hasDomain = function() { - return !!this.domain_; }; +goog.inherits(ol.control.FullScreen, ol.control.Control); /** - * @return {?number} The port number. + * @param {goog.events.BrowserEvent} event The event to handle + * @private */ -goog.Uri.prototype.getPort = function() { - return this.port_; +ol.control.FullScreen.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleFullScreen_(); }; /** - * Sets the port number. - * @param {*} newPort Port number. Will be explicitly casted to a number. - * @return {!goog.Uri} Reference to this URI object. + * @private */ -goog.Uri.prototype.setPort = function(newPort) { - this.enforceReadOnly(); - - if (newPort) { - newPort = Number(newPort); - if (isNaN(newPort) || newPort < 0) { - throw Error('Bad port number ' + newPort); - } - this.port_ = newPort; +ol.control.FullScreen.prototype.handleFullScreen_ = function() { + if (!goog.dom.fullscreen.isSupported()) { + return; + } + var map = this.getMap(); + if (goog.isNull(map)) { + return; + } + if (goog.dom.fullscreen.isFullScreen()) { + goog.dom.fullscreen.exitFullScreen(); } else { - this.port_ = null; + var target = map.getTarget(); + goog.asserts.assert(goog.isDefAndNotNull(target), + 'target should be defined'); + var element = goog.dom.getElement(target); + goog.asserts.assert(goog.isDefAndNotNull(element), + 'element should be defined'); + if (this.keys_) { + goog.dom.fullscreen.requestFullScreenWithKeys(element); + } else { + goog.dom.fullscreen.requestFullScreen(element); + } } - - return this; }; /** - * @return {boolean} Whether the port has been set. + * @private */ -goog.Uri.prototype.hasPort = function() { - return this.port_ != null; +ol.control.FullScreen.prototype.handleFullScreenChange_ = function() { + var opened = this.cssClassName_ + '-true'; + var closed = this.cssClassName_ + '-false'; + var button = goog.dom.getFirstElementChild(this.element); + var map = this.getMap(); + if (goog.dom.fullscreen.isFullScreen()) { + goog.dom.classlist.swap(button, closed, opened); + goog.dom.replaceNode(this.labelActiveNode_, this.labelNode_); + } else { + goog.dom.classlist.swap(button, opened, closed); + goog.dom.replaceNode(this.labelNode_, this.labelActiveNode_); + } + if (!goog.isNull(map)) { + map.updateSize(); + } }; +goog.provide('ol.Pixel'); + /** - * @return {string} The decoded path. + * An array with two elements, representing a pixel. The first element is the + * x-coordinate, the second the y-coordinate of the pixel. + * @typedef {Array.<number>} + * @api stable */ -goog.Uri.prototype.getPath = function() { - return this.path_; -}; +ol.Pixel; +// FIXME should listen on appropriate pane, once it is defined -/** - * Sets the path. - * @param {string} newPath New path value. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * @return {!goog.Uri} Reference to this URI object. - */ -goog.Uri.prototype.setPath = function(newPath, opt_decode) { - this.enforceReadOnly(); - this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath; - return this; -}; +goog.provide('ol.control.MousePosition'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.CoordinateFormatType'); +goog.require('ol.Object'); +goog.require('ol.Pixel'); +goog.require('ol.TransformFunction'); +goog.require('ol.control.Control'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); /** - * @return {boolean} Whether the path has been set. + * @enum {string} */ -goog.Uri.prototype.hasPath = function() { - return !!this.path_; +ol.control.MousePositionProperty = { + PROJECTION: 'projection', + COORDINATE_FORMAT: 'coordinateFormat' }; + /** - * @return {boolean} Whether the query string has been set. + * @classdesc + * A control to show the 2D coordinates of the mouse cursor. By default, these + * are in the view projection, but can be in any supported projection. + * By default the control is shown in the top right corner of the map, but this + * can be changed by using the css selector `.ol-mouse-position`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.MousePositionOptions=} opt_options Mouse position + * options. + * @api stable */ -goog.Uri.prototype.hasQuery = function() { - return this.queryData_.toString() !== ''; -}; +ol.control.MousePosition = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * Sets the query data. - * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * Applies only if queryData is a string. - * @return {!goog.Uri} Reference to this URI object. - */ -goog.Uri.prototype.setQueryData = function(queryData, opt_decode) { - this.enforceReadOnly(); + var className = goog.isDef(options.className) ? + options.className : 'ol-mouse-position'; - if (queryData instanceof goog.Uri.QueryData) { - this.queryData_ = queryData; - this.queryData_.setIgnoreCase(this.ignoreCase_); - } else { - if (!opt_decode) { - // QueryData accepts encoded query string, so encode it if - // opt_decode flag is not true. - queryData = goog.Uri.encodeSpecialChars_(queryData, - goog.Uri.reDisallowedInQuery_); - } - this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_); + var element = goog.dom.createDom(goog.dom.TagName.DIV, className); + + var render = goog.isDef(options.render) ? + options.render : ol.control.MousePosition.render; + + goog.base(this, { + element: element, + render: render, + target: options.target + }); + + goog.events.listen(this, + ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION), + this.handleProjectionChanged_, false, this); + + if (goog.isDef(options.coordinateFormat)) { + this.setCoordinateFormat(options.coordinateFormat); + } + if (goog.isDef(options.projection)) { + this.setProjection(ol.proj.get(options.projection)); } - return this; + /** + * @private + * @type {string} + */ + this.undefinedHTML_ = goog.isDef(options.undefinedHTML) ? + options.undefinedHTML : ''; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = element.innerHTML; + + /** + * @private + * @type {ol.proj.Projection} + */ + this.mapProjection_ = null; + + /** + * @private + * @type {?ol.TransformFunction} + */ + this.transform_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.lastMouseMovePixel_ = null; + }; +goog.inherits(ol.control.MousePosition, ol.control.Control); /** - * Sets the URI query. - * @param {string} newQuery New query value. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * @return {!goog.Uri} Reference to this URI object. + * Update the mouseposition element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.MousePosition} + * @api */ -goog.Uri.prototype.setQuery = function(newQuery, opt_decode) { - return this.setQueryData(newQuery, opt_decode); +ol.control.MousePosition.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (goog.isNull(frameState)) { + this.mapProjection_ = null; + } else { + if (this.mapProjection_ != frameState.viewState.projection) { + this.mapProjection_ = frameState.viewState.projection; + this.transform_ = null; + } + } + this.updateHTML_(this.lastMouseMovePixel_); }; /** - * @return {string} The encoded URI query, not including the ?. + * @private */ -goog.Uri.prototype.getEncodedQuery = function() { - return this.queryData_.toString(); +ol.control.MousePosition.prototype.handleProjectionChanged_ = function() { + this.transform_ = null; }; /** - * @return {string} The decoded URI query, not including the ?. + * Return the coordinate format type used to render the current position or + * undefined. + * @return {ol.CoordinateFormatType|undefined} The format to render the current + * position in. + * @observable + * @api stable */ -goog.Uri.prototype.getDecodedQuery = function() { - return this.queryData_.toDecodedString(); +ol.control.MousePosition.prototype.getCoordinateFormat = function() { + return /** @type {ol.CoordinateFormatType|undefined} */ ( + this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT)); }; /** - * Returns the query data. - * @return {!goog.Uri.QueryData} QueryData object. + * Return the projection that is used to report the mouse position. + * @return {ol.proj.Projection|undefined} The projection to report mouse + * position in. + * @observable + * @api stable */ -goog.Uri.prototype.getQueryData = function() { - return this.queryData_; +ol.control.MousePosition.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.control.MousePositionProperty.PROJECTION)); }; /** - * @return {string} The encoded URI query, not including the ?. - * - * Warning: This method, unlike other getter methods, returns encoded - * value, instead of decoded one. + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @protected */ -goog.Uri.prototype.getQuery = function() { - return this.getEncodedQuery(); +ol.control.MousePosition.prototype.handleMouseMove = function(browserEvent) { + var map = this.getMap(); + this.lastMouseMovePixel_ = map.getEventPixel(browserEvent.getBrowserEvent()); + this.updateHTML_(this.lastMouseMovePixel_); }; /** - * Sets the value of the named query parameters, clearing previous values for - * that key. - * - * @param {string} key The parameter to set. - * @param {*} value The new value. - * @return {!goog.Uri} Reference to this URI object. + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @protected */ -goog.Uri.prototype.setParameterValue = function(key, value) { - this.enforceReadOnly(); - this.queryData_.set(key, value); - return this; +ol.control.MousePosition.prototype.handleMouseOut = function(browserEvent) { + this.updateHTML_(null); + this.lastMouseMovePixel_ = null; }; /** - * Sets the values of the named query parameters, clearing previous values for - * that key. Not new values will currently be moved to the end of the query - * string. - * - * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new']) - * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p> - * - * @param {string} key The parameter to set. - * @param {*} values The new values. If values is a single - * string then it will be treated as the sole value. - * @return {!goog.Uri} Reference to this URI object. + * @inheritDoc + * @api stable */ -goog.Uri.prototype.setParameterValues = function(key, values) { - this.enforceReadOnly(); - - if (!goog.isArray(values)) { - values = [String(values)]; +ol.control.MousePosition.prototype.setMap = function(map) { + goog.base(this, 'setMap', map); + if (!goog.isNull(map)) { + var viewport = map.getViewport(); + this.listenerKeys.push( + goog.events.listen(viewport, goog.events.EventType.MOUSEMOVE, + this.handleMouseMove, false, this), + goog.events.listen(viewport, goog.events.EventType.MOUSEOUT, + this.handleMouseOut, false, this) + ); } - - this.queryData_.setValues(key, values); - - return this; }; /** - * Returns the value<b>s</b> for a given cgi parameter as a list of decoded - * query parameter values. - * @param {string} name The parameter to get values for. - * @return {!Array<?>} The values for a given cgi parameter as a list of - * decoded query parameter values. + * Set the coordinate format type used to render the current position. + * @param {ol.CoordinateFormatType} format The format to render the current + * position in. + * @observable + * @api stable */ -goog.Uri.prototype.getParameterValues = function(name) { - return this.queryData_.getValues(name); +ol.control.MousePosition.prototype.setCoordinateFormat = function(format) { + this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format); }; /** - * Returns the first value for a given cgi parameter or undefined if the given - * parameter name does not appear in the query string. - * @param {string} paramName Unescaped parameter name. - * @return {string|undefined} The first value for a given cgi parameter or - * undefined if the given parameter name does not appear in the query - * string. + * Set the projection that is used to report the mouse position. + * @param {ol.proj.Projection} projection The projection to report mouse + * position in. + * @observable + * @api stable */ -goog.Uri.prototype.getParameterValue = function(paramName) { - // NOTE(nicksantos): This type-cast is a lie when - // preserveParameterTypesCompatibilityFlag is set to true. - // But this should only be set to true in tests. - return /** @type {string|undefined} */ (this.queryData_.get(paramName)); +ol.control.MousePosition.prototype.setProjection = function(projection) { + this.set(ol.control.MousePositionProperty.PROJECTION, projection); }; /** - * @return {string} The URI fragment, not including the #. + * @param {?ol.Pixel} pixel Pixel. + * @private */ -goog.Uri.prototype.getFragment = function() { - return this.fragment_; +ol.control.MousePosition.prototype.updateHTML_ = function(pixel) { + var html = this.undefinedHTML_; + if (!goog.isNull(pixel) && !goog.isNull(this.mapProjection_)) { + if (goog.isNull(this.transform_)) { + var projection = this.getProjection(); + if (goog.isDef(projection)) { + this.transform_ = ol.proj.getTransformFromProjections( + this.mapProjection_, projection); + } else { + this.transform_ = ol.proj.identityTransform; + } + } + var map = this.getMap(); + var coordinate = map.getCoordinateFromPixel(pixel); + if (!goog.isNull(coordinate)) { + this.transform_(coordinate, coordinate); + var coordinateFormat = this.getCoordinateFormat(); + if (goog.isDef(coordinateFormat)) { + html = coordinateFormat(coordinate); + } else { + html = coordinate.toString(); + } + } + } + if (!goog.isDef(this.renderedHTML_) || html != this.renderedHTML_) { + this.element.innerHTML = html; + this.renderedHTML_ = html; + } }; +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Sets the URI fragment. - * @param {string} newFragment New fragment value. - * @param {boolean=} opt_decode Optional param for whether to decode new value. - * @return {!goog.Uri} Reference to this URI object. + * @fileoverview A delayed callback that pegs to the next animation frame + * instead of a user-configurable timeout. + * + * @author nicksantos@google.com (Nick Santos) */ -goog.Uri.prototype.setFragment = function(newFragment, opt_decode) { - this.enforceReadOnly(); - this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) : - newFragment; - return this; -}; + +goog.provide('goog.async.AnimationDelay'); + +goog.require('goog.Disposable'); +goog.require('goog.events'); +goog.require('goog.functions'); + + + +// TODO(nicksantos): Should we factor out the common code between this and +// goog.async.Delay? I'm not sure if there's enough code for this to really +// make sense. Subclassing seems like the wrong approach for a variety of +// reasons. Maybe there should be a common interface? + /** - * @return {boolean} Whether the URI has a fragment set. + * A delayed callback that pegs to the next animation frame + * instead of a user configurable timeout. By design, this should have + * the same interface as goog.async.Delay. + * + * Uses requestAnimationFrame and friends when available, but falls + * back to a timeout of goog.async.AnimationDelay.TIMEOUT. + * + * For more on requestAnimationFrame and how you can use it to create smoother + * animations, see: + * @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + * + * @param {function(number)} listener Function to call when the delay completes. + * Will be passed the timestamp when it's called, in unix ms. + * @param {Window=} opt_window The window object to execute the delay in. + * Defaults to the global object. + * @param {Object=} opt_handler The object scope to invoke the function in. + * @constructor + * @extends {goog.Disposable} + * @final */ -goog.Uri.prototype.hasFragment = function() { - return !!this.fragment_; +goog.async.AnimationDelay = function(listener, opt_window, opt_handler) { + goog.async.AnimationDelay.base(this, 'constructor'); + + /** + * The function that will be invoked after a delay. + * @type {function(number)} + * @private + */ + this.listener_ = listener; + + /** + * The object context to invoke the callback in. + * @type {Object|undefined} + * @private + */ + this.handler_ = opt_handler; + + /** + * @type {Window} + * @private + */ + this.win_ = opt_window || window; + + /** + * Cached callback function invoked when the delay finishes. + * @type {function()} + * @private + */ + this.callback_ = goog.bind(this.doAction_, this); }; +goog.inherits(goog.async.AnimationDelay, goog.Disposable); /** - * Returns true if this has the same domain as that of uri2. - * @param {!goog.Uri} uri2 The URI object to compare to. - * @return {boolean} true if same domain; false otherwise. + * Identifier of the active delay timeout, or event listener, + * or null when inactive. + * @type {goog.events.Key|number|null} + * @private */ -goog.Uri.prototype.hasSameDomainAs = function(uri2) { - return ((!this.hasDomain() && !uri2.hasDomain()) || - this.getDomain() == uri2.getDomain()) && - ((!this.hasPort() && !uri2.hasPort()) || - this.getPort() == uri2.getPort()); -}; +goog.async.AnimationDelay.prototype.id_ = null; /** - * Adds a random parameter to the Uri. - * @return {!goog.Uri} Reference to this Uri object. + * If we're using dom listeners. + * @type {?boolean} + * @private */ -goog.Uri.prototype.makeUnique = function() { - this.enforceReadOnly(); - this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString()); +goog.async.AnimationDelay.prototype.usingListeners_ = false; - return this; -}; + +/** + * Default wait timeout for animations (in milliseconds). Only used for timed + * animation, which uses a timer (setTimeout) to schedule animation. + * + * @type {number} + * @const + */ +goog.async.AnimationDelay.TIMEOUT = 20; /** - * Removes the named query parameter. + * Name of event received from the requestAnimationFrame in Firefox. * - * @param {string} key The parameter to remove. - * @return {!goog.Uri} Reference to this URI object. + * @type {string} + * @const + * @private */ -goog.Uri.prototype.removeParameter = function(key) { - this.enforceReadOnly(); - this.queryData_.remove(key); - return this; +goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint'; + + +/** + * Starts the delay timer. The provided listener function will be called + * before the next animation frame. + */ +goog.async.AnimationDelay.prototype.start = function() { + this.stop(); + this.usingListeners_ = false; + + var raf = this.getRaf_(); + var cancelRaf = this.getCancelRaf_(); + if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) { + // Because Firefox (Gecko) runs animation in separate threads, it also saves + // time by running the requestAnimationFrame callbacks in that same thread. + // Sadly this breaks the assumption of implicit thread-safety in JS, and can + // thus create thread-based inconsistencies on counters etc. + // + // Calling cycleAnimations_ using the MozBeforePaint event instead of as + // callback fixes this. + // + // Trigger this condition only if the mozRequestAnimationFrame is available, + // but not the W3C requestAnimationFrame function (as in draft) or the + // equivalent cancel functions. + this.id_ = goog.events.listen( + this.win_, + goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_, + this.callback_); + this.win_.mozRequestAnimationFrame(null); + this.usingListeners_ = true; + } else if (raf && cancelRaf) { + this.id_ = raf.call(this.win_, this.callback_); + } else { + this.id_ = this.win_.setTimeout( + // Prior to Firefox 13, Gecko passed a non-standard parameter + // to the callback that we want to ignore. + goog.functions.lock(this.callback_), + goog.async.AnimationDelay.TIMEOUT); + } }; /** - * Sets whether Uri is read only. If this goog.Uri is read-only, - * enforceReadOnly_ will be called at the start of any function that may modify - * this Uri. - * @param {boolean} isReadOnly whether this goog.Uri should be read only. - * @return {!goog.Uri} Reference to this Uri object. + * Stops the delay timer if it is active. No action is taken if the timer is not + * in use. */ -goog.Uri.prototype.setReadOnly = function(isReadOnly) { - this.isReadOnly_ = isReadOnly; - return this; +goog.async.AnimationDelay.prototype.stop = function() { + if (this.isActive()) { + var raf = this.getRaf_(); + var cancelRaf = this.getCancelRaf_(); + if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) { + goog.events.unlistenByKey(this.id_); + } else if (raf && cancelRaf) { + cancelRaf.call(this.win_, /** @type {number} */ (this.id_)); + } else { + this.win_.clearTimeout(/** @type {number} */ (this.id_)); + } + } + this.id_ = null; }; /** - * @return {boolean} Whether the URI is read only. + * Fires delay's action even if timer has already gone off or has not been + * started yet; guarantees action firing. Stops the delay timer. */ -goog.Uri.prototype.isReadOnly = function() { - return this.isReadOnly_; +goog.async.AnimationDelay.prototype.fire = function() { + this.stop(); + this.doAction_(); }; /** - * Checks if this Uri has been marked as read only, and if so, throws an error. - * This should be called whenever any modifying function is called. + * Fires delay's action only if timer is currently active. Stops the delay + * timer. */ -goog.Uri.prototype.enforceReadOnly = function() { - if (this.isReadOnly_) { - throw Error('Tried to modify a read-only Uri'); +goog.async.AnimationDelay.prototype.fireIfActive = function() { + if (this.isActive()) { + this.fire(); } }; /** - * Sets whether to ignore case. - * NOTE: If there are already key/value pairs in the QueryData, and - * ignoreCase_ is set to false, the keys will all be lower-cased. - * @param {boolean} ignoreCase whether this goog.Uri should ignore case. - * @return {!goog.Uri} Reference to this Uri object. + * @return {boolean} True if the delay is currently active, false otherwise. */ -goog.Uri.prototype.setIgnoreCase = function(ignoreCase) { - this.ignoreCase_ = ignoreCase; - if (this.queryData_) { - this.queryData_.setIgnoreCase(ignoreCase); - } - return this; +goog.async.AnimationDelay.prototype.isActive = function() { + return this.id_ != null; }; /** - * @return {boolean} Whether to ignore case. + * Invokes the callback function after the delay successfully completes. + * @private */ -goog.Uri.prototype.getIgnoreCase = function() { - return this.ignoreCase_; +goog.async.AnimationDelay.prototype.doAction_ = function() { + if (this.usingListeners_ && this.id_) { + goog.events.unlistenByKey(this.id_); + } + this.id_ = null; + + // We are not using the timestamp returned by requestAnimationFrame + // because it may be either a Date.now-style time or a + // high-resolution time (depending on browser implementation). Using + // goog.now() will ensure that the timestamp used is consistent and + // compatible with goog.fx.Animation. + this.listener_.call(this.handler_, goog.now()); }; -//============================================================================== -// Static members -//============================================================================== +/** @override */ +goog.async.AnimationDelay.prototype.disposeInternal = function() { + this.stop(); + goog.async.AnimationDelay.base(this, 'disposeInternal'); +}; /** - * Creates a uri from the string form. Basically an alias of new goog.Uri(). - * If a Uri object is passed to parse then it will return a clone of the object. - * - * @param {*} uri Raw URI string or instance of Uri - * object. - * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter - * names in #getParameterValue. - * @return {!goog.Uri} The new URI object. + * @return {?function(function(number)): number} The requestAnimationFrame + * function, or null if not available on this browser. + * @private */ -goog.Uri.parse = function(uri, opt_ignoreCase) { - return uri instanceof goog.Uri ? - uri.clone() : new goog.Uri(uri, opt_ignoreCase); +goog.async.AnimationDelay.prototype.getRaf_ = function() { + var win = this.win_; + return win.requestAnimationFrame || + win.webkitRequestAnimationFrame || + win.mozRequestAnimationFrame || + win.oRequestAnimationFrame || + win.msRequestAnimationFrame || + null; }; /** - * Creates a new goog.Uri object from unencoded parts. - * - * @param {?string=} opt_scheme Scheme/protocol or full URI to parse. - * @param {?string=} opt_userInfo username:password. - * @param {?string=} opt_domain www.google.com. - * @param {?number=} opt_port 9830. - * @param {?string=} opt_path /some/path/to/a/file.html. - * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2. - * @param {?string=} opt_fragment The fragment without the #. - * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in - * #getParameterValue. - * - * @return {!goog.Uri} The new URI object. + * @return {?function(number): number} The cancelAnimationFrame function, + * or null if not available on this browser. + * @private */ -goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port, - opt_path, opt_query, opt_fragment, opt_ignoreCase) { - - var uri = new goog.Uri(null, opt_ignoreCase); - - // Only set the parts if they are defined and not empty strings. - opt_scheme && uri.setScheme(opt_scheme); - opt_userInfo && uri.setUserInfo(opt_userInfo); - opt_domain && uri.setDomain(opt_domain); - opt_port && uri.setPort(opt_port); - opt_path && uri.setPath(opt_path); - opt_query && uri.setQueryData(opt_query); - opt_fragment && uri.setFragment(opt_fragment); - - return uri; +goog.async.AnimationDelay.prototype.getCancelRaf_ = function() { + var win = this.win_; + return win.cancelAnimationFrame || + win.cancelRequestAnimationFrame || + win.webkitCancelRequestAnimationFrame || + win.mozCancelRequestAnimationFrame || + win.oCancelRequestAnimationFrame || + win.msCancelRequestAnimationFrame || + null; }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Resolves a relative Uri against a base Uri, accepting both strings and - * Uri objects. + * @fileoverview Provides a function to schedule running a function as soon + * as possible after the current JS execution stops and yields to the event + * loop. * - * @param {*} base Base Uri. - * @param {*} rel Relative Uri. - * @return {!goog.Uri} Resolved uri. */ -goog.Uri.resolve = function(base, rel) { - if (!(base instanceof goog.Uri)) { - base = goog.Uri.parse(base); - } - if (!(rel instanceof goog.Uri)) { - rel = goog.Uri.parse(rel); - } +goog.provide('goog.async.nextTick'); +goog.provide('goog.async.throwException'); - return base.resolve(rel); -}; +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.functions'); +goog.require('goog.labs.userAgent.browser'); /** - * Removes dot segments in given path component, as described in - * RFC 3986, section 5.2.4. - * - * @param {string} path A non-empty path component. - * @return {string} Path component with removed dot segments. + * Throw an item without interrupting the current execution context. For + * example, if processing a group of items in a loop, sometimes it is useful + * to report an error while still allowing the rest of the batch to be + * processed. + * @param {*} exception */ -goog.Uri.removeDotSegments = function(path) { - if (path == '..' || path == '.') { - return ''; - - } else if (!goog.string.contains(path, './') && - !goog.string.contains(path, '/.')) { - // This optimization detects uris which do not contain dot-segments, - // and as a consequence do not require any processing. - return path; - - } else { - var leadingSlash = goog.string.startsWith(path, '/'); - var segments = path.split('/'); - var out = []; - - for (var pos = 0; pos < segments.length; ) { - var segment = segments[pos++]; - - if (segment == '.') { - if (leadingSlash && pos == segments.length) { - out.push(''); - } - } else if (segment == '..') { - if (out.length > 1 || out.length == 1 && out[0] != '') { - out.pop(); - } - if (leadingSlash && pos == segments.length) { - out.push(''); - } - } else { - out.push(segment); - leadingSlash = true; - } - } - - return out.join('/'); - } +goog.async.throwException = function(exception) { + // Each throw needs to be in its own context. + goog.global.setTimeout(function() { throw exception; }, 0); }; /** - * Decodes a value or returns the empty string if it isn't defined or empty. - * @param {string|undefined} val Value to decode. - * @param {boolean=} opt_preserveReserved If true, restricted characters will - * not be decoded. - * @return {string} Decoded value. - * @private + * Fires the provided callbacks as soon as possible after the current JS + * execution context. setTimeout(…, 0) takes at least 4ms when called from + * within another setTimeout(…, 0) for legacy reasons. + * + * This will not schedule the callback as a microtask (i.e. a task that can + * preempt user input or networking callbacks). It is meant to emulate what + * setTimeout(_, 0) would do if it were not throttled. If you desire microtask + * behavior, use {@see goog.Promise} instead. + * + * @param {function(this:SCOPE)} callback Callback function to fire as soon as + * possible. + * @param {SCOPE=} opt_context Object in whose scope to call the listener. + * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that + * ensures correctness at the cost of speed. See comments for details. + * @template SCOPE */ -goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) { - // Don't use UrlDecode() here because val is not a query parameter. - if (!val) { - return ''; +goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) { + var cb = callback; + if (opt_context) { + cb = goog.bind(callback, opt_context); + } + cb = goog.async.nextTick.wrapCallback_(cb); + // window.setImmediate was introduced and currently only supported by IE10+, + // but due to a bug in the implementation it is not guaranteed that + // setImmediate is faster than setTimeout nor that setImmediate N is before + // setImmediate N+1. That is why we do not use the native version if + // available. We do, however, call setImmediate if it is a normal function + // because that indicates that it has been replaced by goog.testing.MockClock + // which we do want to support. + // See + // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10 + // + // Note we do allow callers to also request setImmediate if they are willing + // to accept the possible tradeoffs of incorrectness in exchange for speed. + // The IE fallback of readystate change is much slower. + if (goog.isFunction(goog.global.setImmediate) && + (opt_useSetImmediate || !goog.global.Window || + goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) { + goog.global.setImmediate(cb); + return; } - return opt_preserveReserved ? decodeURI(val) : decodeURIComponent(val); -}; - - -/** - * If unescapedPart is non null, then escapes any characters in it that aren't - * valid characters in a url and also escapes any special characters that - * appear in extra. - * - * @param {*} unescapedPart The string to encode. - * @param {RegExp} extra A character set of characters in [\01-\177]. - * @param {boolean=} opt_removeDoubleEncoding If true, remove double percent - * encoding. - * @return {?string} null iff unescapedPart == null. - * @private - */ -goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra, - opt_removeDoubleEncoding) { - if (goog.isString(unescapedPart)) { - var encoded = encodeURI(unescapedPart). - replace(extra, goog.Uri.encodeChar_); - if (opt_removeDoubleEncoding) { - // encodeURI double-escapes %XX sequences used to represent restricted - // characters in some URI components, remove the double escaping here. - encoded = goog.Uri.removeDoubleEncoding_(encoded); - } - return encoded; + // Look for and cache the custom fallback version of setImmediate. + if (!goog.async.nextTick.setImmediate_) { + goog.async.nextTick.setImmediate_ = + goog.async.nextTick.getSetImmediateEmulator_(); } - return null; + goog.async.nextTick.setImmediate_(cb); }; /** - * Converts a character in [\01-\177] to its unicode character equivalent. - * @param {string} ch One character string. - * @return {string} Encoded string. + * Cache for the setImmediate implementation. + * @type {function(function())} * @private */ -goog.Uri.encodeChar_ = function(ch) { - var n = ch.charCodeAt(0); - return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16); -}; +goog.async.nextTick.setImmediate_; /** - * Removes double percent-encoding from a string. - * @param {string} doubleEncodedString String - * @return {string} String with double encoding removed. + * Determines the best possible implementation to run a function as soon as + * the JS event loop is idle. + * @return {function(function())} The "setImmediate" implementation. * @private */ -goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) { - return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1'); +goog.async.nextTick.getSetImmediateEmulator_ = function() { + // Create a private message channel and use it to postMessage empty messages + // to ourselves. + var Channel = goog.global['MessageChannel']; + // If MessageChannel is not available and we are in a browser, implement + // an iframe based polyfill in browsers that have postMessage and + // document.addEventListener. The latter excludes IE8 because it has a + // synchronous postMessage implementation. + if (typeof Channel === 'undefined' && typeof window !== 'undefined' && + window.postMessage && window.addEventListener) { + /** @constructor */ + Channel = function() { + // Make an empty, invisible iframe. + var iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = ''; + document.documentElement.appendChild(iframe); + var win = iframe.contentWindow; + var doc = win.document; + doc.open(); + doc.write(''); + doc.close(); + // Do not post anything sensitive over this channel, as the workaround for + // pages with file: origin could allow that information to be modified or + // intercepted. + var message = 'callImmediate' + Math.random(); + // The same origin policy rejects attempts to postMessage from file: urls + // unless the origin is '*'. + // TODO(b/16335441): Use '*' origin for data: and other similar protocols. + var origin = win.location.protocol == 'file:' ? + '*' : win.location.protocol + '//' + win.location.host; + var onmessage = goog.bind(function(e) { + // Validate origin and message to make sure that this message was + // intended for us. If the origin is set to '*' (see above) only the + // message needs to match since, for example, '*' != 'file://'. Allowing + // the wildcard is ok, as we are not concerned with security here. + if ((origin != '*' && e.origin != origin) || e.data != message) { + return; + } + this['port1'].onmessage(); + }, this); + win.addEventListener('message', onmessage, false); + this['port1'] = {}; + this['port2'] = { + postMessage: function() { + win.postMessage(message, origin); + } + }; + }; + } + if (typeof Channel !== 'undefined' && + (!goog.labs.userAgent.browser.isIE())) { + // Exclude all of IE due to + // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ + // which allows starving postMessage with a busy setTimeout loop. + // This currently affects IE10 and IE11 which would otherwise be able + // to use the postMessage based fallbacks. + var channel = new Channel(); + // Use a fifo linked list to call callbacks in the right order. + var head = {}; + var tail = head; + channel['port1'].onmessage = function() { + if (goog.isDef(head.next)) { + head = head.next; + var cb = head.cb; + head.cb = null; + cb(); + } + }; + return function(cb) { + tail.next = { + cb: cb + }; + tail = tail.next; + channel['port2'].postMessage(0); + }; + } + // Implementation for IE6+: Script elements fire an asynchronous + // onreadystatechange event when inserted into the DOM. + if (typeof document !== 'undefined' && 'onreadystatechange' in + document.createElement('script')) { + return function(cb) { + var script = document.createElement('script'); + script.onreadystatechange = function() { + // Clean up and call the callback. + script.onreadystatechange = null; + script.parentNode.removeChild(script); + script = null; + cb(); + cb = null; + }; + document.documentElement.appendChild(script); + }; + } + // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms + // or more. + return function(cb) { + goog.global.setTimeout(cb, 0); + }; }; /** - * Regular expression for characters that are disallowed in the scheme or - * userInfo part of the URI. - * @type {RegExp} - * @private - */ -goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g; - - -/** - * Regular expression for characters that are disallowed in a relative path. - * Colon is included due to RFC 3986 3.3. - * @type {RegExp} - * @private - */ -goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g; - - -/** - * Regular expression for characters that are disallowed in an absolute path. - * @type {RegExp} + * Helper function that is overrided to protect callbacks with entry point + * monitor if the application monitors entry points. + * @param {function()} callback Callback function to fire as soon as possible. + * @return {function()} The wrapped callback. * @private */ -goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g; +goog.async.nextTick.wrapCallback_ = goog.functions.identity; -/** - * Regular expression for characters that are disallowed in the query. - * @type {RegExp} - * @private - */ -goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g; +// Register the callback function as an entry point, so that it can be +// monitored for exception handling, etc. This has to be done in this file +// since it requires special code to handle all browsers. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.async.nextTick.wrapCallback_ = transformer; + }); +// Copyright 2014 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Regular expression for characters that are disallowed in the fragment. - * @type {RegExp} - * @private + * @fileoverview The SafeScript type and its builders. + * + * TODO(user): Link to document stating type contract. */ -goog.Uri.reDisallowedInFragment_ = /#/g; +goog.provide('goog.html.SafeScript'); -/** - * Checks whether two URIs have the same domain. - * @param {string} uri1String First URI string. - * @param {string} uri2String Second URI string. - * @return {boolean} true if the two URIs have the same domain; false otherwise. - */ -goog.Uri.haveSameDomain = function(uri1String, uri2String) { - // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme. - // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain. - var pieces1 = goog.uri.utils.split(uri1String); - var pieces2 = goog.uri.utils.split(uri2String); - return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == - pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && - pieces1[goog.uri.utils.ComponentIndex.PORT] == - pieces2[goog.uri.utils.ComponentIndex.PORT]; -}; +goog.require('goog.asserts'); +goog.require('goog.string.Const'); +goog.require('goog.string.TypedString'); /** - * Class used to represent URI query parameters. It is essentially a hash of - * name-value pairs, though a name can be present more than once. + * A string-like object which represents JavaScript code and that carries the + * security type contract that its value, as a string, will not cause execution + * of unconstrained attacker controlled code (XSS) when evaluated as JavaScript + * in a browser. * - * Has the same interface as the collections in goog.structs. + * Instances of this type must be created via the factory method + * {@code goog.html.SafeScript.fromConstant} and not by invoking its + * constructor. The constructor intentionally takes no parameters and the type + * is immutable; hence only a default instance corresponding to the empty string + * can be obtained via constructor invocation. * - * @param {?string=} opt_query Optional encoded query string to parse into - * the object. - * @param {goog.Uri=} opt_uri Optional uri object that should have its - * cache invalidated when this object updates. Deprecated -- this - * is no longer required. - * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter - * name in #get. + * A SafeScript's string representation can safely be interpolated as the + * content of a script element within HTML. The SafeScript string should not be + * escaped before interpolation. + * + * Note that the SafeScript might contain text that is attacker-controlled but + * that text should have been interpolated with appropriate escaping, + * sanitization and/or validation into the right location in the script, such + * that it is highly constrained in its effect (for example, it had to match a + * set of whitelisted words). + * + * A SafeScript can be constructed via security-reviewed unchecked + * conversions. In this case producers of SafeScript must ensure themselves that + * the SafeScript does not contain unsafe script. Note in particular that + * {@code <} is dangerous, even when inside JavaScript strings, and so should + * always be forbidden or JavaScript escaped in user controlled input. For + * example, if {@code </script><script>evil</script>"} were + * interpolated inside a JavaScript string, it would break out of the context + * of the original script element and {@code evil} would execute. Also note + * that within an HTML script (raw text) element, HTML character references, + * such as "<" are not allowed. See + * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements. + * + * @see goog.html.SafeScript#fromConstant * @constructor * @final + * @struct + * @implements {goog.string.TypedString} */ -goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) { +goog.html.SafeScript = function() { /** - * Encoded query string, or null if it requires computing from the key map. - * @type {?string} - * @private + * The contained value of this SafeScript. The field has a purposely + * ugly name to make (non-compiled) code that attempts to directly access this + * field stand out. + * @private {string} */ - this.encodedQuery_ = opt_query || null; + this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = ''; /** - * If true, ignore the case of the parameter name in #get. - * @type {boolean} + * A type marker used to implement additional run-time type checking. + * @see goog.html.SafeScript#unwrap + * @const * @private */ - this.ignoreCase_ = !!opt_ignoreCase; + this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = + goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; }; /** - * If the underlying key map is not yet initialized, it parses the - * query string and fills the map with parsed data. + * @override + * @const + */ +goog.html.SafeScript.prototype.implementsGoogStringTypedString = true; + + +/** + * Type marker for the SafeScript type, used to implement additional + * run-time type checking. + * @const * @private */ -goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() { - if (!this.keyMap_) { - this.keyMap_ = new goog.structs.Map(); - this.count_ = 0; +goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; - if (this.encodedQuery_) { - var pairs = this.encodedQuery_.split('&'); - for (var i = 0; i < pairs.length; i++) { - var indexOfEquals = pairs[i].indexOf('='); - var name = null; - var value = null; - if (indexOfEquals >= 0) { - name = pairs[i].substring(0, indexOfEquals); - value = pairs[i].substring(indexOfEquals + 1); - } else { - name = pairs[i]; - } - name = goog.string.urlDecode(name); - name = this.getKeyName_(name); - this.add(name, value ? goog.string.urlDecode(value) : ''); - } - } + +/** + * Creates a SafeScript object from a compile-time constant string. + * + * @param {!goog.string.Const} script A compile-time-constant string from which + * to create a SafeScript. + * @return {!goog.html.SafeScript} A SafeScript object initialized to + * {@code script}. + */ +goog.html.SafeScript.fromConstant = function(script) { + var scriptString = goog.string.Const.unwrap(script); + if (scriptString.length === 0) { + return goog.html.SafeScript.EMPTY; } + return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( + scriptString); }; /** - * Creates a new query data instance from a map of names and values. + * Returns this SafeScript's value as a string. * - * @param {!goog.structs.Map<string, ?>|!Object} map Map of string parameter - * names to parameter value. If parameter value is an array, it is - * treated as if the key maps to each individual value in the - * array. - * @param {goog.Uri=} opt_uri URI object that should have its cache - * invalidated when this object updates. - * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter - * name in #get. - * @return {!goog.Uri.QueryData} The populated query data instance. + * IMPORTANT: In code where it is security relevant that an object's type is + * indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of + * this method. If in doubt, assume that it's security relevant. In particular, + * note that goog.html functions which return a goog.html type do not guarantee + * the returned instance is of the right type. For example: + * + * <pre> + * var fakeSafeHtml = new String('fake'); + * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; + * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); + * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by + * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml + * // instanceof goog.html.SafeHtml. + * </pre> + * + * @see goog.html.SafeScript#unwrap + * @override */ -goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) { - var keys = goog.structs.getKeys(map); - if (typeof keys == 'undefined') { - throw Error('Keys are undefined'); - } - - var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase); - var values = goog.structs.getValues(map); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var value = values[i]; - if (!goog.isArray(value)) { - queryData.add(key, value); - } else { - queryData.setValues(key, value); - } - } - return queryData; +goog.html.SafeScript.prototype.getTypedStringValue = function() { + return this.privateDoNotAccessOrElseSafeScriptWrappedValue_; }; +if (goog.DEBUG) { + /** + * Returns a debug string-representation of this value. + * + * To obtain the actual string value wrapped in a SafeScript, use + * {@code goog.html.SafeScript.unwrap}. + * + * @see goog.html.SafeScript#unwrap + * @override + */ + goog.html.SafeScript.prototype.toString = function() { + return 'SafeScript{' + + this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}'; + }; +} + + /** - * Creates a new query data instance from parallel arrays of parameter names - * and values. Allows for duplicate parameter names. Throws an error if the - * lengths of the arrays differ. + * Performs a runtime check that the provided object is indeed a + * SafeScript object, and returns its value. * - * @param {!Array<string>} keys Parameter names. - * @param {!Array<?>} values Parameter values. - * @param {goog.Uri=} opt_uri URI object that should have its cache - * invalidated when this object updates. - * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter - * name in #get. - * @return {!goog.Uri.QueryData} The populated query data instance. + * @param {!goog.html.SafeScript} safeScript The object to extract from. + * @return {string} The safeScript object's contained string, unless + * the run-time type check fails. In that case, {@code unwrap} returns an + * innocuous string, or, if assertions are enabled, throws + * {@code goog.asserts.AssertionError}. */ -goog.Uri.QueryData.createFromKeysValues = function( - keys, values, opt_uri, opt_ignoreCase) { - if (keys.length != values.length) { - throw Error('Mismatched lengths for keys/values'); - } - var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase); - for (var i = 0; i < keys.length; i++) { - queryData.add(keys[i], values[i]); +goog.html.SafeScript.unwrap = function(safeScript) { + // Perform additional Run-time type-checking to ensure that + // safeScript is indeed an instance of the expected type. This + // provides some additional protection against security bugs due to + // application code that disables type checks. + // Specifically, the following checks are performed: + // 1. The object is an instance of the expected type. + // 2. The object is not an instance of a subclass. + // 3. The object carries a type marker for the expected type. "Faking" an + // object requires a reference to the type marker, which has names intended + // to stand out in code reviews. + if (safeScript instanceof goog.html.SafeScript && + safeScript.constructor === goog.html.SafeScript && + safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === + goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { + return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_; + } else { + goog.asserts.fail( + 'expected object of type SafeScript, got \'' + safeScript + '\''); + return 'type_error:SafeScript'; } - return queryData; }; /** - * The map containing name/value or name/array-of-values pairs. - * May be null if it requires parsing from the query string. - * - * We need to use a Map because we cannot guarantee that the key names will - * not be problematic for IE. + * Package-internal utility method to create SafeScript instances. * - * @private {goog.structs.Map<string, !Array<*>>} + * @param {string} script The string to initialize the SafeScript object with. + * @return {!goog.html.SafeScript} The initialized SafeScript object. + * @package */ -goog.Uri.QueryData.prototype.keyMap_ = null; +goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse = + function(script) { + var safeScript = new goog.html.SafeScript(); + safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script; + return safeScript; +}; /** - * The number of params, or null if it requires computing. - * @type {?number} - * @private + * A SafeScript instance corresponding to the empty string. + * @const {!goog.html.SafeScript} */ -goog.Uri.QueryData.prototype.count_ = null; +goog.html.SafeScript.EMPTY = + goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(''); +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @return {?number} The number of parameters. + * @fileoverview Unchecked conversions to create values of goog.html types from + * plain strings. Use of these functions could potentially result in instances + * of goog.html types that violate their type contracts, and hence result in + * security vulnerabilties. + * + * Therefore, all uses of the methods herein must be carefully security + * reviewed. Avoid use of the methods in this file whenever possible; instead + * prefer to create instances of goog.html types using inherently safe builders + * or template systems. + * + * + * @visibility {//closure/goog/html:approved_for_unchecked_conversion} + * @visibility {//closure/goog/bin/sizetests:__pkg__} */ -goog.Uri.QueryData.prototype.getCount = function() { - this.ensureKeyMapInitialized_(); - return this.count_; -}; -/** - * Adds a key value pair. - * @param {string} key Name. - * @param {*} value Value. - * @return {!goog.Uri.QueryData} Instance of this object. - */ -goog.Uri.QueryData.prototype.add = function(key, value) { - this.ensureKeyMapInitialized_(); - this.invalidateCache_(); +goog.provide('goog.html.uncheckedconversions'); - key = this.getKeyName_(key); - var values = this.keyMap_.get(key); - if (!values) { - this.keyMap_.set(key, (values = [])); - } - values.push(value); - this.count_++; - return this; -}; +goog.require('goog.asserts'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeScript'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.html.SafeStyleSheet'); +goog.require('goog.html.SafeUrl'); +goog.require('goog.html.TrustedResourceUrl'); +goog.require('goog.string'); +goog.require('goog.string.Const'); /** - * Removes all the params with the given key. - * @param {string} key Name. - * @return {boolean} Whether any parameter was removed. + * Performs an "unchecked conversion" to SafeHtml from a plain string that is + * known to satisfy the SafeHtml type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code html} satisfies the SafeHtml type contract in all + * possible program states. + * + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} html A string that is claimed to adhere to the SafeHtml + * contract. + * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the + * SafeHtml to be constructed. A null or undefined value signifies an + * unknown directionality. + * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml + * object. + * @suppress {visibility} For access to SafeHtml.create... Note that this + * use is appropriate since this method is intended to be "package private" + * withing goog.html. DO NOT call SafeHtml.create... from outside this + * package; use appropriate wrappers instead. */ -goog.Uri.QueryData.prototype.remove = function(key) { - this.ensureKeyMapInitialized_(); - - key = this.getKeyName_(key); - if (this.keyMap_.containsKey(key)) { - this.invalidateCache_(); - - // Decrement parameter count. - this.count_ -= this.keyMap_.get(key).length; - return this.keyMap_.remove(key); - } - return false; +goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract = + function(justification, html, opt_dir) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString(goog.string.Const.unwrap(justification), + 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( + html, opt_dir || null); }; /** - * Clears the parameters. + * Performs an "unchecked conversion" to SafeScript from a plain string that is + * known to satisfy the SafeScript type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code script} satisfies the SafeScript type contract in + * all possible program states. + * + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} script The string to wrap as a SafeScript. + * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a + * SafeScript object. */ -goog.Uri.QueryData.prototype.clear = function() { - this.invalidateCache_(); - this.keyMap_ = null; - this.count_ = 0; +goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract = + function(justification, script) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString(goog.string.Const.unwrap(justification), + 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmpty(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( + script); }; /** - * @return {boolean} Whether we have any parameters. + * Performs an "unchecked conversion" to SafeStyle from a plain string that is + * known to satisfy the SafeStyle type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code style} satisfies the SafeUrl type contract in all + * possible program states. + * + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} style The string to wrap as a SafeStyle. + * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a + * SafeStyle object. */ -goog.Uri.QueryData.prototype.isEmpty = function() { - this.ensureKeyMapInitialized_(); - return this.count_ == 0; +goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract = + function(justification, style) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString(goog.string.Const.unwrap(justification), + 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( + style); }; /** - * Whether there is a parameter with the given name - * @param {string} key The parameter name to check for. - * @return {boolean} Whether there is a parameter with the given name. + * Performs an "unchecked conversion" to SafeStyleSheet from a plain string + * that is known to satisfy the SafeStyleSheet type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code styleSheet} satisfies the SafeUrl type contract in + * all possible program states. + * + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} styleSheet The string to wrap as a SafeStyleSheet. + * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped + * in a SafeStyleSheet object. */ -goog.Uri.QueryData.prototype.containsKey = function(key) { - this.ensureKeyMapInitialized_(); - key = this.getKeyName_(key); - return this.keyMap_.containsKey(key); +goog.html.uncheckedconversions. + safeStyleSheetFromStringKnownToSatisfyTypeContract = + function(justification, styleSheet) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString(goog.string.Const.unwrap(justification), + 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeStyleSheet. + createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet); }; /** - * Whether there is a parameter with the given value. - * @param {*} value The value to check for. - * @return {boolean} Whether there is a parameter with the given value. + * Performs an "unchecked conversion" to SafeUrl from a plain string that is + * known to satisfy the SafeUrl type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code url} satisfies the SafeUrl type contract in all + * possible program states. + * + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} url The string to wrap as a SafeUrl. + * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl + * object. */ -goog.Uri.QueryData.prototype.containsValue = function(value) { - // NOTE(arv): This solution goes through all the params even if it was the - // first param. We can get around this by not reusing code or by switching to - // iterators. - var vals = this.getValues(); - return goog.array.contains(vals, value); +goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract = + function(justification, url) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString(goog.string.Const.unwrap(justification), + 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); }; /** - * Returns all the keys of the parameters. If a key is used multiple times - * it will be included multiple times in the returned array - * @return {!Array<string>} All the keys of the parameters. + * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string + * that is known to satisfy the TrustedResourceUrl type contract. + * + * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure + * that the value of {@code url} satisfies the TrustedResourceUrl type contract + * in all possible program states. + * + * + * @param {!goog.string.Const} justification A constant string explaining why + * this use of this method is safe. May include a security review ticket + * number. + * @param {string} url The string to wrap as a TrustedResourceUrl. + * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in + * a TrustedResourceUrl object. */ -goog.Uri.QueryData.prototype.getKeys = function() { - this.ensureKeyMapInitialized_(); - // We need to get the values to know how many keys to add. - var vals = /** @type {!Array<*>} */ (this.keyMap_.getValues()); - var keys = this.keyMap_.getKeys(); - var rv = []; - for (var i = 0; i < keys.length; i++) { - var val = vals[i]; - for (var j = 0; j < val.length; j++) { - rv.push(keys[i]); - } - } - return rv; +goog.html.uncheckedconversions. + trustedResourceUrlFromStringKnownToSatisfyTypeContract = + function(justification, url) { + // unwrap() called inside an assert so that justification can be optimized + // away in production code. + goog.asserts.assertString(goog.string.Const.unwrap(justification), + 'must provide justification'); + goog.asserts.assert( + !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), + 'must provide non-empty justification'); + return goog.html.TrustedResourceUrl. + createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url); }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Returns all the values of the parameters with the given name. If the query - * data has no such key this will return an empty array. If no key is given - * all values wil be returned. - * @param {string=} opt_key The name of the parameter to get the values for. - * @return {!Array<?>} All the values of the parameters with the given name. + * @fileoverview Generics method for collection-like classes and objects. + * + * @author arv@google.com (Erik Arvidsson) + * + * This file contains functions to work with collections. It supports using + * Map, Set, Array and Object and other classes that implement collection-like + * methods. */ -goog.Uri.QueryData.prototype.getValues = function(opt_key) { - this.ensureKeyMapInitialized_(); - var rv = []; - if (goog.isString(opt_key)) { - if (this.containsKey(opt_key)) { - rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key))); - } - } else { - // Return all values. - var values = this.keyMap_.getValues(); - for (var i = 0; i < values.length; i++) { - rv = goog.array.concat(rv, values[i]); - } - } - return rv; -}; -/** - * Sets a key value pair and removes all other keys with the same value. - * - * @param {string} key Name. - * @param {*} value Value. - * @return {!goog.Uri.QueryData} Instance of this object. - */ -goog.Uri.QueryData.prototype.set = function(key, value) { - this.ensureKeyMapInitialized_(); - this.invalidateCache_(); +goog.provide('goog.structs'); - // TODO(chrishenry): This could be better written as - // this.remove(key), this.add(key, value), but that would reorder - // the key (since the key is first removed and then added at the - // end) and we would have to fix unit tests that depend on key - // ordering. - key = this.getKeyName_(key); - if (this.containsKey(key)) { - this.count_ -= this.keyMap_.get(key).length; - } - this.keyMap_.set(key, [value]); - this.count_++; - return this; -}; +goog.require('goog.array'); +goog.require('goog.object'); -/** - * Returns the first value associated with the key. If the query data has no - * such key this will return undefined or the optional default. - * @param {string} key The name of the parameter to get the value for. - * @param {*=} opt_default The default value to return if the query data - * has no such key. - * @return {*} The first string value associated with the key, or opt_default - * if there's no value. - */ -goog.Uri.QueryData.prototype.get = function(key, opt_default) { - var values = key ? this.getValues(key) : []; - if (goog.Uri.preserveParameterTypesCompatibilityFlag) { - return values.length > 0 ? values[0] : opt_default; - } else { - return values.length > 0 ? String(values[0]) : opt_default; - } -}; +// We treat an object as a dictionary if it has getKeys or it is an object that +// isn't arrayLike. /** - * Sets the values for a key. If the key already exists, this will - * override all of the existing values that correspond to the key. - * @param {string} key The key to set values for. - * @param {!Array<?>} values The values to set. + * Returns the number of values in the collection-like object. + * @param {Object} col The collection-like object. + * @return {number} The number of values in the collection-like object. */ -goog.Uri.QueryData.prototype.setValues = function(key, values) { - this.remove(key); - - if (values.length > 0) { - this.invalidateCache_(); - this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values)); - this.count_ += values.length; +goog.structs.getCount = function(col) { + if (typeof col.getCount == 'function') { + return col.getCount(); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return col.length; } + return goog.object.getCount(col); }; /** - * @return {string} Encoded query string. - * @override + * Returns the values of the collection-like object. + * @param {Object} col The collection-like object. + * @return {!Array<?>} The values in the collection-like object. */ -goog.Uri.QueryData.prototype.toString = function() { - if (this.encodedQuery_) { - return this.encodedQuery_; +goog.structs.getValues = function(col) { + if (typeof col.getValues == 'function') { + return col.getValues(); } - - if (!this.keyMap_) { - return ''; + if (goog.isString(col)) { + return col.split(''); + } + if (goog.isArrayLike(col)) { + var rv = []; + var l = col.length; + for (var i = 0; i < l; i++) { + rv.push(col[i]); + } + return rv; } + return goog.object.getValues(col); +}; - var sb = []; - // In the past, we use this.getKeys() and this.getVals(), but that - // generates a lot of allocations as compared to simply iterating - // over the keys. - var keys = this.keyMap_.getKeys(); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var encodedKey = goog.string.urlEncode(key); - var val = this.getValues(key); - for (var j = 0; j < val.length; j++) { - var param = encodedKey; - // Ensure that null and undefined are encoded into the url as - // literal strings. - if (val[j] !== '') { - param += '=' + goog.string.urlEncode(val[j]); - } - sb.push(param); +/** + * Returns the keys of the collection. Some collections have no notion of + * keys/indexes and this function will return undefined in those cases. + * @param {Object} col The collection-like object. + * @return {!Array|undefined} The keys in the collection. + */ +goog.structs.getKeys = function(col) { + if (typeof col.getKeys == 'function') { + return col.getKeys(); + } + // if we have getValues but no getKeys we know this is a key-less collection + if (typeof col.getValues == 'function') { + return undefined; + } + if (goog.isArrayLike(col) || goog.isString(col)) { + var rv = []; + var l = col.length; + for (var i = 0; i < l; i++) { + rv.push(i); } + return rv; } - return this.encodedQuery_ = sb.join('&'); + return goog.object.getKeys(col); }; /** - * @return {string} Decoded query string. + * Whether the collection contains the given value. This is O(n) and uses + * equals (==) to test the existence. + * @param {Object} col The collection-like object. + * @param {*} val The value to check for. + * @return {boolean} True if the map contains the value. */ -goog.Uri.QueryData.prototype.toDecodedString = function() { - return goog.Uri.decodeOrEmpty_(this.toString()); +goog.structs.contains = function(col, val) { + if (typeof col.contains == 'function') { + return col.contains(val); + } + if (typeof col.containsValue == 'function') { + return col.containsValue(val); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.contains(/** @type {!Array<?>} */ (col), val); + } + return goog.object.containsValue(col, val); }; /** - * Invalidate the cache. - * @private + * Whether the collection is empty. + * @param {Object} col The collection-like object. + * @return {boolean} True if empty. */ -goog.Uri.QueryData.prototype.invalidateCache_ = function() { - this.encodedQuery_ = null; +goog.structs.isEmpty = function(col) { + if (typeof col.isEmpty == 'function') { + return col.isEmpty(); + } + + // We do not use goog.string.isEmptyOrWhitespace because here we treat the string as + // collection and as such even whitespace matters + + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.isEmpty(/** @type {!Array<?>} */ (col)); + } + return goog.object.isEmpty(col); }; /** - * Removes all keys that are not in the provided list. (Modifies this object.) - * @param {Array<string>} keys The desired keys. - * @return {!goog.Uri.QueryData} a reference to this object. + * Removes all the elements from the collection. + * @param {Object} col The collection-like object. */ -goog.Uri.QueryData.prototype.filterKeys = function(keys) { - this.ensureKeyMapInitialized_(); - this.keyMap_.forEach( - function(value, key) { - if (!goog.array.contains(keys, key)) { - this.remove(key); - } - }, this); - return this; +goog.structs.clear = function(col) { + // NOTE(arv): This should not contain strings because strings are immutable + if (typeof col.clear == 'function') { + col.clear(); + } else if (goog.isArrayLike(col)) { + goog.array.clear(/** @type {goog.array.ArrayLike} */ (col)); + } else { + goog.object.clear(col); + } }; /** - * Clone the query data instance. - * @return {!goog.Uri.QueryData} New instance of the QueryData object. + * Calls a function for each value in a collection. The function takes + * three arguments; the value, the key and the collection. + * + * NOTE: This will be deprecated soon! Please use a more specific method if + * possible, e.g. goog.array.forEach, goog.object.forEach, etc. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):?} f The function to call for every value. + * This function takes + * 3 arguments (the value, the key or undefined if the collection has no + * notion of keys, and the collection) and the return value is irrelevant. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @template T,S */ -goog.Uri.QueryData.prototype.clone = function() { - var rv = new goog.Uri.QueryData(); - rv.encodedQuery_ = this.encodedQuery_; - if (this.keyMap_) { - rv.keyMap_ = this.keyMap_.clone(); - rv.count_ = this.count_; +goog.structs.forEach = function(col, f, opt_obj) { + if (typeof col.forEach == 'function') { + col.forEach(f, opt_obj); + } else if (goog.isArrayLike(col) || goog.isString(col)) { + goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj); + } else { + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + f.call(opt_obj, values[i], keys && keys[i], col); + } + } +}; + + +/** + * Calls a function for every value in the collection. When a call returns true, + * adds the value to a new collection (Array is returned by default). + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes + * 3 arguments (the value, the key or undefined if the collection has no + * notion of keys, and the collection) and should return a Boolean. If the + * return value is true the value is added to the result collection. If it + * is false the value is not included. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {!Object|!Array<?>} A new collection where the passed values are + * present. If col is a key-less collection an array is returned. If col + * has keys and values a plain old JS object is returned. + * @template T,S + */ +goog.structs.filter = function(col, f, opt_obj) { + if (typeof col.filter == 'function') { + return col.filter(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj); + } + + var rv; + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + if (keys) { + rv = {}; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], keys[i], col)) { + rv[keys[i]] = values[i]; + } + } + } else { + // We should not use goog.array.filter here since we want to make sure that + // the index is undefined as well as make sure that col is passed to the + // function. + rv = []; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], undefined, col)) { + rv.push(values[i]); + } + } } return rv; }; /** - * Helper function to get the key name from a JavaScript object. Converts - * the object to a string, and to lower case if necessary. - * @private - * @param {*} arg The object to get a key name from. - * @return {string} valid key name which can be looked up in #keyMap_. + * Calls a function for every value in the collection and adds the result into a + * new collection (defaults to creating a new Array). + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):V} f The function to call for every value. + * This function takes 3 arguments (the value, the key or undefined if the + * collection has no notion of keys, and the collection) and should return + * something. The result will be used as the value in the new collection. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {!Object<V>|!Array<V>} A new collection with the new values. If + * col is a key-less collection an array is returned. If col has keys and + * values a plain old JS object is returned. + * @template T,S,V */ -goog.Uri.QueryData.prototype.getKeyName_ = function(arg) { - var keyName = String(arg); - if (this.ignoreCase_) { - keyName = keyName.toLowerCase(); +goog.structs.map = function(col, f, opt_obj) { + if (typeof col.map == 'function') { + return col.map(f, opt_obj); } - return keyName; + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj); + } + + var rv; + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + if (keys) { + rv = {}; + for (var i = 0; i < l; i++) { + rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col); + } + } else { + // We should not use goog.array.map here since we want to make sure that + // the index is undefined as well as make sure that col is passed to the + // function. + rv = []; + for (var i = 0; i < l; i++) { + rv[i] = f.call(opt_obj, values[i], undefined, col); + } + } + return rv; }; /** - * Ignore case in parameter names. - * NOTE: If there are already key/value pairs in the QueryData, and - * ignoreCase_ is set to false, the keys will all be lower-cased. - * @param {boolean} ignoreCase whether this goog.Uri should ignore case. + * Calls f for each value in a collection. If any call returns true this returns + * true (without checking the rest). If all returns false this returns false. + * + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes 3 arguments (the value, the key or undefined + * if the collection has no notion of keys, and the collection) and should + * return a boolean. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {boolean} True if any value passes the test. + * @template T,S */ -goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) { - var resetKeys = ignoreCase && !this.ignoreCase_; - if (resetKeys) { - this.ensureKeyMapInitialized_(); - this.invalidateCache_(); - this.keyMap_.forEach( - function(value, key) { - var lowerCase = key.toLowerCase(); - if (key != lowerCase) { - this.remove(key); - this.setValues(lowerCase, value); - } - }, this); +goog.structs.some = function(col, f, opt_obj) { + if (typeof col.some == 'function') { + return col.some(f, opt_obj); } - this.ignoreCase_ = ignoreCase; + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj); + } + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + if (f.call(opt_obj, values[i], keys && keys[i], col)) { + return true; + } + } + return false; }; /** - * Extends a query data object with another query data or map like object. This - * operates 'in-place', it does not create a new QueryData object. + * Calls f for each value in a collection. If all calls return true this return + * true this returns true. If any returns false this returns false at this point + * and does not continue to check the remaining values. * - * @param {...(goog.Uri.QueryData|goog.structs.Map<?, ?>|Object)} var_args - * The object from which key value pairs will be copied. + * @param {S} col The collection-like object. + * @param {function(this:T,?,?,S):boolean} f The function to call for every + * value. This function takes 3 arguments (the value, the key or + * undefined if the collection has no notion of keys, and the collection) + * and should return a boolean. + * @param {T=} opt_obj The object to be used as the value of 'this' + * within {@code f}. + * @return {boolean} True if all key-value pairs pass the test. + * @template T,S */ -goog.Uri.QueryData.prototype.extend = function(var_args) { - for (var i = 0; i < arguments.length; i++) { - var data = arguments[i]; - goog.structs.forEach(data, - /** @this {goog.Uri.QueryData} */ - function(value, key) { - this.add(key, value); - }, this); +goog.structs.every = function(col, f, opt_obj) { + if (typeof col.every == 'function') { + return col.every(f, opt_obj); + } + if (goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj); + } + var keys = goog.structs.getKeys(col); + var values = goog.structs.getValues(col); + var l = values.length; + for (var i = 0; i < l; i++) { + if (!f.call(opt_obj, values[i], keys && keys[i], col)) { + return false; + } } + return true; }; -// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// Copyright 2011 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -39566,1269 +40224,1353 @@ goog.Uri.QueryData.prototype.extend = function(var_args) { // limitations under the License. /** - * @fileoverview A delayed callback that pegs to the next animation frame - * instead of a user-configurable timeout. + * @fileoverview Defines the collection interface. * - * @author nicksantos@google.com (Nick Santos) + * @author nnaze@google.com (Nathan Naze) */ -goog.provide('goog.async.AnimationDelay'); +goog.provide('goog.structs.Collection'); -goog.require('goog.Disposable'); -goog.require('goog.events'); -goog.require('goog.functions'); +/** + * An interface for a collection of values. + * @interface + * @template T + */ +goog.structs.Collection = function() {}; -// TODO(nicksantos): Should we factor out the common code between this and -// goog.async.Delay? I'm not sure if there's enough code for this to really -// make sense. Subclassing seems like the wrong approach for a variety of -// reasons. Maybe there should be a common interface? +/** + * @param {T} value Value to add to the collection. + */ +goog.structs.Collection.prototype.add; /** - * A delayed callback that pegs to the next animation frame - * instead of a user configurable timeout. By design, this should have - * the same interface as goog.async.Delay. - * - * Uses requestAnimationFrame and friends when available, but falls - * back to a timeout of goog.async.AnimationDelay.TIMEOUT. - * - * For more on requestAnimationFrame and how you can use it to create smoother - * animations, see: - * @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - * - * @param {function(number)} listener Function to call when the delay completes. - * Will be passed the timestamp when it's called, in unix ms. - * @param {Window=} opt_window The window object to execute the delay in. - * Defaults to the global object. - * @param {Object=} opt_handler The object scope to invoke the function in. - * @constructor - * @extends {goog.Disposable} - * @final + * @param {T} value Value to remove from the collection. */ -goog.async.AnimationDelay = function(listener, opt_window, opt_handler) { - goog.async.AnimationDelay.base(this, 'constructor'); +goog.structs.Collection.prototype.remove; - /** - * The function that will be invoked after a delay. - * @type {function(number)} - * @private - */ - this.listener_ = listener; - /** - * The object context to invoke the callback in. - * @type {Object|undefined} - * @private - */ - this.handler_ = opt_handler; +/** + * @param {T} value Value to find in the collection. + * @return {boolean} Whether the collection contains the specified value. + */ +goog.structs.Collection.prototype.contains; - /** - * @type {Window} - * @private - */ - this.win_ = opt_window || window; - /** - * Cached callback function invoked when the delay finishes. - * @type {function()} - * @private - */ - this.callback_ = goog.bind(this.doAction_, this); -}; -goog.inherits(goog.async.AnimationDelay, goog.Disposable); +/** + * @return {number} The number of values stored in the collection. + */ +goog.structs.Collection.prototype.getCount; + +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Identifier of the active delay timeout, or event listener, - * or null when inactive. - * @type {goog.events.Key|number|null} - * @private + * @fileoverview Python style iteration utilities. + * @author arv@google.com (Erik Arvidsson) */ -goog.async.AnimationDelay.prototype.id_ = null; + + +goog.provide('goog.iter'); +goog.provide('goog.iter.Iterable'); +goog.provide('goog.iter.Iterator'); +goog.provide('goog.iter.StopIteration'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.math'); /** - * If we're using dom listeners. - * @type {?boolean} - * @private + * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}} */ -goog.async.AnimationDelay.prototype.usingListeners_ = false; +goog.iter.Iterable; /** - * Default wait timeout for animations (in milliseconds). Only used for timed - * animation, which uses a timer (setTimeout) to schedule animation. - * - * @type {number} - * @const + * Singleton Error object that is used to terminate iterations. + * @const {!Error} */ -goog.async.AnimationDelay.TIMEOUT = 20; +goog.iter.StopIteration = ('StopIteration' in goog.global) ? + // For script engines that support legacy iterators. + goog.global['StopIteration'] : + Error('StopIteration'); + /** - * Name of event received from the requestAnimationFrame in Firefox. - * - * @type {string} - * @const - * @private + * Class/interface for iterators. An iterator needs to implement a {@code next} + * method and it needs to throw a {@code goog.iter.StopIteration} when the + * iteration passes beyond the end. Iterators have no {@code hasNext} method. + * It is recommended to always use the helper functions to iterate over the + * iterator or in case you are only targeting JavaScript 1.7 for in loops. + * @constructor + * @template VALUE */ -goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint'; +goog.iter.Iterator = function() {}; /** - * Starts the delay timer. The provided listener function will be called - * before the next animation frame. + * Returns the next value of the iteration. This will throw the object + * {@see goog.iter#StopIteration} when the iteration passes the end. + * @return {VALUE} Any object or value. */ -goog.async.AnimationDelay.prototype.start = function() { - this.stop(); - this.usingListeners_ = false; +goog.iter.Iterator.prototype.next = function() { + throw goog.iter.StopIteration; +}; - var raf = this.getRaf_(); - var cancelRaf = this.getCancelRaf_(); - if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) { - // Because Firefox (Gecko) runs animation in separate threads, it also saves - // time by running the requestAnimationFrame callbacks in that same thread. - // Sadly this breaks the assumption of implicit thread-safety in JS, and can - // thus create thread-based inconsistencies on counters etc. - // - // Calling cycleAnimations_ using the MozBeforePaint event instead of as - // callback fixes this. - // - // Trigger this condition only if the mozRequestAnimationFrame is available, - // but not the W3C requestAnimationFrame function (as in draft) or the - // equivalent cancel functions. - this.id_ = goog.events.listen( - this.win_, - goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_, - this.callback_); - this.win_.mozRequestAnimationFrame(null); - this.usingListeners_ = true; - } else if (raf && cancelRaf) { - this.id_ = raf.call(this.win_, this.callback_); - } else { - this.id_ = this.win_.setTimeout( - // Prior to Firefox 13, Gecko passed a non-standard parameter - // to the callback that we want to ignore. - goog.functions.lock(this.callback_), - goog.async.AnimationDelay.TIMEOUT); - } + +/** + * Returns the {@code Iterator} object itself. This is used to implement + * the iterator protocol in JavaScript 1.7 + * @param {boolean=} opt_keys Whether to return the keys or values. Default is + * to only return the values. This is being used by the for-in loop (true) + * and the for-each-in loop (false). Even though the param gives a hint + * about what the iterator will return there is no guarantee that it will + * return the keys when true is passed. + * @return {!goog.iter.Iterator<VALUE>} The object itself. + */ +goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) { + return this; }; /** - * Stops the delay timer if it is active. No action is taken if the timer is not - * in use. + * Returns an iterator that knows how to iterate over the values in the object. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable If the + * object is an iterator it will be returned as is. If the object has an + * {@code __iterator__} method that will be called to get the value + * iterator. If the object is an array-like object we create an iterator + * for that. + * @return {!goog.iter.Iterator<VALUE>} An iterator that knows how to iterate + * over the values in {@code iterable}. + * @template VALUE */ -goog.async.AnimationDelay.prototype.stop = function() { - if (this.isActive()) { - var raf = this.getRaf_(); - var cancelRaf = this.getCancelRaf_(); - if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) { - goog.events.unlistenByKey(this.id_); - } else if (raf && cancelRaf) { - cancelRaf.call(this.win_, /** @type {number} */ (this.id_)); - } else { - this.win_.clearTimeout(/** @type {number} */ (this.id_)); - } +goog.iter.toIterator = function(iterable) { + if (iterable instanceof goog.iter.Iterator) { + return iterable; } - this.id_ = null; + if (typeof iterable.__iterator__ == 'function') { + return iterable.__iterator__(false); + } + if (goog.isArrayLike(iterable)) { + var i = 0; + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + if (i >= iterable.length) { + throw goog.iter.StopIteration; + } + // Don't include deleted elements. + if (!(i in iterable)) { + i++; + continue; + } + return iterable[i++]; + } + }; + return newIter; + } + + + // TODO(arv): Should we fall back on goog.structs.getValues()? + throw Error('Not implemented'); }; /** - * Fires delay's action even if timer has already gone off or has not been - * started yet; guarantees action firing. Stops the delay timer. + * Calls a function for each element in the iterator with the element of the + * iterator passed as argument. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. If the iterable is an object {@code toIterator} will be + * called on it. + * @param {function(this:THIS,VALUE,?,!goog.iter.Iterator<VALUE>)} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and the return value is + * irrelevant. The reason for passing undefined as the second argument is + * so that the same function can be used in {@see goog.array#forEach} as + * well as others. The third parameter is of type "number" for + * arraylike objects, undefined, otherwise. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @template THIS, VALUE */ -goog.async.AnimationDelay.prototype.fire = function() { - this.stop(); - this.doAction_(); +goog.iter.forEach = function(iterable, f, opt_obj) { + if (goog.isArrayLike(iterable)) { + /** @preserveTry */ + try { + // NOTES: this passes the index number to the second parameter + // of the callback contrary to the documentation above. + goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f, + opt_obj); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + } else { + iterable = goog.iter.toIterator(iterable); + /** @preserveTry */ + try { + while (true) { + f.call(opt_obj, iterable.next(), undefined, iterable); + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + } }; /** - * Fires delay's action only if timer is currently active. Stops the delay - * timer. + * Calls a function for every element in the iterator, and if the function + * returns true adds the element to a new iterator. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a boolean. + * If the return value is true the element will be included in the returned + * iterator. If it is false the element is not included. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements + * that passed the test are present. + * @template THIS, VALUE */ -goog.async.AnimationDelay.prototype.fireIfActive = function() { - if (this.isActive()) { - this.fire(); - } +goog.iter.filter = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + var val = iterator.next(); + if (f.call(opt_obj, val, undefined, iterator)) { + return val; + } + } + }; + return newIter; }; /** - * @return {boolean} True if the delay is currently active, false otherwise. + * Calls a function for every element in the iterator, and if the function + * returns false adds the element to a new iterator. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a boolean. + * If the return value is false the element will be included in the returned + * iterator. If it is true the element is not included. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements + * that did not pass the test are present. + * @template THIS, VALUE */ -goog.async.AnimationDelay.prototype.isActive = function() { - return this.id_ != null; +goog.iter.filterFalse = function(iterable, f, opt_obj) { + return goog.iter.filter(iterable, goog.functions.not(f), opt_obj); }; /** - * Invokes the callback function after the delay successfully completes. - * @private + * Creates a new iterator that returns the values in a range. This function + * can take 1, 2 or 3 arguments: + * <pre> + * range(5) same as range(0, 5, 1) + * range(2, 5) same as range(2, 5, 1) + * </pre> + * + * @param {number} startOrStop The stop value if only one argument is provided. + * The start value if 2 or more arguments are provided. If only one + * argument is used the start value is 0. + * @param {number=} opt_stop The stop value. If left out then the first + * argument is used as the stop value. + * @param {number=} opt_step The number to increment with between each call to + * next. This can be negative. + * @return {!goog.iter.Iterator<number>} A new iterator that returns the values + * in the range. */ -goog.async.AnimationDelay.prototype.doAction_ = function() { - if (this.usingListeners_ && this.id_) { - goog.events.unlistenByKey(this.id_); +goog.iter.range = function(startOrStop, opt_stop, opt_step) { + var start = 0; + var stop = startOrStop; + var step = opt_step || 1; + if (arguments.length > 1) { + start = startOrStop; + stop = opt_stop; + } + if (step == 0) { + throw Error('Range step argument must not be zero'); } - this.id_ = null; - - // We are not using the timestamp returned by requestAnimationFrame - // because it may be either a Date.now-style time or a - // high-resolution time (depending on browser implementation). Using - // goog.now() will ensure that the timestamp used is consistent and - // compatible with goog.fx.Animation. - this.listener_.call(this.handler_, goog.now()); -}; - -/** @override */ -goog.async.AnimationDelay.prototype.disposeInternal = function() { - this.stop(); - goog.async.AnimationDelay.base(this, 'disposeInternal'); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + if (step > 0 && start >= stop || step < 0 && start <= stop) { + throw goog.iter.StopIteration; + } + var rv = start; + start += step; + return rv; + }; + return newIter; }; /** - * @return {?function(function(number)): number} The requestAnimationFrame - * function, or null if not available on this browser. - * @private + * Joins the values in a iterator with a delimiter. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to get the values from. + * @param {string} deliminator The text to put between the values. + * @return {string} The joined value string. + * @template VALUE */ -goog.async.AnimationDelay.prototype.getRaf_ = function() { - var win = this.win_; - return win.requestAnimationFrame || - win.webkitRequestAnimationFrame || - win.mozRequestAnimationFrame || - win.oRequestAnimationFrame || - win.msRequestAnimationFrame || - null; +goog.iter.join = function(iterable, deliminator) { + return goog.iter.toArray(iterable).join(deliminator); }; /** - * @return {?function(number): number} The cancelAnimationFrame function, - * or null if not available on this browser. - * @private + * For every element in the iterator call a function and return a new iterator + * with that value. + * + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterator to iterate over. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):RESULT} f + * The function to call for every element. This function takes 3 arguments + * (the element, undefined, and the iterator) and should return a new value. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the + * results of applying the function to each element in the original + * iterator. + * @template THIS, VALUE, RESULT */ -goog.async.AnimationDelay.prototype.getCancelRaf_ = function() { - var win = this.win_; - return win.cancelRequestAnimationFrame || - win.webkitCancelRequestAnimationFrame || - win.mozCancelRequestAnimationFrame || - win.oCancelRequestAnimationFrame || - win.msCancelRequestAnimationFrame || - null; +goog.iter.map = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + var val = iterator.next(); + return f.call(opt_obj, val, undefined, iterator); + }; + return newIter; }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Provides a function to schedule running a function as soon - * as possible after the current JS execution stops and yields to the event - * loop. + * Passes every element of an iterator into a function and accumulates the + * result. * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to iterate over. + * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for + * every element. This function takes 2 arguments (the function's previous + * result or the initial value, and the value of the current element). + * function(previousValue, currentElement) : newValue. + * @param {VALUE} val The initial value to pass into the function on the first + * call. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * f. + * @return {VALUE} Result of evaluating f repeatedly across the values of + * the iterator. + * @template THIS, VALUE */ - -goog.provide('goog.async.nextTick'); -goog.provide('goog.async.throwException'); - -goog.require('goog.debug.entryPointRegistry'); -goog.require('goog.functions'); -goog.require('goog.labs.userAgent.browser'); +goog.iter.reduce = function(iterable, f, val, opt_obj) { + var rval = val; + goog.iter.forEach(iterable, function(val) { + rval = f.call(opt_obj, rval, val); + }); + return rval; +}; /** - * Throw an item without interrupting the current execution context. For - * example, if processing a group of items in a loop, sometimes it is useful - * to report an error while still allowing the rest of the batch to be - * processed. - * @param {*} exception + * Goes through the values in the iterator. Calls f for each of these, and if + * any of them returns true, this returns true (without checking the rest). If + * all return false this will return false. + * + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {boolean} true if any value passes the test. + * @template THIS, VALUE */ -goog.async.throwException = function(exception) { - // Each throw needs to be in its own context. - goog.global.setTimeout(function() { throw exception; }, 0); +goog.iter.some = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + /** @preserveTry */ + try { + while (true) { + if (f.call(opt_obj, iterable.next(), undefined, iterable)) { + return true; + } + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + } + return false; }; /** - * Fires the provided callbacks as soon as possible after the current JS - * execution context. setTimeout(…, 0) takes at least 4ms when called from - * within another setTimeout(…, 0) for legacy reasons. - * - * This will not schedule the callback as a microtask (i.e. a task that can - * preempt user input or networking callbacks). It is meant to emulate what - * setTimeout(_, 0) would do if it were not throttled. If you desire microtask - * behavior, use {@see goog.Promise} instead. + * Goes through the values in the iterator. Calls f for each of these and if any + * of them returns false this returns false (without checking the rest). If all + * return true this will return true. * - * @param {function(this:SCOPE)} callback Callback function to fire as soon as - * possible. - * @param {SCOPE=} opt_context Object in whose scope to call the listener. - * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that - * ensures correctness at the cost of speed. See comments for details. - * @template SCOPE + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {boolean} true if every value passes the test. + * @template THIS, VALUE */ -goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) { - var cb = callback; - if (opt_context) { - cb = goog.bind(callback, opt_context); - } - cb = goog.async.nextTick.wrapCallback_(cb); - // window.setImmediate was introduced and currently only supported by IE10+, - // but due to a bug in the implementation it is not guaranteed that - // setImmediate is faster than setTimeout nor that setImmediate N is before - // setImmediate N+1. That is why we do not use the native version if - // available. We do, however, call setImmediate if it is a normal function - // because that indicates that it has been replaced by goog.testing.MockClock - // which we do want to support. - // See - // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10 - // - // Note we do allow callers to also request setImmediate if they are willing - // to accept the possible tradeoffs of incorrectness in exchange for speed. - // The IE fallback of readystate change is much slower. - if (goog.isFunction(goog.global.setImmediate) && - (opt_useSetImmediate || !goog.global.Window || - goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) { - goog.global.setImmediate(cb); - return; +goog.iter.every = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + /** @preserveTry */ + try { + while (true) { + if (!f.call(opt_obj, iterable.next(), undefined, iterable)) { + return false; + } + } + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } } + return true; +}; - // Look for and cache the custom fallback version of setImmediate. - if (!goog.async.nextTick.setImmediate_) { - goog.async.nextTick.setImmediate_ = - goog.async.nextTick.getSetImmediateEmulator_(); - } - goog.async.nextTick.setImmediate_(cb); + +/** + * Takes zero or more iterables and returns one iterator that will iterate over + * them in the order chained. + * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will + * iterate over all the given iterables' contents. + * @template VALUE + */ +goog.iter.chain = function(var_args) { + return goog.iter.chainFromIterable(arguments); }; /** - * Cache for the setImmediate implementation. - * @type {function(function())} - * @private + * Takes a single iterable containing zero or more iterables and returns one + * iterator that will iterate over each one in the order given. + * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable + * @param {goog.iter.Iterable} iterable The iterable of iterables to chain. + * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will + * iterate over all the contents of the iterables contained within + * {@code iterable}. + * @template VALUE */ -goog.async.nextTick.setImmediate_; +goog.iter.chainFromIterable = function(iterable) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + var current = null; + + iter.next = function() { + while (true) { + if (current == null) { + var it = iterator.next(); + current = goog.iter.toIterator(it); + } + try { + return current.next(); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + current = null; + } + } + }; + + return iter; +}; /** - * Determines the best possible implementation to run a function as soon as - * the JS event loop is idle. - * @return {function(function())} The "setImmediate" implementation. - * @private + * Builds a new iterator that iterates over the original, but skips elements as + * long as a supplied function returns true. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that drops elements from + * the original iterator as long as {@code f} is true. + * @template THIS, VALUE */ -goog.async.nextTick.getSetImmediateEmulator_ = function() { - // Create a private message channel and use it to postMessage empty messages - // to ourselves. - var Channel = goog.global['MessageChannel']; - // If MessageChannel is not available and we are in a browser, implement - // an iframe based polyfill in browsers that have postMessage and - // document.addEventListener. The latter excludes IE8 because it has a - // synchronous postMessage implementation. - if (typeof Channel === 'undefined' && typeof window !== 'undefined' && - window.postMessage && window.addEventListener) { - /** @constructor */ - Channel = function() { - // Make an empty, invisible iframe. - var iframe = document.createElement('iframe'); - iframe.style.display = 'none'; - iframe.src = ''; - document.documentElement.appendChild(iframe); - var win = iframe.contentWindow; - var doc = win.document; - doc.open(); - doc.write(''); - doc.close(); - // Do not post anything sensitive over this channel, as the workaround for - // pages with file: origin could allow that information to be modified or - // intercepted. - var message = 'callImmediate' + Math.random(); - // The same origin policy rejects attempts to postMessage from file: urls - // unless the origin is '*'. - // TODO(b/16335441): Use '*' origin for data: and other similar protocols. - var origin = win.location.protocol == 'file:' ? - '*' : win.location.protocol + '//' + win.location.host; - var onmessage = goog.bind(function(e) { - // Validate origin and message to make sure that this message was - // intended for us. If the origin is set to '*' (see above) only the - // message needs to match since, for example, '*' != 'file://'. Allowing - // the wildcard is ok, as we are not concerned with security here. - if ((origin != '*' && e.origin != origin) || e.data != message) { - return; - } - this['port1'].onmessage(); - }, this); - win.addEventListener('message', onmessage, false); - this['port1'] = {}; - this['port2'] = { - postMessage: function() { - win.postMessage(message, origin); - } - }; - }; - } - if (typeof Channel !== 'undefined' && - (!goog.labs.userAgent.browser.isIE())) { - // Exclude all of IE due to - // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ - // which allows starving postMessage with a busy setTimeout loop. - // This currently affects IE10 and IE11 which would otherwise be able - // to use the postMessage based fallbacks. - var channel = new Channel(); - // Use a fifo linked list to call callbacks in the right order. - var head = {}; - var tail = head; - channel['port1'].onmessage = function() { - if (goog.isDef(head.next)) { - head = head.next; - var cb = head.cb; - head.cb = null; - cb(); +goog.iter.dropWhile = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + var dropping = true; + newIter.next = function() { + while (true) { + var val = iterator.next(); + if (dropping && f.call(opt_obj, val, undefined, iterator)) { + continue; + } else { + dropping = false; } - }; - return function(cb) { - tail.next = { - cb: cb - }; - tail = tail.next; - channel['port2'].postMessage(0); - }; - } - // Implementation for IE6+: Script elements fire an asynchronous - // onreadystatechange event when inserted into the DOM. - if (typeof document !== 'undefined' && 'onreadystatechange' in - document.createElement('script')) { - return function(cb) { - var script = document.createElement('script'); - script.onreadystatechange = function() { - // Clean up and call the callback. - script.onreadystatechange = null; - script.parentNode.removeChild(script); - script = null; - cb(); - cb = null; - }; - document.documentElement.appendChild(script); - }; - } - // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms - // or more. - return function(cb) { - goog.global.setTimeout(cb, 0); + return val; + } }; + return newIter; }; /** - * Helper function that is overrided to protect callbacks with entry point - * monitor if the application monitors entry points. - * @param {function()} callback Callback function to fire as soon as possible. - * @return {function()} The wrapped callback. - * @private + * Builds a new iterator that iterates over the original, but only as long as a + * supplied function returns true. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * object. + * @param { + * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f + * The function to call for every value. This function takes 3 arguments + * (the value, undefined, and the iterator) and should return a boolean. + * @param {THIS=} opt_obj This is used as the 'this' object in f when called. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that keeps elements in + * the original iterator as long as the function is true. + * @template THIS, VALUE */ -goog.async.nextTick.wrapCallback_ = goog.functions.identity; - - -// Register the callback function as an entry point, so that it can be -// monitored for exception handling, etc. This has to be done in this file -// since it requires special code to handle all browsers. -goog.debug.entryPointRegistry.register( - /** - * @param {function(!Function): !Function} transformer The transforming - * function. - */ - function(transformer) { - goog.async.nextTick.wrapCallback_ = transformer; - }); +goog.iter.takeWhile = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + iter.next = function() { + var val = iterator.next(); + if (f.call(opt_obj, val, undefined, iterator)) { + return val; + } + throw goog.iter.StopIteration; + }; + return iter; +}; -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview The SafeScript type and its builders. - * - * TODO(user): Link to document stating type contract. + * Converts the iterator to an array + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator + * to convert to an array. + * @return {!Array<VALUE>} An array of the elements the iterator iterates over. + * @template VALUE */ - -goog.provide('goog.html.SafeScript'); - -goog.require('goog.asserts'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); - +goog.iter.toArray = function(iterable) { + // Fast path for array-like. + if (goog.isArrayLike(iterable)) { + return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable)); + } + iterable = goog.iter.toIterator(iterable); + var array = []; + goog.iter.forEach(iterable, function(val) { + array.push(val); + }); + return array; +}; /** - * A string-like object which represents JavaScript code and that carries the - * security type contract that its value, as a string, will not cause execution - * of unconstrained attacker controlled code (XSS) when evaluated as JavaScript - * in a browser. - * - * Instances of this type must be created via the factory method - * {@code goog.html.SafeScript.fromConstant} and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty string - * can be obtained via constructor invocation. - * - * A SafeScript's string representation can safely be interpolated as the - * content of a script element within HTML. The SafeScript string should not be - * escaped before interpolation. - * - * Note that the SafeScript might contain text that is attacker-controlled but - * that text should have been interpolated with appropriate escaping, - * sanitization and/or validation into the right location in the script, such - * that it is highly constrained in its effect (for example, it had to match a - * set of whitelisted words). - * - * A SafeScript can be constructed via security-reviewed unchecked - * conversions. In this case producers of SafeScript must ensure themselves that - * the SafeScript does not contain unsafe script. Note in particular that - * {@code <} is dangerous, even when inside JavaScript strings, and so should - * always be forbidden or JavaScript escaped in user controlled input. For - * example, if {@code </script><script>evil</script>"} were - * interpolated inside a JavaScript string, it would break out of the context - * of the original script element and {@code evil} would execute. Also note - * that within an HTML script (raw text) element, HTML character references, - * such as "<" are not allowed. See - * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements. - * - * @see goog.html.SafeScript#fromConstant - * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} + * Iterates over two iterables and returns true if they contain the same + * sequence of elements and have the same length. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable1 The first + * iterable object. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable2 The second + * iterable object. + * @param {function(VALUE,VALUE):boolean=} opt_equalsFn Optional comparison + * function. + * Should take two arguments to compare, and return true if the arguments + * are equal. Defaults to {@link goog.array.defaultCompareEquality} which + * compares the elements using the built-in '===' operator. + * @return {boolean} true if the iterables contain the same sequence of elements + * and have the same length. + * @template VALUE */ -goog.html.SafeScript = function() { - /** - * The contained value of this SafeScript. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = ''; - - /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeScript#unwrap - * @const - * @private - */ - this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; +goog.iter.equals = function(iterable1, iterable2, opt_equalsFn) { + var fillValue = {}; + var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2); + var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality; + return goog.iter.every(pairs, function(pair) { + return equalsFn(pair[0], pair[1]); + }); }; /** - * @override - * @const + * Advances the iterator to the next position, returning the given default value + * instead of throwing an exception if the iterator has no more entries. + * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterable + * object. + * @param {VALUE} defaultValue The value to return if the iterator is empty. + * @return {VALUE} The next item in the iteration, or defaultValue if the + * iterator was empty. + * @template VALUE */ -goog.html.SafeScript.prototype.implementsGoogStringTypedString = true; +goog.iter.nextOrValue = function(iterable, defaultValue) { + try { + return goog.iter.toIterator(iterable).next(); + } catch (e) { + if (e != goog.iter.StopIteration) { + throw e; + } + return defaultValue; + } +}; /** - * Type marker for the SafeScript type, used to implement additional - * run-time type checking. - * @const - * @private + * Cartesian product of zero or more sets. Gives an iterator that gives every + * combination of one element chosen from each set. For example, + * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]). + * @see http://docs.python.org/library/itertools.html#itertools.product + * @param {...!goog.array.ArrayLike<VALUE>} var_args Zero or more sets, as + * arrays. + * @return {!goog.iter.Iterator<!Array<VALUE>>} An iterator that gives each + * n-tuple (as an array). + * @template VALUE */ -goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; - +goog.iter.product = function(var_args) { + var someArrayEmpty = goog.array.some(arguments, function(arr) { + return !arr.length; + }); -/** - * Creates a SafeScript object from a compile-time constant string. - * - * @param {!goog.string.Const} script A compile-time-constant string from which - * to create a SafeScript. - * @return {!goog.html.SafeScript} A SafeScript object initialized to - * {@code script}. - */ -goog.html.SafeScript.fromConstant = function(script) { - var scriptString = goog.string.Const.unwrap(script); - if (scriptString.length === 0) { - return goog.html.SafeScript.EMPTY; + // An empty set in a cartesian product gives an empty set. + if (someArrayEmpty || !arguments.length) { + return new goog.iter.Iterator(); } - return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( - scriptString); -}; + var iter = new goog.iter.Iterator(); + var arrays = arguments; -/** - * Returns this SafeScript's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of - * this method. If in doubt, assume that it's security relevant. In particular, - * note that goog.html functions which return a goog.html type do not guarantee - * the returned instance is of the right type. For example: - * - * <pre> - * var fakeSafeHtml = new String('fake'); - * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; - * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); - * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by - * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml - * // instanceof goog.html.SafeHtml. - * </pre> - * - * @see goog.html.SafeScript#unwrap - * @override - */ -goog.html.SafeScript.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeScriptWrappedValue_; -}; + // The first indices are [0, 0, ...] + var indicies = goog.array.repeat(0, arrays.length); + iter.next = function() { -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeScript, use - * {@code goog.html.SafeScript.unwrap}. - * - * @see goog.html.SafeScript#unwrap - * @override - */ - goog.html.SafeScript.prototype.toString = function() { - return 'SafeScript{' + - this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}'; - }; -} + if (indicies) { + var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) { + return arrays[arrayIndex][valueIndex]; + }); + // Generate the next-largest indices for the next call. + // Increase the rightmost index. If it goes over, increase the next + // rightmost (like carry-over addition). + for (var i = indicies.length - 1; i >= 0; i--) { + // Assertion prevents compiler warning below. + goog.asserts.assert(indicies); + if (indicies[i] < arrays[i].length - 1) { + indicies[i]++; + break; + } -/** - * Performs a runtime check that the provided object is indeed a - * SafeScript object, and returns its value. - * - * @param {!goog.html.SafeScript} safeScript The object to extract from. - * @return {string} The safeScript object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.SafeScript.unwrap = function(safeScript) { - // Perform additional Run-time type-checking to ensure that - // safeScript is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeScript instanceof goog.html.SafeScript && - safeScript.constructor === goog.html.SafeScript && - safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_; - } else { - goog.asserts.fail( - 'expected object of type SafeScript, got \'' + safeScript + '\''); - return 'type_error:SafeScript'; - } -}; + // We're at the last indices (the last element of every array), so + // the iteration is over on the next call. + if (i == 0) { + indicies = null; + break; + } + // Reset the index in this column and loop back to increment the + // next one. + indicies[i] = 0; + } + return retVal; + } + throw goog.iter.StopIteration; + }; -/** - * Package-internal utility method to create SafeScript instances. - * - * @param {string} script The string to initialize the SafeScript object with. - * @return {!goog.html.SafeScript} The initialized SafeScript object. - * @package - */ -goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse = - function(script) { - var safeScript = new goog.html.SafeScript(); - safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script; - return safeScript; + return iter; }; /** - * A SafeScript instance corresponding to the empty string. - * @const {!goog.html.SafeScript} + * Create an iterator to cycle over the iterable's elements indefinitely. + * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ... + * @see: http://docs.python.org/library/itertools.html#itertools.cycle. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable object. + * @return {!goog.iter.Iterator<VALUE>} An iterator that iterates indefinitely + * over the values in {@code iterable}. + * @template VALUE */ -goog.html.SafeScript.EMPTY = - goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(''); - -// Copyright 2014 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +goog.iter.cycle = function(iterable) { + var baseIterator = goog.iter.toIterator(iterable); -/** - * @fileoverview The SafeStyleSheet type and its builders. - * - * TODO(user): Link to document stating type contract. - */ + // We maintain a cache to store the iterable elements as we iterate + // over them. The cache is used to return elements once we have + // iterated over the iterable once. + var cache = []; + var cacheIndex = 0; -goog.provide('goog.html.SafeStyleSheet'); + var iter = new goog.iter.Iterator(); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.string'); -goog.require('goog.string.Const'); -goog.require('goog.string.TypedString'); + // This flag is set after the iterable is iterated over once + var useCache = false; + iter.next = function() { + var returnElement = null; + // Pull elements off the original iterator if not using cache + if (!useCache) { + try { + // Return the element from the iterable + returnElement = baseIterator.next(); + cache.push(returnElement); + return returnElement; + } catch (e) { + // If an exception other than StopIteration is thrown + // or if there are no elements to iterate over (the iterable was empty) + // throw an exception + if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) { + throw e; + } + // set useCache to true after we know that a 'StopIteration' exception + // was thrown and the cache is not empty (to handle the 'empty iterable' + // use case) + useCache = true; + } + } -/** - * A string-like object which represents a CSS style sheet and that carries the - * security type contract that its value, as a string, will not cause untrusted - * script execution (XSS) when evaluated as CSS in a browser. - * - * Instances of this type must be created via the factory method - * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its - * constructor. The constructor intentionally takes no parameters and the type - * is immutable; hence only a default instance corresponding to the empty string - * can be obtained via constructor invocation. - * - * A SafeStyleSheet's string representation can safely be interpolated as the - * content of a style element within HTML. The SafeStyleSheet string should - * not be escaped before interpolation. - * - * Values of this type must be composable, i.e. for any two values - * {@code styleSheet1} and {@code styleSheet2} of this type, - * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) + - * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that - * satisfies the SafeStyleSheet type constraint. This requirement implies that - * for any value {@code styleSheet} of this type, - * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in - * "beginning of rule" context. + returnElement = cache[cacheIndex]; + cacheIndex = (cacheIndex + 1) % cache.length; - * A SafeStyleSheet can be constructed via security-reviewed unchecked - * conversions. In this case producers of SafeStyleSheet must ensure themselves - * that the SafeStyleSheet does not contain unsafe script. Note in particular - * that {@code <} is dangerous, even when inside CSS strings, and so should - * always be forbidden or CSS-escaped in user controlled input. For example, if - * {@code </style><script>evil</script>"} were interpolated - * inside a CSS string, it would break out of the context of the original - * style element and {@code evil} would execute. Also note that within an HTML - * style (raw text) element, HTML character references, such as - * {@code &lt;}, are not allowed. See - * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements - * (similar considerations apply to the style element). - * - * @see goog.html.SafeStyleSheet#fromConstant - * @constructor - * @final - * @struct - * @implements {goog.string.TypedString} - */ -goog.html.SafeStyleSheet = function() { - /** - * The contained value of this SafeStyleSheet. The field has a purposely - * ugly name to make (non-compiled) code that attempts to directly access this - * field stand out. - * @private {string} - */ - this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = ''; + return returnElement; + }; - /** - * A type marker used to implement additional run-time type checking. - * @see goog.html.SafeStyleSheet#unwrap - * @const - * @private - */ - this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = - goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_; + return iter; }; /** - * @override - * @const + * Creates an iterator that counts indefinitely from a starting value. + * @see http://docs.python.org/2/library/itertools.html#itertools.count + * @param {number=} opt_start The starting value. Default is 0. + * @param {number=} opt_step The number to increment with between each call to + * next. Negative and floating point numbers are allowed. Default is 1. + * @return {!goog.iter.Iterator<number>} A new iterator that returns the values + * in the series. */ -goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true; +goog.iter.count = function(opt_start, opt_step) { + var counter = opt_start || 0; + var step = goog.isDef(opt_step) ? opt_step : 1; + var iter = new goog.iter.Iterator(); + + iter.next = function() { + var returnValue = counter; + counter += step; + return returnValue; + }; + + return iter; +}; /** - * Type marker for the SafeStyleSheet type, used to implement additional - * run-time type checking. - * @const - * @private + * Creates an iterator that returns the same object or value repeatedly. + * @param {VALUE} value Any object or value to repeat. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the + * repeated value. + * @template VALUE */ -goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; +goog.iter.repeat = function(value) { + var iter = new goog.iter.Iterator(); + + iter.next = goog.functions.constant(value); + + return iter; +}; /** - * Creates a new SafeStyleSheet object by concatenating values. - * @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)} - * var_args Values to concatenate. - * @return {!goog.html.SafeStyleSheet} + * Creates an iterator that returns running totals from the numbers in + * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields + * {@code 1 -> 3 -> 6 -> 10 -> 15}. + * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate + * @param {!goog.iter.Iterable<number>} iterable The iterable of numbers to + * accumulate. + * @return {!goog.iter.Iterator<number>} A new iterator that returns the + * numbers in the series. */ -goog.html.SafeStyleSheet.concat = function(var_args) { - var result = ''; +goog.iter.accumulate = function(iterable) { + var iterator = goog.iter.toIterator(iterable); + var total = 0; + var iter = new goog.iter.Iterator(); - /** - * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>} - * argument - */ - var addArgument = function(argument) { - if (goog.isArray(argument)) { - goog.array.forEach(argument, addArgument); - } else { - result += goog.html.SafeStyleSheet.unwrap(argument); - } + iter.next = function() { + total += iterator.next(); + return total; }; - goog.array.forEach(arguments, addArgument); - return goog.html.SafeStyleSheet - .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result); + return iter; }; /** - * Creates a SafeStyleSheet object from a compile-time constant string. - * - * {@code styleSheet} must not have any < characters in it, so that - * the syntactic structure of the surrounding HTML is not affected. - * - * @param {!goog.string.Const} styleSheet A compile-time-constant string from - * which to create a SafeStyleSheet. - * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to - * {@code styleSheet}. + * Creates an iterator that returns arrays containing the ith elements from the + * provided iterables. The returned arrays will be the same size as the number + * of iterables given in {@code var_args}. Once the shortest iterable is + * exhausted, subsequent calls to {@code next()} will throw + * {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.izip + * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns + * arrays of elements from the provided iterables. + * @template VALUE */ -goog.html.SafeStyleSheet.fromConstant = function(styleSheet) { - var styleSheetString = goog.string.Const.unwrap(styleSheet); - if (styleSheetString.length === 0) { - return goog.html.SafeStyleSheet.EMPTY; +goog.iter.zip = function(var_args) { + var args = arguments; + var iter = new goog.iter.Iterator(); + + if (args.length > 0) { + var iterators = goog.array.map(args, goog.iter.toIterator); + iter.next = function() { + var arr = goog.array.map(iterators, function(it) { + return it.next(); + }); + return arr; + }; } - // > is a valid character in CSS selectors and there's no strict need to - // block it if we already block <. - goog.asserts.assert(!goog.string.contains(styleSheetString, '<'), - "Forbidden '<' character in style sheet string: " + styleSheetString); - return goog.html.SafeStyleSheet. - createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString); + + return iter; }; /** - * Returns this SafeStyleSheet's value as a string. - * - * IMPORTANT: In code where it is security relevant that an object's type is - * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap} - * instead of this method. If in doubt, assume that it's security relevant. In - * particular, note that goog.html functions which return a goog.html type do - * not guarantee the returned instance is of the right type. For example: - * - * <pre> - * var fakeSafeHtml = new String('fake'); - * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype; - * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml); - * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by - * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml - * // instanceof goog.html.SafeHtml. - * </pre> - * - * @see goog.html.SafeStyleSheet#unwrap - * @override + * Creates an iterator that returns arrays containing the ith elements from the + * provided iterables. The returned arrays will be the same size as the number + * of iterables given in {@code var_args}. Shorter iterables will be extended + * with {@code fillValue}. Once the longest iterable is exhausted, subsequent + * calls to {@code next()} will throw {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest + * @param {VALUE} fillValue The object or value used to fill shorter iterables. + * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any + * number of iterable objects. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns + * arrays of elements from the provided iterables. + * @template VALUE */ -goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() { - return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; -}; - +goog.iter.zipLongest = function(fillValue, var_args) { + var args = goog.array.slice(arguments, 1); + var iter = new goog.iter.Iterator(); -if (goog.DEBUG) { - /** - * Returns a debug string-representation of this value. - * - * To obtain the actual string value wrapped in a SafeStyleSheet, use - * {@code goog.html.SafeStyleSheet.unwrap}. - * - * @see goog.html.SafeStyleSheet#unwrap - * @override - */ - goog.html.SafeStyleSheet.prototype.toString = function() { - return 'SafeStyleSheet{' + - this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}'; - }; -} + if (args.length > 0) { + var iterators = goog.array.map(args, goog.iter.toIterator); + iter.next = function() { + var iteratorsHaveValues = false; // false when all iterators are empty. + var arr = goog.array.map(iterators, function(it) { + var returnValue; + try { + returnValue = it.next(); + // Iterator had a value, so we've not exhausted the iterators. + // Set flag accordingly. + iteratorsHaveValues = true; + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + returnValue = fillValue; + } + return returnValue; + }); -/** - * Performs a runtime check that the provided object is indeed a - * SafeStyleSheet object, and returns its value. - * - * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from. - * @return {string} The safeStyleSheet object's contained string, unless - * the run-time type check fails. In that case, {@code unwrap} returns an - * innocuous string, or, if assertions are enabled, throws - * {@code goog.asserts.AssertionError}. - */ -goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) { - // Perform additional Run-time type-checking to ensure that - // safeStyleSheet is indeed an instance of the expected type. This - // provides some additional protection against security bugs due to - // application code that disables type checks. - // Specifically, the following checks are performed: - // 1. The object is an instance of the expected type. - // 2. The object is not an instance of a subclass. - // 3. The object carries a type marker for the expected type. "Faking" an - // object requires a reference to the type marker, which has names intended - // to stand out in code reviews. - if (safeStyleSheet instanceof goog.html.SafeStyleSheet && - safeStyleSheet.constructor === goog.html.SafeStyleSheet && - safeStyleSheet.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ === - goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) { - return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_; - } else { - goog.asserts.fail( - "expected object of type SafeStyleSheet, got '" + safeStyleSheet + - "'"); - return 'type_error:SafeStyleSheet'; + if (!iteratorsHaveValues) { + throw goog.iter.StopIteration; + } + return arr; + }; } + + return iter; }; /** - * Package-internal utility method to create SafeStyleSheet instances. - * - * @param {string} styleSheet The string to initialize the SafeStyleSheet - * object with. - * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object. - * @package + * Creates an iterator that filters {@code iterable} based on a series of + * {@code selectors}. On each call to {@code next()}, one item is taken from + * both the {@code iterable} and {@code selectors} iterators. If the item from + * {@code selectors} evaluates to true, the item from {@code iterable} is given. + * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors} + * is exhausted, subsequent calls to {@code next()} will throw + * {@code goog.iter.StopIteration}. + * @see http://docs.python.org/2/library/itertools.html#itertools.compress + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to filter. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} selectors An + * iterable of items to be evaluated in a boolean context to determine if + * the corresponding element in {@code iterable} should be included in the + * result. + * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the + * filtered values. + * @template VALUE */ -goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse = - function(styleSheet) { - var safeStyleSheet = new goog.html.SafeStyleSheet(); - safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = - styleSheet; - return safeStyleSheet; +goog.iter.compress = function(iterable, selectors) { + var selectorIterator = goog.iter.toIterator(selectors); + + return goog.iter.filter(iterable, function() { + return !!selectorIterator.next(); + }); }; + /** - * A SafeStyleSheet instance corresponding to the empty string. - * @const {!goog.html.SafeStyleSheet} + * Implements the {@code goog.iter.groupBy} iterator. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to group. + * @param {function(...VALUE): KEY=} opt_keyFunc Optional function for + * determining the key value for each group in the {@code iterable}. Default + * is the identity function. + * @constructor + * @extends {goog.iter.Iterator<!Array<?>>} + * @template KEY, VALUE + * @private */ -goog.html.SafeStyleSheet.EMPTY = - goog.html.SafeStyleSheet. - createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(''); +goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) { -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** + * The iterable to group, coerced to an iterator. + * @type {!goog.iter.Iterator} + */ + this.iterator = goog.iter.toIterator(iterable); -/** - * @fileoverview Unchecked conversions to create values of goog.html types from - * plain strings. Use of these functions could potentially result in instances - * of goog.html types that violate their type contracts, and hence result in - * security vulnerabilties. - * - * Therefore, all uses of the methods herein must be carefully security - * reviewed. Avoid use of the methods in this file whenever possible; instead - * prefer to create instances of goog.html types using inherently safe builders - * or template systems. - * - * - * @visibility {//closure/goog/html:approved_for_unchecked_conversion} - * @visibility {//closure/goog/bin/sizetests:__pkg__} - */ + /** + * A function for determining the key value for each element in the iterable. + * If no function is provided, the identity function is used and returns the + * element unchanged. + * @type {function(...VALUE): KEY} + */ + this.keyFunc = opt_keyFunc || goog.functions.identity; + /** + * The target key for determining the start of a group. + * @type {KEY} + */ + this.targetKey; -goog.provide('goog.html.uncheckedconversions'); + /** + * The current key visited during iteration. + * @type {KEY} + */ + this.currentKey; -goog.require('goog.asserts'); -goog.require('goog.html.SafeHtml'); -goog.require('goog.html.SafeScript'); -goog.require('goog.html.SafeStyle'); -goog.require('goog.html.SafeStyleSheet'); -goog.require('goog.html.SafeUrl'); -goog.require('goog.html.TrustedResourceUrl'); -goog.require('goog.string'); -goog.require('goog.string.Const'); + /** + * The current value being added to the group. + * @type {VALUE} + */ + this.currentValue; +}; +goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator); -/** - * Performs an "unchecked conversion" to SafeHtml from a plain string that is - * known to satisfy the SafeHtml type contract. - * - * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure - * that the value of {@code html} satisfies the SafeHtml type contract in all - * possible program states. - * - * - * @param {!goog.string.Const} justification A constant string explaining why - * this use of this method is safe. May include a security review ticket - * number. - * @param {string} html A string that is claimed to adhere to the SafeHtml - * contract. - * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the - * SafeHtml to be constructed. A null or undefined value signifies an - * unknown directionality. - * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml - * object. - * @suppress {visibility} For access to SafeHtml.create... Note that this - * use is appropriate since this method is intended to be "package private" - * withing goog.html. DO NOT call SafeHtml.create... from outside this - * package; use appropriate wrappers instead. - */ -goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract = - function(justification, html, opt_dir) { - // unwrap() called inside an assert so that justification can be optimized - // away in production code. - goog.asserts.assertString(goog.string.Const.unwrap(justification), - 'must provide justification'); - goog.asserts.assert( - !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), - 'must provide non-empty justification'); - return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse( - html, opt_dir || null); +/** @override */ +goog.iter.GroupByIterator_.prototype.next = function() { + while (this.currentKey == this.targetKey) { + this.currentValue = this.iterator.next(); // Exits on StopIteration + this.currentKey = this.keyFunc(this.currentValue); + } + this.targetKey = this.currentKey; + return [this.currentKey, this.groupItems_(this.targetKey)]; }; /** - * Performs an "unchecked conversion" to SafeScript from a plain string that is - * known to satisfy the SafeScript type contract. - * - * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure - * that the value of {@code script} satisfies the SafeScript type contract in - * all possible program states. - * - * - * @param {!goog.string.Const} justification A constant string explaining why - * this use of this method is safe. May include a security review ticket - * number. - * @param {string} script The string to wrap as a SafeScript. - * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a - * SafeScript object. + * Performs the grouping of objects using the given key. + * @param {KEY} targetKey The target key object for the group. + * @return {!Array<VALUE>} An array of grouped objects. + * @private */ -goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract = - function(justification, script) { - // unwrap() called inside an assert so that justification can be optimized - // away in production code. - goog.asserts.assertString(goog.string.Const.unwrap(justification), - 'must provide justification'); - goog.asserts.assert( - !goog.string.isEmpty(goog.string.Const.unwrap(justification)), - 'must provide non-empty justification'); - return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse( - script); +goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) { + var arr = []; + while (this.currentKey == targetKey) { + arr.push(this.currentValue); + try { + this.currentValue = this.iterator.next(); + } catch (ex) { + if (ex !== goog.iter.StopIteration) { + throw ex; + } + break; + } + this.currentKey = this.keyFunc(this.currentValue); + } + return arr; }; /** - * Performs an "unchecked conversion" to SafeStyle from a plain string that is - * known to satisfy the SafeStyle type contract. - * - * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure - * that the value of {@code style} satisfies the SafeUrl type contract in all - * possible program states. - * - * - * @param {!goog.string.Const} justification A constant string explaining why - * this use of this method is safe. May include a security review ticket - * number. - * @param {string} style The string to wrap as a SafeStyle. - * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a - * SafeStyle object. - */ -goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract = - function(justification, style) { - // unwrap() called inside an assert so that justification can be optimized - // away in production code. - goog.asserts.assertString(goog.string.Const.unwrap(justification), - 'must provide justification'); - goog.asserts.assert( - !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), - 'must provide non-empty justification'); - return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse( - style); + * Creates an iterator that returns arrays containing elements from the + * {@code iterable} grouped by a key value. For iterables with repeated + * elements (i.e. sorted according to a particular key function), this function + * has a {@code uniq}-like effect. For example, grouping the array: + * {@code [A, B, B, C, C, A]} produces + * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.groupby + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to group. + * @param {function(...VALUE): KEY=} opt_keyFunc Optional function for + * determining the key value for each group in the {@code iterable}. Default + * is the identity function. + * @return {!goog.iter.Iterator<!Array<?>>} A new iterator that returns + * arrays of consecutive key and groups. + * @template KEY, VALUE + */ +goog.iter.groupBy = function(iterable, opt_keyFunc) { + return new goog.iter.GroupByIterator_(iterable, opt_keyFunc); }; /** - * Performs an "unchecked conversion" to SafeStyleSheet from a plain string - * that is known to satisfy the SafeStyleSheet type contract. - * - * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure - * that the value of {@code styleSheet} satisfies the SafeUrl type contract in - * all possible program states. + * Gives an iterator that gives the result of calling the given function + * <code>f</code> with the arguments taken from the next element from + * <code>iterable</code> (the elements are expected to also be iterables). * + * Similar to {@see goog.iter#map} but allows the function to accept multiple + * arguments from the iterable. * - * @param {!goog.string.Const} justification A constant string explaining why - * this use of this method is safe. May include a security review ticket - * number. - * @param {string} styleSheet The string to wrap as a SafeStyleSheet. - * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped - * in a SafeStyleSheet object. + * @param {!goog.iter.Iterable<!goog.iter.Iterable>} iterable The iterable of + * iterables to iterate over. + * @param {function(this:THIS,...*):RESULT} f The function to call for every + * element. This function takes N+2 arguments, where N represents the + * number of items from the next element of the iterable. The two + * additional arguments passed to the function are undefined and the + * iterator itself. The function should return a new value. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within + * {@code f}. + * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the + * results of applying the function to each element in the original + * iterator. + * @template THIS, RESULT */ -goog.html.uncheckedconversions. - safeStyleSheetFromStringKnownToSatisfyTypeContract = - function(justification, styleSheet) { - // unwrap() called inside an assert so that justification can be optimized - // away in production code. - goog.asserts.assertString(goog.string.Const.unwrap(justification), - 'must provide justification'); - goog.asserts.assert( - !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), - 'must provide non-empty justification'); - return goog.html.SafeStyleSheet. - createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet); +goog.iter.starMap = function(iterable, f, opt_obj) { + var iterator = goog.iter.toIterator(iterable); + var iter = new goog.iter.Iterator(); + + iter.next = function() { + var args = goog.iter.toArray(iterator.next()); + return f.apply(opt_obj, goog.array.concat(args, undefined, iterator)); + }; + + return iter; }; /** - * Performs an "unchecked conversion" to SafeUrl from a plain string that is - * known to satisfy the SafeUrl type contract. - * - * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure - * that the value of {@code url} satisfies the SafeUrl type contract in all - * possible program states. - * - * - * @param {!goog.string.Const} justification A constant string explaining why - * this use of this method is safe. May include a security review ticket - * number. - * @param {string} url The string to wrap as a SafeUrl. - * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl - * object. + * Returns an array of iterators each of which can iterate over the values in + * {@code iterable} without advancing the others. + * @see http://docs.python.org/2/library/itertools.html#itertools.tee + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to tee. + * @param {number=} opt_num The number of iterators to create. Default is 2. + * @return {!Array<goog.iter.Iterator<VALUE>>} An array of iterators. + * @template VALUE */ -goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract = - function(justification, url) { - // unwrap() called inside an assert so that justification can be optimized - // away in production code. - goog.asserts.assertString(goog.string.Const.unwrap(justification), - 'must provide justification'); - goog.asserts.assert( - !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), - 'must provide non-empty justification'); - return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url); +goog.iter.tee = function(iterable, opt_num) { + var iterator = goog.iter.toIterator(iterable); + var num = goog.isNumber(opt_num) ? opt_num : 2; + var buffers = goog.array.map(goog.array.range(num), function() { + return []; + }); + + var addNextIteratorValueToBuffers = function() { + var val = iterator.next(); + goog.array.forEach(buffers, function(buffer) { + buffer.push(val); + }); + }; + + var createIterator = function(buffer) { + // Each tee'd iterator has an associated buffer (initially empty). When a + // tee'd iterator's buffer is empty, it calls + // addNextIteratorValueToBuffers(), adding the next value to all tee'd + // iterators' buffers, and then returns that value. This allows each + // iterator to be advanced independently. + var iter = new goog.iter.Iterator(); + + iter.next = function() { + if (goog.array.isEmpty(buffer)) { + addNextIteratorValueToBuffers(); + } + goog.asserts.assert(!goog.array.isEmpty(buffer)); + return buffer.shift(); + }; + + return iter; + }; + + return goog.array.map(buffers, createIterator); }; /** - * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string - * that is known to satisfy the TrustedResourceUrl type contract. - * - * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure - * that the value of {@code url} satisfies the TrustedResourceUrl type contract - * in all possible program states. - * - * - * @param {!goog.string.Const} justification A constant string explaining why - * this use of this method is safe. May include a security review ticket - * number. - * @param {string} url The string to wrap as a TrustedResourceUrl. - * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in - * a TrustedResourceUrl object. + * Creates an iterator that returns arrays containing a count and an element + * obtained from the given {@code iterable}. + * @see http://docs.python.org/2/library/functions.html#enumerate + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to enumerate. + * @param {number=} opt_start Optional starting value. Default is 0. + * @return {!goog.iter.Iterator<!Array<?>>} A new iterator containing + * count/item pairs. + * @template VALUE */ -goog.html.uncheckedconversions. - trustedResourceUrlFromStringKnownToSatisfyTypeContract = - function(justification, url) { - // unwrap() called inside an assert so that justification can be optimized - // away in production code. - goog.asserts.assertString(goog.string.Const.unwrap(justification), - 'must provide justification'); - goog.asserts.assert( - !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)), - 'must provide non-empty justification'); - return goog.html.TrustedResourceUrl. - createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url); +goog.iter.enumerate = function(iterable, opt_start) { + return goog.iter.zip(goog.iter.count(opt_start), iterable); }; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Defines the collection interface. - * - * @author nnaze@google.com (Nathan Naze) + * Creates an iterator that returns the first {@code limitSize} elements from an + * iterable. If this number is greater than the number of elements in the + * iterable, all the elements are returned. + * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to limit. + * @param {number} limitSize The maximum number of elements to return. + * @return {!goog.iter.Iterator<VALUE>} A new iterator containing + * {@code limitSize} elements. + * @template VALUE */ +goog.iter.limit = function(iterable, limitSize) { + goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0); -goog.provide('goog.structs.Collection'); + var iterator = goog.iter.toIterator(iterable); + + var iter = new goog.iter.Iterator(); + var remaining = limitSize; + + iter.next = function() { + if (remaining-- > 0) { + return iterator.next(); + } + throw goog.iter.StopIteration; + }; + return iter; +}; /** - * An interface for a collection of values. - * @interface - * @template T + * Creates an iterator that is advanced {@code count} steps ahead. Consumed + * values are silently discarded. If {@code count} is greater than the number + * of elements in {@code iterable}, an empty iterator is returned. Subsequent + * calls to {@code next()} will throw {@code goog.iter.StopIteration}. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to consume. + * @param {number} count The number of elements to consume from the iterator. + * @return {!goog.iter.Iterator<VALUE>} An iterator advanced zero or more steps + * ahead. + * @template VALUE */ -goog.structs.Collection = function() {}; +goog.iter.consume = function(iterable, count) { + goog.asserts.assert(goog.math.isInt(count) && count >= 0); + + var iterator = goog.iter.toIterator(iterable); + + while (count-- > 0) { + goog.iter.nextOrValue(iterator, null); + } + + return iterator; +}; /** - * @param {T} value Value to add to the collection. + * Creates an iterator that returns a range of elements from an iterable. + * Similar to {@see goog.array#slice} but does not support negative indexes. + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to slice. + * @param {number} start The index of the first element to return. + * @param {number=} opt_end The index after the last element to return. If + * defined, must be greater than or equal to {@code start}. + * @return {!goog.iter.Iterator<VALUE>} A new iterator containing a slice of + * the original. + * @template VALUE */ -goog.structs.Collection.prototype.add; +goog.iter.slice = function(iterable, start, opt_end) { + goog.asserts.assert(goog.math.isInt(start) && start >= 0); + + var iterator = goog.iter.consume(iterable, start); + + if (goog.isNumber(opt_end)) { + goog.asserts.assert( + goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start); + iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */); + } + + return iterator; +}; /** - * @param {T} value Value to remove from the collection. + * Checks an array for duplicate elements. + * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to check for + * duplicates. + * @return {boolean} True, if the array contains duplicates, false otherwise. + * @private + * @template VALUE */ -goog.structs.Collection.prototype.remove; +// TODO(user): Consider moving this into goog.array as a public function. +goog.iter.hasDuplicates_ = function(arr) { + var deduped = []; + goog.array.removeDuplicates(arr, deduped); + return arr.length != deduped.length; +}; /** - * @param {T} value Value to find in the collection. - * @return {boolean} Whether the collection contains the specified value. + * Creates an iterator that returns permutations of elements in + * {@code iterable}. + * + * Permutations are obtained by taking the Cartesian product of + * {@code opt_length} iterables and filtering out those with repeated + * elements. For example, the permutations of {@code [1,2,3]} are + * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.permutations + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable from which to generate permutations. + * @param {number=} opt_length Length of each permutation. If omitted, defaults + * to the length of {@code iterable}. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing the + * permutations of {@code iterable}. + * @template VALUE */ -goog.structs.Collection.prototype.contains; +goog.iter.permutations = function(iterable, opt_length) { + var elements = goog.iter.toArray(iterable); + var length = goog.isNumber(opt_length) ? opt_length : elements.length; + + var sets = goog.array.repeat(elements, length); + var product = goog.iter.product.apply(undefined, sets); + + return goog.iter.filter(product, function(arr) { + return !goog.iter.hasDuplicates_(arr); + }); +}; /** - * @return {number} The number of values stored in the collection. + * Creates an iterator that returns combinations of elements from + * {@code iterable}. + * + * Combinations are obtained by taking the {@see goog.iter#permutations} of + * {@code iterable} and filtering those whose elements appear in the order they + * are encountered in {@code iterable}. For example, the 3-length combinations + * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.combinations + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable from which to generate combinations. + * @param {number} length The length of each combination. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing + * combinations from the {@code iterable}. + * @template VALUE */ -goog.structs.Collection.prototype.getCount; +goog.iter.combinations = function(iterable, length) { + var elements = goog.iter.toArray(iterable); + var indexes = goog.iter.range(elements.length); + var indexIterator = goog.iter.permutations(indexes, length); + // sortedIndexIterator will now give arrays of with the given length that + // indicate what indexes into "elements" should be returned on each iteration. + var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) { + return goog.array.isSorted(arr); + }); + + var iter = new goog.iter.Iterator(); + + function getIndexFromElements(index) { + return elements[index]; + } + + iter.next = function() { + return goog.array.map( + /** @type {!Array<number>} */ + (sortedIndexIterator.next()), getIndexFromElements); + }; + + return iter; +}; + + +/** + * Creates an iterator that returns combinations of elements from + * {@code iterable}, with repeated elements possible. + * + * Combinations are obtained by taking the Cartesian product of {@code length} + * iterables and filtering those whose elements appear in the order they are + * encountered in {@code iterable}. For example, the 2-length combinations of + * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}. + * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement + * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition + * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The + * iterable to combine. + * @param {number} length The length of each combination. + * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing + * combinations from the {@code iterable}. + * @template VALUE + */ +goog.iter.combinationsWithReplacement = function(iterable, length) { + var elements = goog.iter.toArray(iterable); + var indexes = goog.array.range(elements.length); + var sets = goog.array.repeat(indexes, length); + var indexIterator = goog.iter.product.apply(undefined, sets); + // sortedIndexIterator will now give arrays of with the given length that + // indicate what indexes into "elements" should be returned on each iteration. + var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) { + return goog.array.isSorted(arr); + }); + + var iter = new goog.iter.Iterator(); + + function getIndexFromElements(index) { + return elements[index]; + } + + iter.next = function() { + return goog.array.map( + /** @type {!Array<number>} */ + (sortedIndexIterator.next()), getIndexFromElements); + }; + return iter; +}; // Copyright 2006 The Closure Library Authors. All Rights Reserved. // @@ -40845,80 +41587,541 @@ goog.structs.Collection.prototype.getCount; // limitations under the License. /** - * @fileoverview Datastructure: Set. + * @fileoverview Datastructure: Hash Map. * * @author arv@google.com (Erik Arvidsson) * - * This class implements a set data structure. Adding and removing is O(1). It - * supports both object and primitive values. Be careful because you can add - * both 1 and new Number(1), because these are not the same. You can even add - * multiple new Number(1) because these are not equal. + * This file contains an implementation of a Map structure. It implements a lot + * of the methods used in goog.structs so those functions work on hashes. This + * is best suited for complex key types. For simple keys such as numbers and + * strings, and where special names like __proto__ are not a concern, consider + * using the lighter-weight utilities in goog.object. */ -goog.provide('goog.structs.Set'); +goog.provide('goog.structs.Map'); -goog.require('goog.structs'); -goog.require('goog.structs.Collection'); -goog.require('goog.structs.Map'); +goog.require('goog.iter.Iterator'); +goog.require('goog.iter.StopIteration'); +goog.require('goog.object'); /** - * A set that can contain both primitives and objects. Adding and removing - * elements is O(1). Primitives are treated as identical if they have the same - * type and convert to the same string. Objects are treated as identical only - * if they are references to the same object. WARNING: A goog.structs.Set can - * contain both 1 and (new Number(1)), because they are not the same. WARNING: - * Adding (new Number(1)) twice will yield two distinct elements, because they - * are two different objects. WARNING: Any object that is added to a - * goog.structs.Set will be modified! Because goog.getUid() is used to - * identify objects, every object in the set will be mutated. - * @param {Array<T>|Object<?,T>=} opt_values Initial values to start with. + * Class for Hash Map datastructure. + * @param {*=} opt_map Map or Object to initialize the map with. + * @param {...*} var_args If 2 or more arguments are present then they + * will be used as key-value pairs. * @constructor - * @implements {goog.structs.Collection<T>} - * @final - * @template T + * @template K, V */ -goog.structs.Set = function(opt_values) { - this.map_ = new goog.structs.Map; - if (opt_values) { - this.addAll(opt_values); - } -}; +goog.structs.Map = function(opt_map, var_args) { + + /** + * Underlying JS object used to implement the map. + * @private {!Object} + */ + this.map_ = {}; + /** + * An array of keys. This is necessary for two reasons: + * 1. Iterating the keys using for (var key in this.map_) allocates an + * object for every key in IE which is really bad for IE6 GC perf. + * 2. Without a side data structure, we would need to escape all the keys + * as that would be the only way we could tell during iteration if the + * key was an internal key or a property of the object. + * + * This array can contain deleted keys so it's necessary to check the map + * as well to see if the key is still in the map (this doesn't require a + * memory allocation in IE). + * @private {!Array<string>} + */ + this.keys_ = []; -/** - * Obtains a unique key for an element of the set. Primitives will yield the - * same key if they have the same type and convert to the same string. Object - * references will yield the same key only if they refer to the same object. - * @param {*} val Object or primitive value to get a key for. - * @return {string} A unique key for this value/object. - * @private - */ -goog.structs.Set.getKey_ = function(val) { - var type = typeof val; - if (type == 'object' && val || type == 'function') { - return 'o' + goog.getUid(/** @type {Object} */ (val)); - } else { - return type.substr(0, 1) + val; + /** + * The number of key value pairs in the map. + * @private {number} + */ + this.count_ = 0; + + /** + * Version used to detect changes while iterating. + * @private {number} + */ + this.version_ = 0; + + var argLength = arguments.length; + + if (argLength > 1) { + if (argLength % 2) { + throw Error('Uneven number of arguments'); + } + for (var i = 0; i < argLength; i += 2) { + this.set(arguments[i], arguments[i + 1]); + } + } else if (opt_map) { + this.addAll(/** @type {Object} */ (opt_map)); } }; /** - * @return {number} The number of elements in the set. - * @override + * @return {number} The number of key-value pairs in the map. */ -goog.structs.Set.prototype.getCount = function() { - return this.map_.getCount(); +goog.structs.Map.prototype.getCount = function() { + return this.count_; }; /** - * Add a primitive or an object to the set. - * @param {T} element The primitive or object to add. - * @override + * Returns the values of the map. + * @return {!Array<V>} The values in the map. + */ +goog.structs.Map.prototype.getValues = function() { + this.cleanupKeysArray_(); + + var rv = []; + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + rv.push(this.map_[key]); + } + return rv; +}; + + +/** + * Returns the keys of the map. + * @return {!Array<string>} Array of string values. + */ +goog.structs.Map.prototype.getKeys = function() { + this.cleanupKeysArray_(); + return /** @type {!Array<string>} */ (this.keys_.concat()); +}; + + +/** + * Whether the map contains the given key. + * @param {*} key The key to check for. + * @return {boolean} Whether the map contains the key. + */ +goog.structs.Map.prototype.containsKey = function(key) { + return goog.structs.Map.hasKey_(this.map_, key); +}; + + +/** + * Whether the map contains the given value. This is O(n). + * @param {V} val The value to check for. + * @return {boolean} Whether the map contains the value. + */ +goog.structs.Map.prototype.containsValue = function(val) { + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) { + return true; + } + } + return false; +}; + + +/** + * Whether this map is equal to the argument map. + * @param {goog.structs.Map} otherMap The map against which to test equality. + * @param {function(V, V): boolean=} opt_equalityFn Optional equality function + * to test equality of values. If not specified, this will test whether + * the values contained in each map are identical objects. + * @return {boolean} Whether the maps are equal. + */ +goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) { + if (this === otherMap) { + return true; + } + + if (this.count_ != otherMap.getCount()) { + return false; + } + + var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals; + + this.cleanupKeysArray_(); + for (var key, i = 0; key = this.keys_[i]; i++) { + if (!equalityFn(this.get(key), otherMap.get(key))) { + return false; + } + } + + return true; +}; + + +/** + * Default equality test for values. + * @param {*} a The first value. + * @param {*} b The second value. + * @return {boolean} Whether a and b reference the same object. + */ +goog.structs.Map.defaultEquals = function(a, b) { + return a === b; +}; + + +/** + * @return {boolean} Whether the map is empty. + */ +goog.structs.Map.prototype.isEmpty = function() { + return this.count_ == 0; +}; + + +/** + * Removes all key-value pairs from the map. + */ +goog.structs.Map.prototype.clear = function() { + this.map_ = {}; + this.keys_.length = 0; + this.count_ = 0; + this.version_ = 0; +}; + + +/** + * Removes a key-value pair based on the key. This is O(logN) amortized due to + * updating the keys array whenever the count becomes half the size of the keys + * in the keys array. + * @param {*} key The key to remove. + * @return {boolean} Whether object was removed. + */ +goog.structs.Map.prototype.remove = function(key) { + if (goog.structs.Map.hasKey_(this.map_, key)) { + delete this.map_[key]; + this.count_--; + this.version_++; + + // clean up the keys array if the threshhold is hit + if (this.keys_.length > 2 * this.count_) { + this.cleanupKeysArray_(); + } + + return true; + } + return false; +}; + + +/** + * Cleans up the temp keys array by removing entries that are no longer in the + * map. + * @private + */ +goog.structs.Map.prototype.cleanupKeysArray_ = function() { + if (this.count_ != this.keys_.length) { + // First remove keys that are no longer in the map. + var srcIndex = 0; + var destIndex = 0; + while (srcIndex < this.keys_.length) { + var key = this.keys_[srcIndex]; + if (goog.structs.Map.hasKey_(this.map_, key)) { + this.keys_[destIndex++] = key; + } + srcIndex++; + } + this.keys_.length = destIndex; + } + + if (this.count_ != this.keys_.length) { + // If the count still isn't correct, that means we have duplicates. This can + // happen when the same key is added and removed multiple times. Now we have + // to allocate one extra Object to remove the duplicates. This could have + // been done in the first pass, but in the common case, we can avoid + // allocating an extra object by only doing this when necessary. + var seen = {}; + var srcIndex = 0; + var destIndex = 0; + while (srcIndex < this.keys_.length) { + var key = this.keys_[srcIndex]; + if (!(goog.structs.Map.hasKey_(seen, key))) { + this.keys_[destIndex++] = key; + seen[key] = 1; + } + srcIndex++; + } + this.keys_.length = destIndex; + } +}; + + +/** + * Returns the value for the given key. If the key is not found and the default + * value is not given this will return {@code undefined}. + * @param {*} key The key to get the value for. + * @param {DEFAULT=} opt_val The value to return if no item is found for the + * given key, defaults to undefined. + * @return {V|DEFAULT} The value for the given key. + * @template DEFAULT + */ +goog.structs.Map.prototype.get = function(key, opt_val) { + if (goog.structs.Map.hasKey_(this.map_, key)) { + return this.map_[key]; + } + return opt_val; +}; + + +/** + * Adds a key-value pair to the map. + * @param {*} key The key. + * @param {V} value The value to add. + * @return {*} Some subclasses return a value. + */ +goog.structs.Map.prototype.set = function(key, value) { + if (!(goog.structs.Map.hasKey_(this.map_, key))) { + this.count_++; + this.keys_.push(key); + // Only change the version if we add a new key. + this.version_++; + } + this.map_[key] = value; +}; + + +/** + * Adds multiple key-value pairs from another goog.structs.Map or Object. + * @param {Object} map Object containing the data to add. + */ +goog.structs.Map.prototype.addAll = function(map) { + var keys, values; + if (map instanceof goog.structs.Map) { + keys = map.getKeys(); + values = map.getValues(); + } else { + keys = goog.object.getKeys(map); + values = goog.object.getValues(map); + } + // we could use goog.array.forEach here but I don't want to introduce that + // dependency just for this. + for (var i = 0; i < keys.length; i++) { + this.set(keys[i], values[i]); + } +}; + + +/** + * Calls the given function on each entry in the map. + * @param {function(this:T, V, K, goog.structs.Map<K,V>)} f + * @param {T=} opt_obj The value of "this" inside f. + * @template T + */ +goog.structs.Map.prototype.forEach = function(f, opt_obj) { + var keys = this.getKeys(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = this.get(key); + f.call(opt_obj, value, key, this); + } +}; + + +/** + * Clones a map and returns a new map. + * @return {!goog.structs.Map} A new map with the same key-value pairs. + */ +goog.structs.Map.prototype.clone = function() { + return new goog.structs.Map(this); +}; + + +/** + * Returns a new map in which all the keys and values are interchanged + * (keys become values and values become keys). If multiple keys map to the + * same value, the chosen transposed value is implementation-dependent. + * + * It acts very similarly to {goog.object.transpose(Object)}. + * + * @return {!goog.structs.Map} The transposed map. + */ +goog.structs.Map.prototype.transpose = function() { + var transposed = new goog.structs.Map(); + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + var value = this.map_[key]; + transposed.set(value, key); + } + + return transposed; +}; + + +/** + * @return {!Object} Object representation of the map. + */ +goog.structs.Map.prototype.toObject = function() { + this.cleanupKeysArray_(); + var obj = {}; + for (var i = 0; i < this.keys_.length; i++) { + var key = this.keys_[i]; + obj[key] = this.map_[key]; + } + return obj; +}; + + +/** + * Returns an iterator that iterates over the keys in the map. Removal of keys + * while iterating might have undesired side effects. + * @return {!goog.iter.Iterator} An iterator over the keys in the map. + */ +goog.structs.Map.prototype.getKeyIterator = function() { + return this.__iterator__(true); +}; + + +/** + * Returns an iterator that iterates over the values in the map. Removal of + * keys while iterating might have undesired side effects. + * @return {!goog.iter.Iterator} An iterator over the values in the map. + */ +goog.structs.Map.prototype.getValueIterator = function() { + return this.__iterator__(false); +}; + + +/** + * Returns an iterator that iterates over the values or the keys in the map. + * This throws an exception if the map was mutated since the iterator was + * created. + * @param {boolean=} opt_keys True to iterate over the keys. False to iterate + * over the values. The default value is false. + * @return {!goog.iter.Iterator} An iterator over the values or keys in the map. + */ +goog.structs.Map.prototype.__iterator__ = function(opt_keys) { + // Clean up keys to minimize the risk of iterating over dead keys. + this.cleanupKeysArray_(); + + var i = 0; + var keys = this.keys_; + var map = this.map_; + var version = this.version_; + var selfObj = this; + + var newIter = new goog.iter.Iterator; + newIter.next = function() { + while (true) { + if (version != selfObj.version_) { + throw Error('The map has changed since the iterator was created'); + } + if (i >= keys.length) { + throw goog.iter.StopIteration; + } + var key = keys[i++]; + return opt_keys ? key : map[key]; + } + }; + return newIter; +}; + + +/** + * Safe way to test for hasOwnProperty. It even allows testing for + * 'hasOwnProperty'. + * @param {Object} obj The object to test for presence of the given key. + * @param {*} key The key to check for. + * @return {boolean} Whether the object has the key. + * @private + */ +goog.structs.Map.hasKey_ = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +}; + +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Datastructure: Set. + * + * @author arv@google.com (Erik Arvidsson) + * + * This class implements a set data structure. Adding and removing is O(1). It + * supports both object and primitive values. Be careful because you can add + * both 1 and new Number(1), because these are not the same. You can even add + * multiple new Number(1) because these are not equal. + */ + + +goog.provide('goog.structs.Set'); + +goog.require('goog.structs'); +goog.require('goog.structs.Collection'); +goog.require('goog.structs.Map'); + + + +/** + * A set that can contain both primitives and objects. Adding and removing + * elements is O(1). Primitives are treated as identical if they have the same + * type and convert to the same string. Objects are treated as identical only + * if they are references to the same object. WARNING: A goog.structs.Set can + * contain both 1 and (new Number(1)), because they are not the same. WARNING: + * Adding (new Number(1)) twice will yield two distinct elements, because they + * are two different objects. WARNING: Any object that is added to a + * goog.structs.Set will be modified! Because goog.getUid() is used to + * identify objects, every object in the set will be mutated. + * @param {Array<T>|Object<?,T>=} opt_values Initial values to start with. + * @constructor + * @implements {goog.structs.Collection<T>} + * @final + * @template T + */ +goog.structs.Set = function(opt_values) { + this.map_ = new goog.structs.Map; + if (opt_values) { + this.addAll(opt_values); + } +}; + + +/** + * Obtains a unique key for an element of the set. Primitives will yield the + * same key if they have the same type and convert to the same string. Object + * references will yield the same key only if they refer to the same object. + * @param {*} val Object or primitive value to get a key for. + * @return {string} A unique key for this value/object. + * @private + */ +goog.structs.Set.getKey_ = function(val) { + var type = typeof val; + if (type == 'object' && val || type == 'function') { + return 'o' + goog.getUid(/** @type {Object} */ (val)); + } else { + return type.substr(0, 1) + val; + } +}; + + +/** + * @return {number} The number of elements in the set. + * @override + */ +goog.structs.Set.prototype.getCount = function() { + return this.map_.getCount(); +}; + + +/** + * Add a primitive or an object to the set. + * @param {T} element The primitive or object to add. + * @override */ goog.structs.Set.prototype.add = function(element) { this.map_.set(goog.structs.Set.getKey_(element), element); @@ -45591,6 +46794,529 @@ ol.pointer.PointerEvent.HAS_BUTTONS = false; } })(); +// FIXME add tests for browser features (Modernizr?) + +goog.provide('ol.dom'); +goog.provide('ol.dom.BrowserFeature'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.userAgent'); +goog.require('goog.vec.Mat4'); +goog.require('ol'); + + +/** + * Create an html canvas element and returns its 2d context. + * @param {number=} opt_width Canvas width. + * @param {number=} opt_height Canvas height. + * @return {CanvasRenderingContext2D} + */ +ol.dom.createCanvasContext2D = function(opt_width, opt_height) { + var canvas = goog.dom.createElement(goog.dom.TagName.CANVAS); + if (goog.isDef(opt_width)) { + canvas.width = opt_width; + } + if (goog.isDef(opt_height)) { + canvas.height = opt_height; + } + return canvas.getContext('2d'); +}; + + +/** + * @enum {boolean} + */ +ol.dom.BrowserFeature = { + USE_MS_MATRIX_TRANSFORM: ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE, + USE_MS_ALPHA_FILTER: ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE +}; + + +/** + * Detect 2d transform. + * Adapted from http://stackoverflow.com/q/5661671/130442 + * http://caniuse.com/#feat=transforms2d + * @return {boolean} + */ +ol.dom.canUseCssTransform = (function() { + var canUseCssTransform; + return function() { + if (!goog.isDef(canUseCssTransform)) { + goog.asserts.assert(!goog.isNull(document.body), + 'document.body should not be null'); + if (!goog.global.getComputedStyle) { + // this browser is ancient + canUseCssTransform = false; + } else { + var el = goog.dom.createElement(goog.dom.TagName.P), + has2d, + transforms = { + 'webkitTransform': '-webkit-transform', + 'OTransform': '-o-transform', + 'msTransform': '-ms-transform', + 'MozTransform': '-moz-transform', + 'transform': 'transform' + }; + goog.dom.appendChild(document.body, el); + for (var t in transforms) { + if (t in el.style) { + el.style[t] = 'translate(1px,1px)'; + has2d = goog.global.getComputedStyle(el).getPropertyValue( + transforms[t]); + } + } + goog.dom.removeNode(el); + + canUseCssTransform = (has2d && has2d !== 'none'); + } + } + return canUseCssTransform; + }; +}()); + + +/** + * Detect 3d transform. + * Adapted from http://stackoverflow.com/q/5661671/130442 + * http://caniuse.com/#feat=transforms3d + * @return {boolean} + */ +ol.dom.canUseCssTransform3D = (function() { + var canUseCssTransform3D; + return function() { + if (!goog.isDef(canUseCssTransform3D)) { + goog.asserts.assert(!goog.isNull(document.body), + 'document.body should not be null'); + if (!goog.global.getComputedStyle) { + // this browser is ancient + canUseCssTransform3D = false; + } else { + var el = goog.dom.createElement(goog.dom.TagName.P), + has3d, + transforms = { + 'webkitTransform': '-webkit-transform', + 'OTransform': '-o-transform', + 'msTransform': '-ms-transform', + 'MozTransform': '-moz-transform', + 'transform': 'transform' + }; + goog.dom.appendChild(document.body, el); + for (var t in transforms) { + if (t in el.style) { + el.style[t] = 'translate3d(1px,1px,1px)'; + has3d = goog.global.getComputedStyle(el).getPropertyValue( + transforms[t]); + } + } + goog.dom.removeNode(el); + + canUseCssTransform3D = (has3d && has3d !== 'none'); + } + } + return canUseCssTransform3D; + }; +}()); + + +/** + * @param {Element} element Element. + * @param {string} value Value. + */ +ol.dom.setTransform = function(element, value) { + var style = element.style; + style.WebkitTransform = value; + style.MozTransform = value; + style.OTransform = value; + style.msTransform = value; + style.transform = value; + + // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason + if (goog.userAgent.IE && !ol.IS_LEGACY_IE) { + element.style.transformOrigin = '0 0'; + } +}; + + +/** + * Sets the opacity of an element, in an IE-compatible way + * @param {!Element} element Element + * @param {number} value Opacity, [0..1] + */ +ol.dom.setOpacity = function(element, value) { + if (ol.dom.BrowserFeature.USE_MS_ALPHA_FILTER) { + /** @type {string} */ + var filter = element.currentStyle.filter; + + /** @type {RegExp} */ + var regex; + + /** @type {string} */ + var alpha; + + if (goog.userAgent.VERSION == '8.0') { + regex = /progid:DXImageTransform\.Microsoft\.Alpha\(.*?\)/i; + alpha = 'progid:DXImageTransform.Microsoft.Alpha(Opacity=' + + (value * 100) + ')'; + } else { + regex = /alpha\(.*?\)/i; + alpha = 'alpha(opacity=' + (value * 100) + ')'; + } + + var newFilter = filter.replace(regex, alpha); + if (newFilter === filter) { + // no replace was made? just append the new alpha filter instead + newFilter += ' ' + alpha; + } + + element.style.filter = newFilter; + + // Fix to apply filter to absolutely-positioned children element + if (element.currentStyle.zIndex === 'auto') { + element.style.zIndex = 0; + } + } else { + element.style.opacity = value; + } +}; + + +/** + * Sets the IE matrix transform without replacing other filters + * @private + * @param {!Element} element Element + * @param {string} value The new progid string + */ +ol.dom.setIEMatrix_ = function(element, value) { + var filter = element.currentStyle.filter; + var newFilter = + filter.replace(/progid:DXImageTransform.Microsoft.Matrix\(.*?\)/i, value); + + if (newFilter === filter) { + newFilter = ' ' + value; + } + + element.style.filter = newFilter; + + // Fix to apply filter to absolutely-positioned children element + if (element.currentStyle.zIndex === 'auto') { + element.style.zIndex = 0; + } +}; + + +/** + * @param {!Element} element Element. + * @param {goog.vec.Mat4.Number} transform Matrix. + * @param {number=} opt_precision Precision. + * @param {Element=} opt_translationElement Required for IE7-8 + */ +ol.dom.transformElement2D = + function(element, transform, opt_precision, opt_translationElement) { + // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer + // matrix3d() + var i; + if (ol.dom.canUseCssTransform3D()) { + var value3D; + + if (goog.isDef(opt_precision)) { + /** @type {Array.<string>} */ + var strings3D = new Array(16); + for (i = 0; i < 16; ++i) { + strings3D[i] = transform[i].toFixed(opt_precision); + } + value3D = strings3D.join(','); + } else { + value3D = transform.join(','); + } + ol.dom.setTransform(element, 'matrix3d(' + value3D + ')'); + } else if (ol.dom.canUseCssTransform()) { + /** @type {Array.<number>} */ + var transform2D = [ + goog.vec.Mat4.getElement(transform, 0, 0), + goog.vec.Mat4.getElement(transform, 1, 0), + goog.vec.Mat4.getElement(transform, 0, 1), + goog.vec.Mat4.getElement(transform, 1, 1), + goog.vec.Mat4.getElement(transform, 0, 3), + goog.vec.Mat4.getElement(transform, 1, 3) + ]; + var value2D; + if (goog.isDef(opt_precision)) { + /** @type {Array.<string>} */ + var strings2D = new Array(6); + for (i = 0; i < 6; ++i) { + strings2D[i] = transform2D[i].toFixed(opt_precision); + } + value2D = strings2D.join(','); + } else { + value2D = transform2D.join(','); + } + ol.dom.setTransform(element, 'matrix(' + value2D + ')'); + } else if (ol.dom.BrowserFeature.USE_MS_MATRIX_TRANSFORM) { + var m11 = goog.vec.Mat4.getElement(transform, 0, 0), + m12 = goog.vec.Mat4.getElement(transform, 0, 1), + m21 = goog.vec.Mat4.getElement(transform, 1, 0), + m22 = goog.vec.Mat4.getElement(transform, 1, 1), + dx = goog.vec.Mat4.getElement(transform, 0, 3), + dy = goog.vec.Mat4.getElement(transform, 1, 3); + + // See: http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx + // and: http://extremelysatisfactorytotalitarianism.com/blog/?p=1002 + // @TODO: fix terrible IE bbox rotation issue. + var s = 'progid:DXImageTransform.Microsoft.Matrix('; + s += 'sizingMethod="auto expand"'; + s += ',M11=' + m11.toFixed(opt_precision || 20); + s += ',M12=' + m12.toFixed(opt_precision || 20); + s += ',M21=' + m21.toFixed(opt_precision || 20); + s += ',M22=' + m22.toFixed(opt_precision || 20); + s += ')'; + ol.dom.setIEMatrix_(element, s); + + // scale = m11 = m22 = target resolution [m/px] / current res [m/px] + // dx = (viewport width [px] / 2) * scale + // + (layer.x [m] - view.x [m]) / target resolution [m / px] + // except that we're positioning the child element relative to the + // viewport, not the map. + // dividing by the scale factor isn't the exact correction, but it's + // close enough that you can barely tell unless you're looking for it + dx /= m11; + dy /= m22; + + opt_translationElement.style.left = Math.round(dx) + 'px'; + opt_translationElement.style.top = Math.round(dy) + 'px'; + } else { + element.style.left = + Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px'; + element.style.top = + Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px'; + + // TODO: Add scaling here. This isn't quite as simple as multiplying + // width/height, because that only changes the container size, not the + // content size. + } +}; + + +/** + * Get the current computed width for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerWidth(true)`. + * @param {!Element} element Element. + * @return {number} + */ +ol.dom.outerWidth = function(element) { + var width = element.offsetWidth; + var style = element.currentStyle || window.getComputedStyle(element); + width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10); + + return width; +}; + + +/** + * Get the current computed height for the given element including margin, + * padding and border. + * Equivalent to jQuery's `$(el).outerHeight(true)`. + * @param {!Element} element Element. + * @return {number} + */ +ol.dom.outerHeight = function(element) { + var height = element.offsetHeight; + var style = element.currentStyle || window.getComputedStyle(element); + height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10); + + return height; +}; + +goog.provide('ol.webgl'); +goog.provide('ol.webgl.WebGLContextEventType'); + + +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.webgl.CONTEXT_IDS_ = [ + 'experimental-webgl', + 'webgl', + 'webkit-3d', + 'moz-webgl' +]; + + +/** + * @enum {string} + */ +ol.webgl.WebGLContextEventType = { + LOST: 'webglcontextlost', + RESTORED: 'webglcontextrestored' +}; + + +/** + * @param {HTMLCanvasElement} canvas Canvas. + * @param {Object=} opt_attributes Attributes. + * @return {WebGLRenderingContext} WebGL rendering context. + */ +ol.webgl.getContext = function(canvas, opt_attributes) { + var context, i, ii = ol.webgl.CONTEXT_IDS_.length; + for (i = 0; i < ii; ++i) { + try { + context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); + if (!goog.isNull(context)) { + return /** @type {!WebGLRenderingContext} */ (context); + } + } catch (e) { + } + } + return null; +}; + +goog.provide('ol.has'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('ol'); +goog.require('ol.dom'); +goog.require('ol.webgl'); + + +/** + * The ratio between physical pixels and device-independent pixels + * (dips) on the device (`window.devicePixelRatio`). + * @const + * @type {number} + * @api stable + */ +ol.has.DEVICE_PIXEL_RATIO = goog.global.devicePixelRatio || 1; + + +/** + * True if the browser's Canvas implementation implements {get,set}LineDash. + * @type {boolean} + */ +ol.has.CANVAS_LINE_DASH = false; + + +/** + * True if browser supports Canvas. + * @const + * @type {boolean} + * @api stable + */ +ol.has.CANVAS = ol.ENABLE_CANVAS && ( + /** + * @return {boolean} Canvas supported. + */ + function() { + if (!('HTMLCanvasElement' in goog.global)) { + return false; + } + try { + var context = ol.dom.createCanvasContext2D(); + if (goog.isNull(context)) { + return false; + } else { + if (goog.isDef(context.setLineDash)) { + ol.has.CANVAS_LINE_DASH = true; + } + return true; + } + } catch (e) { + return false; + } + })(); + + +/** + * Indicates if DeviceOrientation is supported in the user's browser. + * @const + * @type {boolean} + * @api stable + */ +ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in goog.global; + + +/** + * True if browser supports DOM. + * @const + * @type {boolean} + */ +ol.has.DOM = ol.ENABLE_DOM; + + +/** + * Is HTML5 geolocation supported in the current browser? + * @const + * @type {boolean} + * @api stable + */ +ol.has.GEOLOCATION = 'geolocation' in goog.global.navigator; + + +/** + * True if browser supports touch events. + * @const + * @type {boolean} + * @api stable + */ +ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in goog.global; + + +/** + * True if browser supports pointer events. + * @const + * @type {boolean} + */ +ol.has.POINTER = 'PointerEvent' in goog.global; + + +/** + * True if browser supports ms pointer events (IE 10). + * @const + * @type {boolean} + */ +ol.has.MSPOINTER = !!(goog.global.navigator.msPointerEnabled); + + +/** + * True if browser supports WebGL. + * @const + * @type {boolean} + * @api stable + */ +ol.has.WEBGL; + + +(function() { + if (ol.ENABLE_WEBGL) { + var hasWebGL = false; + var textureSize; + var /** @type {Array.<string>} */ extensions = []; + + if ('WebGLRenderingContext' in goog.global) { + try { + var canvas = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + var gl = ol.webgl.getContext(canvas, { + failIfMajorPerformanceCaveat: true + }); + if (!goog.isNull(gl)) { + hasWebGL = true; + textureSize = /** @type {number} */ + (gl.getParameter(gl.MAX_TEXTURE_SIZE)); + extensions = gl.getSupportedExtensions(); + } + } catch (e) {} + } + ol.has.WEBGL = hasWebGL; + ol.WEBGL_EXTENSIONS = extensions; + ol.WEBGL_MAX_TEXTURE_SIZE = textureSize; + } +})(); + goog.provide('ol.pointer.EventSource'); goog.require('goog.events.BrowserEvent'); @@ -45818,8 +47544,7 @@ ol.pointer.MouseSource.prototype.mousedown = function(inEvent) { this.cancel(inEvent); } var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); - goog.object.set(this.pointerMap, - ol.pointer.MouseSource.POINTER_ID.toString(), inEvent); + this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent; this.dispatcher.down(e, inEvent); } }; @@ -45845,8 +47570,7 @@ ol.pointer.MouseSource.prototype.mousemove = function(inEvent) { */ ol.pointer.MouseSource.prototype.mouseup = function(inEvent) { if (!this.isEventSimulatedFromTouch_(inEvent)) { - var p = goog.object.get(this.pointerMap, - ol.pointer.MouseSource.POINTER_ID.toString()); + var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()]; if (p && p.button === inEvent.button) { var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher); @@ -46013,8 +47737,7 @@ ol.pointer.MsSource.prototype.cleanup = function(pointerId) { * @param {goog.events.BrowserEvent} inEvent */ ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) { - goog.object.set(this.pointerMap, - inEvent.getBrowserEvent().pointerId, inEvent); + this.pointerMap[inEvent.getBrowserEvent().pointerId] = inEvent; var e = this.prepareEvent_(inEvent); this.dispatcher.down(e, inEvent); }; @@ -46426,7 +48149,7 @@ ol.pointer.TouchSource.prototype.touchToPointer_ = // Touch identifiers can start at 0. // Add 2 to the touch identifier for compatibility. e.pointerId = inTouch.identifier + 2; - // TODO: check if this is neccessary? + // TODO: check if this is necessary? //e.target = findTarget(e); e.bubbles = true; e.cancelable = true; @@ -46551,11 +48274,11 @@ ol.pointer.TouchSource.prototype.touchstart = function(inEvent) { * @param {Object} inPointer */ ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) { - goog.object.set(this.pointerMap, inPointer.pointerId, { + this.pointerMap[inPointer.pointerId] = { target: inPointer.target, out: inPointer, outTarget: inPointer.target - }); + }; this.dispatcher.over(inPointer, browserEvent); this.dispatcher.enter(inPointer, browserEvent); this.dispatcher.down(inPointer, browserEvent); @@ -46581,7 +48304,7 @@ ol.pointer.TouchSource.prototype.touchmove = function(inEvent) { ol.pointer.TouchSource.prototype.moveOverOut_ = function(browserEvent, inPointer) { var event = inPointer; - var pointer = goog.object.get(this.pointerMap, event.pointerId); + var pointer = this.pointerMap[event.pointerId]; // a finger drifted off the screen, ignore it if (!pointer) { return; @@ -46724,6 +48447,7 @@ ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) { goog.provide('ol.pointer.PointerEventHandler'); goog.require('goog.array'); +goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.events.BrowserEvent'); goog.require('goog.events.Event'); @@ -47060,7 +48784,10 @@ ol.pointer.PointerEventHandler.prototype.enterOver = */ ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) { - return container.contains(contained); + if (goog.isNull(contained)) { + return false; + } + return goog.dom.contains(container, contained); }; @@ -47220,9 +48947,11 @@ goog.require('ol.pointer.PointerEventHandler'); * @param {string} type Event type. * @param {ol.Map} map Map. * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @param {boolean=} opt_dragging Is the map currently being dragged? * @param {?olx.FrameState=} opt_frameState Frame state. */ -ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) { +ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging, + opt_frameState) { goog.base(this, type, map, opt_frameState); @@ -47233,6 +48962,7 @@ ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) { this.browserEvent = browserEvent; /** + * The original browser event. * @const * @type {Event} * @api stable @@ -47240,17 +48970,28 @@ ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) { this.originalEvent = browserEvent.getBrowserEvent(); /** + * The pixel of the original browser event. * @type {ol.Pixel} * @api stable */ this.pixel = map.getEventPixel(this.originalEvent); /** + * The coordinate of the original browser event. * @type {ol.Coordinate} * @api stable */ this.coordinate = map.getCoordinateFromPixel(this.pixel); + /** + * Indicates if the map is currently being dragged. Only set for + * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`. + * + * @type {boolean} + * @api stable + */ + this.dragging = goog.isDef(opt_dragging) ? opt_dragging : false; + }; goog.inherits(ol.MapBrowserEvent, ol.MapEvent); @@ -47286,11 +49027,14 @@ ol.MapBrowserEvent.prototype.stopPropagation = function() { * @param {string} type Event type. * @param {ol.Map} map Map. * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @param {boolean=} opt_dragging Is the map currently being dragged? * @param {?olx.FrameState=} opt_frameState Frame state. */ -ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_frameState) { +ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging, + opt_frameState) { - goog.base(this, type, map, pointerEvent.browserEvent, opt_frameState); + goog.base(this, type, map, pointerEvent.browserEvent, opt_dragging, + opt_frameState); /** * @const @@ -47329,10 +49073,10 @@ ol.MapBrowserEventHandler = function(map) { * @type {boolean} * @private */ - this.dragged_ = false; + this.dragging_ = false; /** - * @type {Array.<number>} + * @type {Array.<goog.events.Key>} * @private */ this.dragListenerKeys_ = null; @@ -47352,6 +49096,8 @@ ol.MapBrowserEventHandler = function(map) { } /** + * The most recent "down" type event (or null if none have occurred). + * Set on pointerdown. * @type {ol.pointer.PointerEvent} * @private */ @@ -47412,16 +49158,6 @@ ol.MapBrowserEventHandler = function(map) { goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget); -/** - * Get the last "down" type event. This will be set on pointerdown. - * @return {ol.pointer.PointerEvent} The most recent "down" type event (or null - * if none have occurred). - */ -ol.MapBrowserEventHandler.prototype.getDown = function() { - return this.down_; -}; - - /** * @param {goog.events.BrowserEvent} browserEvent Pointer event. * @private @@ -47494,22 +49230,26 @@ ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) { ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent); this.dispatchEvent(newEvent); - goog.asserts.assert(this.activePointers_ >= 0); + // We emulate click events on left mouse button click, touch contact, and pen + // contact. isMouseActionButton returns true in these cases (evt.button is set + // to 0). + // See http://www.w3.org/TR/pointerevents/#button-states + if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) { + goog.asserts.assert(!goog.isNull(this.down_), + 'this.down_ should not be null'); + this.emulateClick_(this.down_); + } + + goog.asserts.assert(this.activePointers_ >= 0, + 'this.activePointers_ should be equal to or larger than 0'); if (this.activePointers_ === 0) { goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey); this.dragListenerKeys_ = null; + this.dragging_ = false; + this.down_ = null; goog.dispose(this.documentPointerEventHandler_); this.documentPointerEventHandler_ = null; } - - // We emulate click event on left mouse button click, touch contact, and pen - // contact. isMouseActionButton returns true in these cases (evt.button is set - // to 0). - // See http://www.w3.org/TR/pointerevents/#button-states - if (!this.dragged_ && this.isMouseActionButton_(pointerEvent)) { - goog.asserts.assert(!goog.isNull(this.down_)); - this.emulateClick_(this.down_); - } }; @@ -47540,7 +49280,6 @@ ol.MapBrowserEventHandler.prototype.handlePointerDown_ = this.dispatchEvent(newEvent); this.down_ = pointerEvent; - this.dragged_ = false; if (goog.isNull(this.dragListenerKeys_)) { /* Set up a pointer event handler on the `document`, @@ -47589,11 +49328,11 @@ ol.MapBrowserEventHandler.prototype.handlePointerMove_ = // the exact same coordinates of the pointerdown event. To avoid a // 'false' touchmove event to be dispatched , we test if the pointer // effectively moved. - if (pointerEvent.clientX != this.down_.clientX || - pointerEvent.clientY != this.down_.clientY) { - this.dragged_ = true; + if (this.isMoving_(pointerEvent)) { + this.dragging_ = true; var newEvent = new ol.MapBrowserPointerEvent( - ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent); + ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent, + this.dragging_); this.dispatchEvent(newEvent); } @@ -47612,8 +49351,20 @@ ol.MapBrowserEventHandler.prototype.handlePointerMove_ = * @private */ ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) { + var dragging = !goog.isNull(this.down_) && this.isMoving_(pointerEvent); this.dispatchEvent(new ol.MapBrowserPointerEvent( - pointerEvent.type, this.map_, pointerEvent)); + pointerEvent.type, this.map_, pointerEvent, dragging)); +}; + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent Pointer event. + * @return {boolean} + * @private + */ +ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) { + return pointerEvent.clientX != this.down_.clientX || + pointerEvent.clientY != this.down_.clientY; }; @@ -47655,26 +49406,29 @@ ol.MapBrowserEventHandler.prototype.disposeInternal = function() { * @enum {string} */ ol.MapBrowserEvent.EventType = { - // derived event types + /** * A true single click with no dragging and no double click. Note that this * event is delayed by 250 ms to ensure that it is not a double click. * @event ol.MapBrowserEvent#singleclick - * @api + * @api stable */ SINGLECLICK: 'singleclick', + /** * A click with no dragging. A double click will fire two of this. * @event ol.MapBrowserEvent#click - * @api + * @api stable */ CLICK: goog.events.EventType.CLICK, + /** * A true double click, with no dragging. * @event ol.MapBrowserEvent#dblclick - * @api + * @api stable */ DBLCLICK: goog.events.EventType.DBLCLICK, + /** * Triggered when a pointer is dragged. * @event ol.MapBrowserEvent#pointerdrag @@ -47682,14 +49436,14 @@ ol.MapBrowserEvent.EventType = { */ POINTERDRAG: 'pointerdrag', - // original pointer event types /** * Triggered when a pointer is moved. Note that on touch devices this is * triggered when the map is panned, so is not the same as mousemove. * @event ol.MapBrowserEvent#pointermove - * @api + * @api stable */ POINTERMOVE: 'pointermove', + POINTERDOWN: 'pointerdown', POINTERUP: 'pointerup', POINTEROVER: 'pointerover', @@ -47699,172 +49453,6 @@ ol.MapBrowserEvent.EventType = { POINTERCANCEL: 'pointercancel' }; -goog.provide('ol.source.Source'); -goog.provide('ol.source.State'); - -goog.require('goog.events.EventType'); -goog.require('ol.Attribution'); -goog.require('ol.Observable'); -goog.require('ol.proj'); - - -/** - * State of the source, one of 'undefined', 'loading', 'ready' or 'error'. - * @enum {string} - * @api - */ -ol.source.State = { - UNDEFINED: 'undefined', - LOADING: 'loading', - READY: 'ready', - ERROR: 'error' -}; - - -/** - * @typedef {{attributions: (Array.<ol.Attribution>|undefined), - * logo: (string|olx.LogoOptions|undefined), - * projection: ol.proj.ProjectionLike, - * state: (ol.source.State|undefined)}} - */ -ol.source.SourceOptions; - - - -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for {@link ol.layer.Layer} sources. - * - * @constructor - * @extends {ol.Observable} - * @fires change Triggered when the state of the source changes. - * @param {ol.source.SourceOptions} options Source options. - * @api stable - */ -ol.source.Source = function(options) { - - goog.base(this); - - /** - * @private - * @type {ol.proj.Projection} - */ - this.projection_ = ol.proj.get(options.projection); - - /** - * @private - * @type {Array.<ol.Attribution>} - */ - this.attributions_ = goog.isDef(options.attributions) ? - options.attributions : null; - - /** - * @private - * @type {string|olx.LogoOptions|undefined} - */ - this.logo_ = options.logo; - - /** - * @private - * @type {ol.source.State} - */ - this.state_ = goog.isDef(options.state) ? - options.state : ol.source.State.READY; - -}; -goog.inherits(ol.source.Source, ol.Observable); - - -/** - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids. - * @param {function(ol.Feature): T} callback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.source.Source.prototype.forEachFeatureAtPixel = - goog.nullFunction; - - -/** - * @return {Array.<ol.Attribution>} Attributions. - * @api stable - */ -ol.source.Source.prototype.getAttributions = function() { - return this.attributions_; -}; - - -/** - * @return {string|olx.LogoOptions|undefined} Logo. - * @api stable - */ -ol.source.Source.prototype.getLogo = function() { - return this.logo_; -}; - - -/** - * @return {ol.proj.Projection} Projection. - * @api - */ -ol.source.Source.prototype.getProjection = function() { - return this.projection_; -}; - - -/** - * @return {Array.<number>|undefined} Resolutions. - */ -ol.source.Source.prototype.getResolutions = goog.abstractMethod; - - -/** - * @return {ol.source.State} State. - * @api - */ -ol.source.Source.prototype.getState = function() { - return this.state_; -}; - - -/** - * @param {Array.<ol.Attribution>} attributions Attributions. - */ -ol.source.Source.prototype.setAttributions = function(attributions) { - this.attributions_ = attributions; -}; - - -/** - * @param {string|olx.LogoOptions|undefined} logo Logo. - */ -ol.source.Source.prototype.setLogo = function(logo) { - this.logo_ = logo; -}; - - -/** - * @param {ol.source.State} state State. - * @protected - */ -ol.source.Source.prototype.setState = function(state) { - this.state_ = state; - this.changed(); -}; - - -/** - * @param {ol.proj.Projection} projection Projetion. - */ -ol.source.Source.prototype.setProjection = function(projection) { - this.projection_ = projection; -}; - goog.provide('ol.layer.Base'); goog.provide('ol.layer.LayerProperty'); goog.provide('ol.layer.LayerState'); @@ -47902,6 +49490,7 @@ ol.layer.LayerProperty = { * saturation: number, * sourceState: ol.source.State, * visible: boolean, + * managed: boolean, * extent: (ol.Extent|undefined), * maxResolution: number, * minResolution: number}} @@ -47954,47 +49543,36 @@ goog.inherits(ol.layer.Base, ol.Object); /** - * @return {number|undefined} The brightness of the layer. + * Return the brightness of the layer. + * @return {number} The brightness of the layer. * @observable * @api */ ol.layer.Base.prototype.getBrightness = function() { - return /** @type {number|undefined} */ ( - this.get(ol.layer.LayerProperty.BRIGHTNESS)); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.BRIGHTNESS)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getBrightness', - ol.layer.Base.prototype.getBrightness); /** - * @return {number|undefined} The contrast of the layer. + * Return the contrast of the layer. + * @return {number} The contrast of the layer. * @observable * @api */ ol.layer.Base.prototype.getContrast = function() { - return /** @type {number|undefined} */ ( - this.get(ol.layer.LayerProperty.CONTRAST)); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.CONTRAST)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getContrast', - ol.layer.Base.prototype.getContrast); /** - * @return {number|undefined} The hue of the layer. + * Return the hue of the layer. + * @return {number} The hue of the layer. * @observable * @api */ ol.layer.Base.prototype.getHue = function() { - return /** @type {number|undefined} */ (this.get(ol.layer.LayerProperty.HUE)); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getHue', - ol.layer.Base.prototype.getHue); /** @@ -48013,16 +49591,17 @@ ol.layer.Base.prototype.getLayerState = function() { var minResolution = this.getMinResolution(); return { layer: /** @type {ol.layer.Layer} */ (this), - brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0, - contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1, - hue: goog.isDef(hue) ? hue : 0, - opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1, - saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1, + brightness: goog.math.clamp(brightness, -1, 1), + contrast: Math.max(contrast, 0), + hue: hue, + opacity: goog.math.clamp(opacity, 0, 1), + saturation: Math.max(saturation, 0), sourceState: sourceState, - visible: goog.isDef(visible) ? !!visible : true, + visible: visible, + managed: true, extent: extent, - maxResolution: goog.isDef(maxResolution) ? maxResolution : Infinity, - minResolution: goog.isDef(minResolution) ? Math.max(minResolution, 0) : 0 + maxResolution: maxResolution, + minResolution: Math.max(minResolution, 0) }; }; @@ -48044,6 +49623,8 @@ ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod; /** + * Return the {@link ol.Extent extent} of the layer or `undefined` if it + * will be visible regardless of extent. * @return {ol.Extent|undefined} The layer extent. * @observable * @api stable @@ -48052,70 +49633,52 @@ ol.layer.Base.prototype.getExtent = function() { return /** @type {ol.Extent|undefined} */ ( this.get(ol.layer.LayerProperty.EXTENT)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getExtent', - ol.layer.Base.prototype.getExtent); /** - * @return {number|undefined} The maximum resolution of the layer. + * Return the maximum resolution of the layer. + * @return {number} The maximum resolution of the layer. * @observable * @api stable */ ol.layer.Base.prototype.getMaxResolution = function() { - return /** @type {number|undefined} */ ( + return /** @type {number} */ ( this.get(ol.layer.LayerProperty.MAX_RESOLUTION)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getMaxResolution', - ol.layer.Base.prototype.getMaxResolution); /** - * @return {number|undefined} The minimum resolution of the layer. + * Return the minimum resolution of the layer. + * @return {number} The minimum resolution of the layer. * @observable * @api stable */ ol.layer.Base.prototype.getMinResolution = function() { - return /** @type {number|undefined} */ ( + return /** @type {number} */ ( this.get(ol.layer.LayerProperty.MIN_RESOLUTION)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getMinResolution', - ol.layer.Base.prototype.getMinResolution); /** - * @return {number|undefined} The opacity of the layer. + * Return the opacity of the layer (between 0 and 1). + * @return {number} The opacity of the layer. * @observable * @api stable */ ol.layer.Base.prototype.getOpacity = function() { - return /** @type {number|undefined} */ ( - this.get(ol.layer.LayerProperty.OPACITY)); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getOpacity', - ol.layer.Base.prototype.getOpacity); /** - * @return {number|undefined} The saturation of the layer. + * Return the saturation of the layer. + * @return {number} The saturation of the layer. * @observable * @api */ ol.layer.Base.prototype.getSaturation = function() { - return /** @type {number|undefined} */ ( - this.get(ol.layer.LayerProperty.SATURATION)); + return /** @type {number} */ (this.get(ol.layer.LayerProperty.SATURATION)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getSaturation', - ol.layer.Base.prototype.getSaturation); /** @@ -48125,18 +49688,14 @@ ol.layer.Base.prototype.getSourceState = goog.abstractMethod; /** - * @return {boolean|undefined} The visiblity of the layer. + * Return the visibility of the layer (`true` or `false`). + * @return {boolean} The visibility of the layer. * @observable * @api stable */ ol.layer.Base.prototype.getVisible = function() { - return /** @type {boolean|undefined} */ ( - this.get(ol.layer.LayerProperty.VISIBLE)); + return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE)); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'getVisible', - ol.layer.Base.prototype.getVisible); /** @@ -48157,17 +49716,13 @@ goog.exportProperty( * [2] https://github.com/WebKit/webkit/commit/8f4765e569 * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647 * - * @param {number|undefined} brightness The brightness of the layer. + * @param {number} brightness The brightness of the layer. * @observable * @api */ ol.layer.Base.prototype.setBrightness = function(brightness) { this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setBrightness', - ol.layer.Base.prototype.setBrightness); /** @@ -48175,33 +49730,25 @@ goog.exportProperty( * grey. A value of 1 will leave the contrast unchanged. Other values are * linear multipliers on the effect (and values over 1 are permitted). * - * @param {number|undefined} contrast The contrast of the layer. + * @param {number} contrast The contrast of the layer. * @observable * @api */ ol.layer.Base.prototype.setContrast = function(contrast) { this.set(ol.layer.LayerProperty.CONTRAST, contrast); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setContrast', - ol.layer.Base.prototype.setContrast); /** * Apply a hue-rotation to the layer. A value of 0 will leave the hue * unchanged. Other values are radians around the color circle. - * @param {number|undefined} hue The hue of the layer. + * @param {number} hue The hue of the layer. * @observable * @api */ ol.layer.Base.prototype.setHue = function(hue) { this.set(ol.layer.LayerProperty.HUE, hue); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setHue', - ol.layer.Base.prototype.setHue); /** @@ -48214,52 +49761,39 @@ goog.exportProperty( ol.layer.Base.prototype.setExtent = function(extent) { this.set(ol.layer.LayerProperty.EXTENT, extent); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setExtent', - ol.layer.Base.prototype.setExtent); /** - * @param {number|undefined} maxResolution The maximum resolution of the layer. + * Set the maximum resolution at which the layer is visible. + * @param {number} maxResolution The maximum resolution of the layer. * @observable * @api stable */ ol.layer.Base.prototype.setMaxResolution = function(maxResolution) { this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setMaxResolution', - ol.layer.Base.prototype.setMaxResolution); /** - * @param {number|undefined} minResolution The minimum resolution of the layer. + * Set the minimum resolution at which the layer is visible. + * @param {number} minResolution The minimum resolution of the layer. * @observable * @api stable */ ol.layer.Base.prototype.setMinResolution = function(minResolution) { this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setMinResolution', - ol.layer.Base.prototype.setMinResolution); /** - * @param {number|undefined} opacity The opacity of the layer. + * Set the opacity of the layer, allowed values range from 0 to 1. + * @param {number} opacity The opacity of the layer. * @observable * @api stable */ ol.layer.Base.prototype.setOpacity = function(opacity) { this.set(ol.layer.LayerProperty.OPACITY, opacity); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setOpacity', - ol.layer.Base.prototype.setOpacity); /** @@ -48268,41 +49802,231 @@ goog.exportProperty( * values are linear multipliers of the effect (and values over 1 are * permitted). * - * @param {number|undefined} saturation The saturation of the layer. + * @param {number} saturation The saturation of the layer. * @observable * @api */ ol.layer.Base.prototype.setSaturation = function(saturation) { this.set(ol.layer.LayerProperty.SATURATION, saturation); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setSaturation', - ol.layer.Base.prototype.setSaturation); /** - * @param {boolean|undefined} visible The visiblity of the layer. + * Set the visibility of the layer (`true` or `false`). + * @param {boolean} visible The visibility of the layer. * @observable * @api stable */ ol.layer.Base.prototype.setVisible = function(visible) { this.set(ol.layer.LayerProperty.VISIBLE, visible); }; -goog.exportProperty( - ol.layer.Base.prototype, - 'setVisible', - ol.layer.Base.prototype.setVisible); + +goog.provide('ol.render.VectorContext'); + + + +/** + * @constructor + * @struct + * @api + */ +ol.render.VectorContext = function() { +}; + + +/** + * @param {number} zIndex Z index. + * @param {function(ol.render.VectorContext)} callback Callback. + */ +ol.render.VectorContext.prototype.drawAsync = goog.abstractMethod; + + +/** + * @param {ol.geom.Circle} circleGeometry Circle geometry. + * @param {ol.Feature} feature Feature, + */ +ol.render.VectorContext.prototype.drawCircleGeometry = goog.abstractMethod; + + +/** + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + */ +ol.render.VectorContext.prototype.drawFeature = goog.abstractMethod; + + +/** + * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry + * collection. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawGeometryCollectionGeometry = + goog.abstractMethod; + + +/** + * @param {ol.geom.LineString} lineStringGeometry Line string geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawLineStringGeometry = + goog.abstractMethod; + + +/** + * @param {ol.geom.MultiLineString} multiLineStringGeometry + * MultiLineString geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiLineStringGeometry = + goog.abstractMethod; + + +/** + * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiPointGeometry = goog.abstractMethod; + + +/** + * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawMultiPolygonGeometry = + goog.abstractMethod; + + +/** + * @param {ol.geom.Point} pointGeometry Point geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawPointGeometry = goog.abstractMethod; + + +/** + * @param {ol.geom.Polygon} polygonGeometry Polygon geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawPolygonGeometry = goog.abstractMethod; + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.Feature} feature Feature. + */ +ol.render.VectorContext.prototype.drawText = goog.abstractMethod; + + +/** + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. + */ +ol.render.VectorContext.prototype.setFillStrokeStyle = goog.abstractMethod; + + +/** + * @param {ol.style.Image} imageStyle Image style. + */ +ol.render.VectorContext.prototype.setImageStyle = goog.abstractMethod; + + +/** + * @param {ol.style.Text} textStyle Text style. + */ +ol.render.VectorContext.prototype.setTextStyle = goog.abstractMethod; + +goog.provide('ol.render.Event'); +goog.provide('ol.render.EventType'); + +goog.require('goog.events.Event'); +goog.require('ol.render.VectorContext'); + + +/** + * @enum {string} + */ +ol.render.EventType = { + /** + * @event ol.render.Event#postcompose + * @api + */ + POSTCOMPOSE: 'postcompose', + /** + * @event ol.render.Event#precompose + * @api + */ + PRECOMPOSE: 'precompose', + /** + * @event ol.render.Event#render + * @api + */ + RENDER: 'render' +}; + + + +/** + * @constructor + * @extends {goog.events.Event} + * @implements {oli.render.Event} + * @param {ol.render.EventType} type Type. + * @param {Object=} opt_target Target. + * @param {ol.render.VectorContext=} opt_vectorContext Vector context. + * @param {olx.FrameState=} opt_frameState Frame state. + * @param {?CanvasRenderingContext2D=} opt_context Context. + * @param {?ol.webgl.Context=} opt_glContext WebGL Context. + */ +ol.render.Event = function( + type, opt_target, opt_vectorContext, opt_frameState, opt_context, + opt_glContext) { + + goog.base(this, type, opt_target); + + /** + * For canvas, this is an instance of {@link ol.render.canvas.Immediate}. + * @type {ol.render.VectorContext|undefined} + * @api + */ + this.vectorContext = opt_vectorContext; + + /** + * @type {olx.FrameState|undefined} + * @api + */ + this.frameState = opt_frameState; + + /** + * Canvas context. Only available when a Canvas renderer is used, null + * otherwise. + * @type {CanvasRenderingContext2D|null|undefined} + * @api + */ + this.context = opt_context; + + /** + * WebGL context. Only available when a WebGL renderer is used, null + * otherwise. + * @type {ol.webgl.Context|null|undefined} + * @api + */ + this.glContext = opt_glContext; + +}; +goog.inherits(ol.render.Event, goog.events.Event); goog.provide('ol.layer.Layer'); -goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('ol.Object'); goog.require('ol.layer.Base'); goog.require('ol.layer.LayerProperty'); +goog.require('ol.render.EventType'); goog.require('ol.source.State'); @@ -48315,10 +50039,11 @@ goog.require('ol.source.State'); * Layers group together those properties that pertain to how the data is to be * displayed, irrespective of the source of that data. * + * A generic `change` event is fired when the state of the source changes. + * * @constructor * @extends {ol.layer.Base} * @fires ol.render.Event - * @fires change Triggered when the state of the source changes. * @param {olx.layer.LayerOptions} options Layer options. * @api stable */ @@ -48329,12 +50054,28 @@ ol.layer.Layer = function(options) { goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + /** + * @private + * @type {goog.events.Key} + */ + this.mapPrecomposeKey_ = null; + + /** + * @private + * @type {goog.events.Key} + */ + this.mapRenderKey_ = null; + /** * @private * @type {goog.events.Key} */ this.sourceChangeKey_ = null; + if (goog.isDef(options.map)) { + this.setMap(options.map); + } + goog.events.listen(this, ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE), this.handleSourcePropertyChange_, false, this); @@ -48390,10 +50131,6 @@ ol.layer.Layer.prototype.getSource = function() { return goog.isDef(source) ? /** @type {ol.source.Source} */ (source) : null; }; -goog.exportProperty( - ol.layer.Layer.prototype, - 'getSource', - ol.layer.Layer.prototype.getSource); /** @@ -48430,6 +50167,36 @@ ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { }; +/** + * Sets the layer to be rendered on a map. The map will not manage this layer in + * its layers collection, layer filters in {@link ol.Map#forEachLayerAtPixel} + * will not filter the layer, and it will be rendered on top. This is useful for + * temporary layers. To remove an unmanaged layer from the map, use + * `#setMap(null)`. + * + * To add the layer to a map and have it managed by the map, use + * {@link ol.Map#addLayer} instead. + * @param {ol.Map} map Map. + * @api + */ +ol.layer.Layer.prototype.setMap = function(map) { + goog.events.unlistenByKey(this.mapPrecomposeKey_); + this.changed(); + goog.events.unlistenByKey(this.mapRenderKey_); + if (!goog.isNull(map)) { + this.mapPrecomposeKey_ = goog.events.listen( + map, ol.render.EventType.PRECOMPOSE, function(evt) { + var layerState = this.getLayerState(); + layerState.managed = false; + evt.frameState.layerStatesArray.push(layerState); + evt.frameState.layerStates[goog.getUid(this)] = layerState; + }, false, this); + this.mapRenderKey_ = goog.events.listen( + this, goog.events.EventType.CHANGE, map.render, false, map); + } +}; + + /** * Set the layer source. * @param {ol.source.Source} source The layer source. @@ -48439,10 +50206,6 @@ ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() { ol.layer.Layer.prototype.setSource = function(source) { this.set(ol.layer.LayerProperty.SOURCE, source); }; -goog.exportProperty( - ol.layer.Layer.prototype, - 'setSource', - ol.layer.Layer.prototype.setSource); goog.provide('ol.ImageBase'); goog.provide('ol.ImageState'); @@ -48574,11003 +50337,10143 @@ ol.ImageBase.prototype.getState = function() { */ ol.ImageBase.prototype.load = goog.abstractMethod; -goog.provide('ol.Tile'); -goog.provide('ol.TileState'); - -goog.require('goog.events'); -goog.require('goog.events.EventTarget'); -goog.require('goog.events.EventType'); -goog.require('ol.TileCoord'); - - -/** - * @enum {number} - */ -ol.TileState = { - IDLE: 0, - LOADING: 1, - LOADED: 2, - ERROR: 3, - EMPTY: 4 -}; - - - -/** - * @classdesc - * Base class for tiles. - * - * @constructor - * @extends {goog.events.EventTarget} - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.TileState} state State. - */ -ol.Tile = function(tileCoord, state) { - - goog.base(this); - - /** - * @type {ol.TileCoord} - */ - this.tileCoord = tileCoord; - - /** - * @protected - * @type {ol.TileState} - */ - this.state = state; - -}; -goog.inherits(ol.Tile, goog.events.EventTarget); - +goog.provide('ol.vec.Mat4'); +goog.provide('ol.vec.Mat4.Number'); -/** - * @protected - */ -ol.Tile.prototype.changed = function() { - this.dispatchEvent(goog.events.EventType.CHANGE); -}; +goog.require('goog.vec.Mat4'); /** - * @function - * @param {Object=} opt_context Object. - * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. + * A alias for the goog.vec.Number type. + * @typedef {goog.vec.Number} */ -ol.Tile.prototype.getImage = goog.abstractMethod; +ol.vec.Mat4.Number; /** - * @return {string} Key. + * @param {!goog.vec.Mat4.Number} mat Matrix. + * @param {number} translateX1 Translate X1. + * @param {number} translateY1 Translate Y1. + * @param {number} scaleX Scale X. + * @param {number} scaleY Scale Y. + * @param {number} rotation Rotation. + * @param {number} translateX2 Translate X2. + * @param {number} translateY2 Translate Y2. + * @return {!goog.vec.Mat4.Number} Matrix. */ -ol.Tile.prototype.getKey = function() { - return goog.getUid(this).toString(); +ol.vec.Mat4.makeTransform2D = function(mat, translateX1, translateY1, + scaleX, scaleY, rotation, translateX2, translateY2) { + goog.vec.Mat4.makeIdentity(mat); + if (translateX1 !== 0 || translateY1 !== 0) { + goog.vec.Mat4.translate(mat, translateX1, translateY1, 0); + } + if (scaleX != 1 || scaleY != 1) { + goog.vec.Mat4.scale(mat, scaleX, scaleY, 1); + } + if (rotation !== 0) { + goog.vec.Mat4.rotateZ(mat, rotation); + } + if (translateX2 !== 0 || translateY2 !== 0) { + goog.vec.Mat4.translate(mat, translateX2, translateY2, 0); + } + return mat; }; /** - * @return {ol.TileCoord} - * @api + * Returns true if mat1 and mat2 represent the same 2D transformation. + * @param {goog.vec.Mat4.Number} mat1 Matrix 1. + * @param {goog.vec.Mat4.Number} mat2 Matrix 2. + * @return {boolean} Equal 2D. */ -ol.Tile.prototype.getTileCoord = function() { - return this.tileCoord; +ol.vec.Mat4.equals2D = function(mat1, mat2) { + return ( + goog.vec.Mat4.getElement(mat1, 0, 0) == + goog.vec.Mat4.getElement(mat2, 0, 0) && + goog.vec.Mat4.getElement(mat1, 1, 0) == + goog.vec.Mat4.getElement(mat2, 1, 0) && + goog.vec.Mat4.getElement(mat1, 0, 1) == + goog.vec.Mat4.getElement(mat2, 0, 1) && + goog.vec.Mat4.getElement(mat1, 1, 1) == + goog.vec.Mat4.getElement(mat2, 1, 1) && + goog.vec.Mat4.getElement(mat1, 0, 3) == + goog.vec.Mat4.getElement(mat2, 0, 3) && + goog.vec.Mat4.getElement(mat1, 1, 3) == + goog.vec.Mat4.getElement(mat2, 1, 3)); }; /** - * @return {ol.TileState} State. + * Transforms the given vector with the given matrix storing the resulting, + * transformed vector into resultVec. The input vector is multiplied against the + * upper 2x4 matrix omitting the projective component. + * + * @param {goog.vec.Mat4.Number} mat The matrix supplying the transformation. + * @param {Array.<number>} vec The 3 element vector to transform. + * @param {Array.<number>} resultVec The 3 element vector to receive the results + * (may be vec). + * @return {Array.<number>} return resultVec so that operations can be + * chained together. */ -ol.Tile.prototype.getState = function() { - return this.state; +ol.vec.Mat4.multVec2 = function(mat, vec, resultVec) { + var m00 = goog.vec.Mat4.getElement(mat, 0, 0); + var m10 = goog.vec.Mat4.getElement(mat, 1, 0); + var m01 = goog.vec.Mat4.getElement(mat, 0, 1); + var m11 = goog.vec.Mat4.getElement(mat, 1, 1); + var m03 = goog.vec.Mat4.getElement(mat, 0, 3); + var m13 = goog.vec.Mat4.getElement(mat, 1, 3); + var x = vec[0], y = vec[1]; + resultVec[0] = m00 * x + m01 * y + m03; + resultVec[1] = m10 * x + m11 * y + m13; + return resultVec; }; +goog.provide('ol.renderer.Layer'); -/** - * FIXME empty description for jsdoc - */ -ol.Tile.prototype.load = goog.abstractMethod; - -goog.provide('ol.tilegrid.TileGrid'); - -goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('goog.functions'); -goog.require('ol'); -goog.require('ol.Coordinate'); -goog.require('ol.TileCoord'); +goog.require('ol.ImageState'); +goog.require('ol.Observable'); goog.require('ol.TileRange'); -goog.require('ol.array'); -goog.require('ol.extent'); -goog.require('ol.extent.Corner'); -goog.require('ol.proj'); -goog.require('ol.proj.METERS_PER_UNIT'); -goog.require('ol.proj.Projection'); -goog.require('ol.proj.Units'); +goog.require('ol.TileState'); +goog.require('ol.layer.Layer'); +goog.require('ol.source.Source'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); goog.require('ol.tilecoord'); +goog.require('ol.vec.Mat4'); /** - * @classdesc - * Base class for setting the grid pattern for sources accessing tiled-image - * servers. - * * @constructor - * @param {olx.tilegrid.TileGridOptions} options Tile grid options. + * @extends {ol.Observable} + * @param {ol.layer.Layer} layer Layer. + * @suppress {checkStructDictInheritance} * @struct - * @api stable */ -ol.tilegrid.TileGrid = function(options) { +ol.renderer.Layer = function(layer) { - /** - * @protected - * @type {number} - */ - this.minZoom = goog.isDef(options.minZoom) ? options.minZoom : 0; + goog.base(this); /** * @private - * @type {!Array.<number>} + * @type {ol.layer.Layer} */ - this.resolutions_ = options.resolutions; - goog.asserts.assert(goog.array.isSorted(this.resolutions_, function(a, b) { - return b - a; - }, true)); + this.layer_ = layer; - /** - * @protected - * @type {number} - */ - this.maxZoom = this.resolutions_.length - 1; - /** - * @private - * @type {ol.Coordinate} - */ - this.origin_ = goog.isDef(options.origin) ? options.origin : null; +}; +goog.inherits(ol.renderer.Layer, ol.Observable); - /** - * @private - * @type {Array.<ol.Coordinate>} - */ - this.origins_ = null; - if (goog.isDef(options.origins)) { - this.origins_ = options.origins; - goog.asserts.assert(this.origins_.length == this.maxZoom + 1); - } - goog.asserts.assert( - (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) || - (!goog.isNull(this.origin_) && goog.isNull(this.origins_))); - /** - * @private - * @type {Array.<number>} - */ - this.tileSizes_ = null; - if (goog.isDef(options.tileSizes)) { - this.tileSizes_ = options.tileSizes; - goog.asserts.assert(this.tileSizes_.length == this.maxZoom + 1); - } +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T + */ +ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = goog.nullFunction; - /** - * @private - * @type {number|undefined} - */ - this.tileSize_ = goog.isDef(options.tileSize) ? - options.tileSize : - goog.isNull(this.tileSizes_) ? ol.DEFAULT_TILE_SIZE : undefined; - goog.asserts.assert( - (!goog.isDef(this.tileSize_) && !goog.isNull(this.tileSizes_)) || - (goog.isDef(this.tileSize_) && goog.isNull(this.tileSizes_))); +/** + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState Frame state. + * @param {function(this: S, ol.layer.Layer): T} callback Layer callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T + */ +ol.renderer.Layer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + var coordinate = pixel.slice(); + ol.vec.Mat4.multVec2( + frameState.pixelToCoordinateMatrix, coordinate, coordinate); + + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.layer_); + } else { + return undefined; + } }; /** - * @private - * @type {ol.TileCoord} + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState Frame state. + * @return {boolean} Is there a feature at the given coordinate? */ -ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0]; +ol.renderer.Layer.prototype.hasFeatureAtCoordinate = goog.functions.FALSE; /** - * Returns the identity function. May be overridden in subclasses. - * @param {{extent: (ol.Extent|undefined), - * wrapX: (boolean|undefined)}=} opt_options Options. - * @return {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=): - * ol.TileCoord} Tile coordinate transform. + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected */ -ol.tilegrid.TileGrid.prototype.createTileCoordTransform = - function(opt_options) { - return goog.functions.identity; +ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, tiles) { + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + return source.forEachLoadedTile(zoom, tileRange, function(tile) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + }); + }); }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {function(this: T, number, ol.TileRange): boolean} callback Callback. - * @param {T=} opt_this The object to use as `this` in `callback`. - * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. - * @param {ol.Extent=} opt_extent Temporary ol.Extent object. - * @return {boolean} Callback succeeded. - * @template T + * @return {ol.layer.Layer} Layer. */ -ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = - function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) { - var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); - var z = tileCoord[0] - 1; - while (z >= this.minZoom) { - if (callback.call(opt_this, z, - this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) { - return true; - } - --z; - } - return false; +ol.renderer.Layer.prototype.getLayer = function() { + return this.layer_; }; /** - * @return {number} Max zoom. - * @api + * Handle changes in image state. + * @param {goog.events.Event} event Image change event. + * @private */ -ol.tilegrid.TileGrid.prototype.getMaxZoom = function() { - return this.maxZoom; +ol.renderer.Layer.prototype.handleImageChange_ = function(event) { + var image = /** @type {ol.Image} */ (event.target); + if (image.getState() === ol.ImageState.LOADED) { + this.renderIfReadyAndVisible(); + } }; /** - * @return {number} Min zoom. - * @api + * Load the image if not already loaded, and register the image change + * listener if needed. + * @param {ol.ImageBase} image Image. + * @return {boolean} `true` if the image is already loaded, `false` + * otherwise. + * @protected */ -ol.tilegrid.TileGrid.prototype.getMinZoom = function() { - return this.minZoom; +ol.renderer.Layer.prototype.loadImage = function(image) { + var imageState = image.getState(); + if (imageState != ol.ImageState.LOADED && + imageState != ol.ImageState.ERROR) { + // the image is either "idle" or "loading", register the change + // listener (a noop if the listener was already registered) + goog.asserts.assert(imageState == ol.ImageState.IDLE || + imageState == ol.ImageState.LOADING, + 'imageState is "idle" or "loading"'); + goog.events.listen(image, goog.events.EventType.CHANGE, + this.handleImageChange_, false, this); + } + if (imageState == ol.ImageState.IDLE) { + image.load(); + imageState = image.getState(); + goog.asserts.assert(imageState == ol.ImageState.LOADING, + 'imageState is "loading"'); + } + return imageState == ol.ImageState.LOADED; }; /** - * @param {number} z Z. - * @return {ol.Coordinate} Origin. - * @api stable + * @protected */ -ol.tilegrid.TileGrid.prototype.getOrigin = function(z) { - if (!goog.isNull(this.origin_)) { - return this.origin_; - } else { - goog.asserts.assert(!goog.isNull(this.origins_)); - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom); - return this.origins_[z]; +ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() { + var layer = this.getLayer(); + if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) { + this.changed(); } }; /** - * @param {number} z Z. - * @return {number} Resolution. - * @api stable + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @protected */ -ol.tilegrid.TileGrid.prototype.getResolution = function(z) { - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom); - return this.resolutions_[z]; +ol.renderer.Layer.prototype.scheduleExpireCache = + function(frameState, tileSource) { + if (tileSource.canExpireCache()) { + frameState.postRenderFunctions.push( + goog.partial( + /** + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + */ + function(tileSource, map, frameState) { + var tileSourceKey = goog.getUid(tileSource).toString(); + tileSource.expireCache(frameState.usedTiles[tileSourceKey]); + }, tileSource)); + } }; /** - * @return {Array.<number>} Resolutions. - * @api stable + * @param {Object.<string, ol.Attribution>} attributionsSet Attributions + * set (target). + * @param {Array.<ol.Attribution>} attributions Attributions (source). + * @protected */ -ol.tilegrid.TileGrid.prototype.getResolutions = function() { - return this.resolutions_; +ol.renderer.Layer.prototype.updateAttributions = + function(attributionsSet, attributions) { + if (goog.isDefAndNotNull(attributions)) { + var attribution, i, ii; + for (i = 0, ii = attributions.length; i < ii; ++i) { + attribution = attributions[i]; + attributionsSet[goog.getUid(attribution).toString()] = attribution; + } + } }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. - * @param {ol.Extent=} opt_extent Temporary ol.Extent object. - * @return {ol.TileRange} Tile range. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Source} source Source. + * @protected */ -ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = - function(tileCoord, opt_tileRange, opt_extent) { - if (tileCoord[0] < this.maxZoom) { - var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent); - return this.getTileRangeForExtentAndZ( - tileCoordExtent, tileCoord[0] + 1, opt_tileRange); - } else { - return null; +ol.renderer.Layer.prototype.updateLogos = function(frameState, source) { + var logo = source.getLogo(); + if (goog.isDef(logo)) { + if (goog.isString(logo)) { + frameState.logos[logo] = ''; + } else if (goog.isObject(logo)) { + goog.asserts.assertString(logo.href, 'logo.href is a string'); + goog.asserts.assertString(logo.src, 'logo.src is a string'); + frameState.logos[logo.src] = logo.href; + } } }; /** + * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles. + * @param {ol.source.Tile} tileSource Tile source. * @param {number} z Z. * @param {ol.TileRange} tileRange Tile range. - * @param {ol.Extent=} opt_extent Temporary ol.Extent object. - * @return {ol.Extent} Extent. + * @protected */ -ol.tilegrid.TileGrid.prototype.getTileRangeExtent = - function(z, tileRange, opt_extent) { - var origin = this.getOrigin(z); - var resolution = this.getResolution(z); - var tileSize = this.getTileSize(z); - var minX = origin[0] + tileRange.minX * tileSize * resolution; - var maxX = origin[0] + (tileRange.maxX + 1) * tileSize * resolution; - var minY = origin[1] + tileRange.minY * tileSize * resolution; - var maxY = origin[1] + (tileRange.maxY + 1) * tileSize * resolution; - return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +ol.renderer.Layer.prototype.updateUsedTiles = + function(usedTiles, tileSource, z, tileRange) { + // FIXME should we use tilesToDrawByZ instead? + var tileSourceKey = goog.getUid(tileSource).toString(); + var zKey = z.toString(); + if (tileSourceKey in usedTiles) { + if (zKey in usedTiles[tileSourceKey]) { + usedTiles[tileSourceKey][zKey].extend(tileRange); + } else { + usedTiles[tileSourceKey][zKey] = tileRange; + } + } else { + usedTiles[tileSourceKey] = {}; + usedTiles[tileSourceKey][zKey] = tileRange; + } }; /** - * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} center Center. * @param {number} resolution Resolution. - * @param {ol.TileRange=} opt_tileRange Temporary tile range object. - * @return {ol.TileRange} Tile range. + * @param {ol.Size} size Size. + * @protected + * @return {ol.Coordinate} Snapped center. */ -ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = - function(extent, resolution, opt_tileRange) { - var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_; - this.getTileCoordForXYAndResolution_( - extent[0], extent[1], resolution, false, tileCoord); - var minX = tileCoord[1]; - var minY = tileCoord[2]; - this.getTileCoordForXYAndResolution_( - extent[2], extent[3], resolution, true, tileCoord); - return ol.TileRange.createOrUpdate( - minX, tileCoord[1], minY, tileCoord[2], opt_tileRange); +ol.renderer.Layer.prototype.snapCenterToPixel = + function(center, resolution, size) { + return [ + resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2), + resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2) + ]; }; /** + * Manage tile pyramid. + * This function performs a number of functions related to the tiles at the + * current zoom and lower zoom levels: + * - registers idle tiles in frameState.wantedTiles so that they are not + * discarded by the tile queue + * - enqueues missing tiles + * @param {olx.FrameState} frameState Frame state. + * @param {ol.source.Tile} tileSource Tile source. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. * @param {ol.Extent} extent Extent. - * @param {number} z Z. - * @param {ol.TileRange=} opt_tileRange Temporary tile range object. - * @return {ol.TileRange} Tile range. + * @param {number} currentZ Current Z. + * @param {number} preload Load low resolution tiles up to 'preload' levels. + * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback. + * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`. + * @protected + * @template T */ -ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = - function(extent, z, opt_tileRange) { - var resolution = this.getResolution(z); - return this.getTileRangeForExtentAndResolution( - extent, resolution, opt_tileRange); +ol.renderer.Layer.prototype.manageTilePyramid = function( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, + currentZ, preload, opt_tileCallback, opt_this) { + var tileSourceKey = goog.getUid(tileSource).toString(); + if (!(tileSourceKey in frameState.wantedTiles)) { + frameState.wantedTiles[tileSourceKey] = {}; + } + var wantedTiles = frameState.wantedTiles[tileSourceKey]; + var tileQueue = frameState.tileQueue; + var minZoom = tileGrid.getMinZoom(); + var tile, tileRange, tileResolution, x, y, z; + for (z = currentZ; z >= minZoom; --z) { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); + tileResolution = tileGrid.getResolution(z); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + if (currentZ - z <= preload) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (tile.getState() == ol.TileState.IDLE) { + wantedTiles[ol.tilecoord.toString(tile.tileCoord)] = true; + if (!tileQueue.isKeyQueued(tile.getKey())) { + tileQueue.enqueue([tile, tileSourceKey, + tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); + } + } + if (goog.isDef(opt_tileCallback)) { + opt_tileCallback.call(opt_this, tile); + } + } else { + tileSource.useTile(z, x, y); + } + } + } + } }; - -/** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {ol.Coordinate} Tile center. - */ -ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) { - var origin = this.getOrigin(tileCoord[0]); - var resolution = this.getResolution(tileCoord[0]); - var tileSize = this.getTileSize(tileCoord[0]); - return [ - origin[0] + (tileCoord[1] + 0.5) * tileSize * resolution, - origin[1] + (tileCoord[2] + 0.5) * tileSize * resolution - ]; -}; +goog.provide('ol.style.Image'); +goog.provide('ol.style.ImageState'); /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.Extent=} opt_extent Temporary extent object. - * @return {ol.Extent} Extent. + * @enum {number} */ -ol.tilegrid.TileGrid.prototype.getTileCoordExtent = - function(tileCoord, opt_extent) { - var origin = this.getOrigin(tileCoord[0]); - var resolution = this.getResolution(tileCoord[0]); - var tileSize = this.getTileSize(tileCoord[0]); - var minX = origin[0] + tileCoord[1] * tileSize * resolution; - var minY = origin[1] + tileCoord[2] * tileSize * resolution; - var maxX = minX + tileSize * resolution; - var maxY = minY + tileSize * resolution; - return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); +ol.style.ImageState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 }; /** - * Get the tile coordinate for the given map coordinate and resolution. This - * method considers that coordinates that intersect tile boundaries should be - * assigned the higher tile coordinate. - * - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} resolution Resolution. - * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. - * @return {ol.TileCoord} Tile coordinate. - * @api + * @typedef {{opacity: number, + * rotateWithView: boolean, + * rotation: number, + * scale: number, + * snapToPixel: boolean}} */ -ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function( - coordinate, resolution, opt_tileCoord) { - return this.getTileCoordForXYAndResolution_( - coordinate[0], coordinate[1], resolution, false, opt_tileCoord); -}; +ol.style.ImageOptions; + /** - * @param {number} x X. - * @param {number} y Y. - * @param {number} resolution Resolution. - * @param {boolean} reverseIntersectionPolicy Instead of letting edge - * intersections go to the higher tile coordinate, let edge intersections - * go to the lower tile coordinate. - * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object. - * @return {ol.TileCoord} Tile coordinate. - * @private + * @classdesc + * A base class used for creating subclasses and not instantiated in + * apps. Base class for {@link ol.style.Icon} and {@link ol.style.Circle}. + * + * @constructor + * @param {ol.style.ImageOptions} options Options. + * @api */ -ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function( - x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) { - var z = this.getZForResolution(resolution); - var scale = resolution / this.getResolution(z); - var origin = this.getOrigin(z); - var tileSize = this.getTileSize(z); +ol.style.Image = function(options) { - var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize); - var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize); + /** + * @private + * @type {number} + */ + this.opacity_ = options.opacity; - if (reverseIntersectionPolicy) { - tileCoordX = Math.ceil(tileCoordX) - 1; - tileCoordY = Math.ceil(tileCoordY) - 1; - } else { - tileCoordX = Math.floor(tileCoordX); - tileCoordY = Math.floor(tileCoordY); - } + /** + * @private + * @type {boolean} + */ + this.rotateWithView_ = options.rotateWithView; + + /** + * @private + * @type {number} + */ + this.rotation_ = options.rotation; + + /** + * @private + * @type {number} + */ + this.scale_ = options.scale; + + /** + * @private + * @type {boolean} + */ + this.snapToPixel_ = options.snapToPixel; - return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord); }; /** - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} z Z. - * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object. - * @return {ol.TileCoord} Tile coordinate. + * Get the symbolizer opacity. + * @return {number} Opacity. * @api */ -ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = - function(coordinate, z, opt_tileCoord) { - var resolution = this.getResolution(z); - return this.getTileCoordForXYAndResolution_( - coordinate[0], coordinate[1], resolution, false, opt_tileCoord); +ol.style.Image.prototype.getOpacity = function() { + return this.opacity_; }; /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {number} Tile resolution. + * Determine whether the symbolizer rotates with the map. + * @return {boolean} Rotate with map. + * @api */ -ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) { - goog.asserts.assert( - this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom); - return this.resolutions_[tileCoord[0]]; +ol.style.Image.prototype.getRotateWithView = function() { + return this.rotateWithView_; }; /** - * @param {number} z Z. - * @return {number} Tile size. - * @api stable + * Get the symoblizer rotation. + * @return {number} Rotation. + * @api */ -ol.tilegrid.TileGrid.prototype.getTileSize = function(z) { - if (goog.isDef(this.tileSize_)) { - return this.tileSize_; - } else { - goog.asserts.assert(!goog.isNull(this.tileSizes_)); - goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom); - return this.tileSizes_[z]; - } +ol.style.Image.prototype.getRotation = function() { + return this.rotation_; }; /** - * @param {number} resolution Resolution. - * @return {number} Z. + * Get the symbolizer scale. + * @return {number} Scale. + * @api */ -ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) { - return ol.array.linearFindNearest(this.resolutions_, resolution, 0); +ol.style.Image.prototype.getScale = function() { + return this.scale_; }; /** - * @param {ol.proj.Projection} projection Projection. - * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection. + * Determine whether the symbolizer should be snapped to a pixel. + * @return {boolean} The symbolizer should snap to a pixel. + * @api */ -ol.tilegrid.getForProjection = function(projection) { - var tileGrid = projection.getDefaultTileGrid(); - if (goog.isNull(tileGrid)) { - tileGrid = ol.tilegrid.createForProjection(projection); - projection.setDefaultTileGrid(tileGrid); - } - return tileGrid; +ol.style.Image.prototype.getSnapToPixel = function() { + return this.snapToPixel_; }; /** - * @param {ol.Extent} extent Extent. - * @param {number=} opt_maxZoom Maximum zoom level (default is - * ol.DEFAULT_MAX_ZOOM). - * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). - * @param {ol.extent.Corner=} opt_corner Extent corner (default is - * ol.extent.Corner.BOTTOM_LEFT). - * @return {ol.tilegrid.TileGrid} TileGrid instance. + * @function + * @return {Array.<number>} Anchor. */ -ol.tilegrid.createForExtent = - function(extent, opt_maxZoom, opt_tileSize, opt_corner) { - var tileSize = goog.isDef(opt_tileSize) ? - opt_tileSize : ol.DEFAULT_TILE_SIZE; - - var corner = goog.isDef(opt_corner) ? - opt_corner : ol.extent.Corner.BOTTOM_LEFT; - - var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, opt_maxZoom, tileSize); - - return new ol.tilegrid.TileGrid({ - origin: ol.extent.getCorner(extent, corner), - resolutions: resolutions, - tileSize: tileSize - }); -}; +ol.style.Image.prototype.getAnchor = goog.abstractMethod; /** - * Create a resolutions array from an extent. A zoom factor of 2 is assumed. - * @param {ol.Extent} extent Extent. - * @param {number=} opt_maxZoom Maximum zoom level (default is - * ol.DEFAULT_MAX_ZOOM). - * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). - * @return {!Array.<number>} Resolutions array. + * @function + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. */ -ol.tilegrid.resolutionsFromExtent = - function(extent, opt_maxZoom, opt_tileSize) { - var maxZoom = goog.isDef(opt_maxZoom) ? - opt_maxZoom : ol.DEFAULT_MAX_ZOOM; - - var height = ol.extent.getHeight(extent); - var width = ol.extent.getWidth(extent); - - var tileSize = goog.isDef(opt_tileSize) ? - opt_tileSize : ol.DEFAULT_TILE_SIZE; - var maxResolution = Math.max( - width / tileSize, height / tileSize); - - var length = maxZoom + 1; - var resolutions = new Array(length); - for (var z = 0; z < length; ++z) { - resolutions[z] = maxResolution / Math.pow(2, z); - } - return resolutions; -}; +ol.style.Image.prototype.getImage = goog.abstractMethod; /** - * @param {ol.proj.ProjectionLike} projection Projection. - * @param {number=} opt_maxZoom Maximum zoom level (default is - * ol.DEFAULT_MAX_ZOOM). - * @param {number=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). - * @param {ol.extent.Corner=} opt_corner Extent corner (default is - * ol.extent.Corner.BOTTOM_LEFT). - * @return {ol.tilegrid.TileGrid} TileGrid instance. + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. */ -ol.tilegrid.createForProjection = - function(projection, opt_maxZoom, opt_tileSize, opt_corner) { - var extent = ol.tilegrid.extentFromProjection(projection); - return ol.tilegrid.createForExtent( - extent, opt_maxZoom, opt_tileSize, opt_corner); -}; +ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod; /** - * Generate a tile grid extent from a projection. If the projection has an - * extent, it is used. If not, a global extent is assumed. - * @param {ol.proj.ProjectionLike} projection Projection. - * @return {ol.Extent} Extent. + * @return {ol.style.ImageState} Image state. */ -ol.tilegrid.extentFromProjection = function(projection) { - projection = ol.proj.get(projection); - var extent = projection.getExtent(); - if (goog.isNull(extent)) { - var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] / - projection.getMetersPerUnit(); - extent = ol.extent.createOrUpdate(-half, -half, half, half); - } - return extent; -}; - -goog.provide('ol.source.Tile'); -goog.provide('ol.source.TileOptions'); - -goog.require('goog.functions'); -goog.require('ol.Attribution'); -goog.require('ol.Extent'); -goog.require('ol.TileRange'); -goog.require('ol.source.Source'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); +ol.style.Image.prototype.getImageState = goog.abstractMethod; /** - * @typedef {{attributions: (Array.<ol.Attribution>|undefined), - * extent: (ol.Extent|undefined), - * logo: (string|olx.LogoOptions|undefined), - * opaque: (boolean|undefined), - * tilePixelRatio: (number|undefined), - * projection: ol.proj.ProjectionLike, - * state: (ol.source.State|undefined), - * tileGrid: (ol.tilegrid.TileGrid|undefined)}} + * @return {ol.Size} Image size. */ -ol.source.TileOptions; - +ol.style.Image.prototype.getImageSize = goog.abstractMethod; /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for sources providing images divided into a tile grid. - * - * @constructor - * @extends {ol.source.Source} - * @param {ol.source.TileOptions} options Tile source options. - * @api + * @return {ol.Size} Size of the hit-detection image. */ -ol.source.Tile = function(options) { - - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - logo: options.logo, - projection: options.projection, - state: options.state - }); - - /** - * @private - * @type {boolean} - */ - this.opaque_ = goog.isDef(options.opaque) ? options.opaque : false; - - /** - * @private - * @type {number} - */ - this.tilePixelRatio_ = goog.isDef(options.tilePixelRatio) ? - options.tilePixelRatio : 1; - - /** - * @protected - * @type {ol.tilegrid.TileGrid} - */ - this.tileGrid = goog.isDef(options.tileGrid) ? options.tileGrid : null; - -}; -goog.inherits(ol.source.Tile, ol.source.Source); +ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod; /** - * @return {boolean} Can expire cache. + * @function + * @return {Array.<number>} Origin. */ -ol.source.Tile.prototype.canExpireCache = goog.functions.FALSE; +ol.style.Image.prototype.getOrigin = goog.abstractMethod; /** - * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. + * @function + * @return {ol.Size} Size. */ -ol.source.Tile.prototype.expireCache = goog.abstractMethod; +ol.style.Image.prototype.getSize = goog.abstractMethod; /** - * Look for loaded tiles over a given tile range and zoom level. Adds - * properties to the provided lookup representing key/tile pairs for already - * loaded tiles. + * Set the opacity. * - * @param {Object.<number, Object.<string, ol.Tile>>} loadedTilesByZ A lookup of - * loaded tiles by zoom level. - * @param {function(number, number, number): ol.Tile} getTileIfLoaded A function - * that returns the tile only if it is fully loaded. - * @param {number} z Zoom level. - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} The tile range is fully covered with loaded tiles. + * @param {number} opacity Opacity. */ -ol.source.Tile.prototype.findLoadedTiles = function(loadedTilesByZ, - getTileIfLoaded, z, tileRange) { - // FIXME this could be more efficient about filling partial holes - var fullyCovered = true; - var tile, tileCoordKey, x, y; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - tileCoordKey = this.getKeyZXY(z, x, y); - if (loadedTilesByZ[z] && loadedTilesByZ[z][tileCoordKey]) { - continue; - } - tile = getTileIfLoaded(z, x, y); - if (!goog.isNull(tile)) { - if (!loadedTilesByZ[z]) { - loadedTilesByZ[z] = {}; - } - loadedTilesByZ[z][tileCoordKey] = tile; - } else { - fullyCovered = false; - } - } - } - return fullyCovered; +ol.style.Image.prototype.setOpacity = function(opacity) { + this.opacity_ = opacity; }; /** - * @return {number} Gutter. + * Set whether to rotate the style with the view. + * + * @param {boolean} rotateWithView Rotate with map. */ -ol.source.Tile.prototype.getGutter = function() { - return 0; +ol.style.Image.prototype.setRotateWithView = function(rotateWithView) { + this.rotateWithView_ = rotateWithView; }; /** - * @param {number} z Z. - * @param {number} x X. - * @param {number} y Y. - * @return {string} Key. - * @protected + * Set the rotation. + * + * @param {number} rotation Rotation. + * @api */ -ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY; +ol.style.Image.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; +}; /** - * @return {boolean} Opaque. + * Set the scale. + * + * @param {number} scale Scale. + * @api */ -ol.source.Tile.prototype.getOpaque = function() { - return this.opaque_; +ol.style.Image.prototype.setScale = function(scale) { + this.scale_ = scale; }; /** - * @inheritDoc + * Set whether to snap the image to the closest pixel. + * + * @param {boolean} snapToPixel Snap to pixel? */ -ol.source.Tile.prototype.getResolutions = function() { - return this.tileGrid.getResolutions(); +ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) { + this.snapToPixel_ = snapToPixel; }; /** - * @param {number} z Tile coordinate z. - * @param {number} x Tile coordinate x. - * @param {number} y Tile coordinate y. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection=} opt_projection Projection. - * @return {!ol.Tile} Tile. + * @param {function(this: T, goog.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {goog.events.Key|undefined} Listener key. + * @template T */ -ol.source.Tile.prototype.getTile = goog.abstractMethod; +ol.style.Image.prototype.listenImageChange = goog.abstractMethod; /** - * @return {ol.tilegrid.TileGrid} Tile grid. - * @api stable + * Load not yet loaded URI. */ -ol.source.Tile.prototype.getTileGrid = function() { - return this.tileGrid; -}; +ol.style.Image.prototype.load = goog.abstractMethod; /** - * @param {ol.proj.Projection} projection Projection. - * @return {ol.tilegrid.TileGrid} Tile grid. + * @param {function(this: T, goog.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @template T */ -ol.source.Tile.prototype.getTileGridForProjection = function(projection) { - if (goog.isNull(this.tileGrid)) { - return ol.tilegrid.getForProjection(projection); - } else { - return this.tileGrid; - } -}; +ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod; + +goog.provide('ol.style.Icon'); +goog.provide('ol.style.IconAnchorUnits'); +goog.provide('ol.style.IconImageCache'); +goog.provide('ol.style.IconOrigin'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('ol.dom'); +goog.require('ol.style.Image'); +goog.require('ol.style.ImageState'); /** - * @param {number} z Z. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {number} Tile size. + * Icon anchor units. One of 'fraction', 'pixels'. + * @enum {string} + * @api */ -ol.source.Tile.prototype.getTilePixelSize = - function(z, pixelRatio, projection) { - var tileGrid = this.getTileGridForProjection(projection); - return tileGrid.getTileSize(z) * this.tilePixelRatio_; +ol.style.IconAnchorUnits = { + FRACTION: 'fraction', + PIXELS: 'pixels' }; /** - * Marks a tile coord as being used, without triggering a load. - * @param {number} z Tile coordinate z. - * @param {number} x Tile coordinate x. - * @param {number} y Tile coordinate y. + * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'. + * @enum {string} + * @api */ -ol.source.Tile.prototype.useTile = goog.nullFunction; - -goog.provide('ol.renderer.Layer'); - -goog.require('goog.Disposable'); -goog.require('goog.asserts'); -goog.require('ol.ImageState'); -goog.require('ol.TileRange'); -goog.require('ol.TileState'); -goog.require('ol.layer.Layer'); -goog.require('ol.source.Source'); -goog.require('ol.source.State'); -goog.require('ol.source.Tile'); -goog.require('ol.tilecoord'); +ol.style.IconOrigin = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_RIGHT: 'bottom-right', + TOP_LEFT: 'top-left', + TOP_RIGHT: 'top-right' +}; /** + * @classdesc + * Set icon style for vector features. + * * @constructor - * @extends {goog.Disposable} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Layer} layer Layer. - * @suppress {checkStructDictInheritance} - * @struct + * @param {olx.style.IconOptions=} opt_options Options. + * @extends {ol.style.Image} + * @api */ -ol.renderer.Layer = function(mapRenderer, layer) { +ol.style.Icon = function(opt_options) { - goog.base(this); + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {ol.renderer.Map} + * @type {Array.<number>} */ - this.mapRenderer_ = mapRenderer; + this.anchor_ = goog.isDef(options.anchor) ? options.anchor : [0.5, 0.5]; /** * @private - * @type {ol.layer.Layer} + * @type {Array.<number>} */ - this.layer_ = layer; - + this.normalizedAnchor_ = null; -}; -goog.inherits(ol.renderer.Layer, goog.Disposable); + /** + * @private + * @type {ol.style.IconOrigin} + */ + this.anchorOrigin_ = goog.isDef(options.anchorOrigin) ? + options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT; + /** + * @private + * @type {ol.style.IconAnchorUnits} + */ + this.anchorXUnits_ = goog.isDef(options.anchorXUnits) ? + options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION; -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @param {olx.FrameState} frameState Frame state. - * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature - * callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. - * @return {T|undefined} Callback result. - * @template S,T - */ -ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction; + /** + * @private + * @type {ol.style.IconAnchorUnits} + */ + this.anchorYUnits_ = goog.isDef(options.anchorYUnits) ? + options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION; + /** + * @type {?string} + */ + var crossOrigin = + goog.isDef(options.crossOrigin) ? options.crossOrigin : null; -/** - * @protected - * @return {ol.layer.Layer} Layer. - */ -ol.renderer.Layer.prototype.getLayer = function() { - return this.layer_; -}; + /** + * @type {Image} + */ + var image = goog.isDef(options.img) ? options.img : null; + /** + * @type {ol.Size} + */ + var imgSize = goog.isDef(options.imgSize) ? options.imgSize : null; -/** - * @protected - * @return {ol.Map} Map. - */ -ol.renderer.Layer.prototype.getMap = function() { - return this.mapRenderer_.getMap(); -}; + /** + * @type {string|undefined} + */ + var src = options.src; + goog.asserts.assert(!(goog.isDef(src) && !goog.isNull(image)), + 'image and src can not provided at the same time'); + goog.asserts.assert( + !goog.isDef(src) || (goog.isDef(src) && goog.isNull(imgSize)), + 'imgSize should not be set when src is provided'); + goog.asserts.assert( + goog.isNull(image) || (!goog.isNull(image) && !goog.isNull(imgSize)), + 'imgSize must be set when image is provided'); + + if ((!goog.isDef(src) || src.length === 0) && !goog.isNull(image)) { + src = image.src; + } + goog.asserts.assert(goog.isDef(src) && src.length > 0, + 'must provide a defined and non-empty src or image'); + + /** + * @type {ol.style.ImageState} + */ + var imageState = goog.isDef(options.src) ? + ol.style.ImageState.IDLE : ol.style.ImageState.LOADED; + + /** + * @private + * @type {ol.style.IconImage_} + */ + this.iconImage_ = ol.style.IconImage_.get( + image, src, imgSize, crossOrigin, imageState); + + /** + * @private + * @type {Array.<number>} + */ + this.offset_ = goog.isDef(options.offset) ? options.offset : [0, 0]; + + /** + * @private + * @type {ol.style.IconOrigin} + */ + this.offsetOrigin_ = goog.isDef(options.offsetOrigin) ? + options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT; + + /** + * @private + * @type {Array.<number>} + */ + this.origin_ = null; + + /** + * @private + * @type {ol.Size} + */ + this.size_ = goog.isDef(options.size) ? options.size : null; + + /** + * @type {number} + */ + var opacity = goog.isDef(options.opacity) ? options.opacity : 1; + + /** + * @type {boolean} + */ + var rotateWithView = goog.isDef(options.rotateWithView) ? + options.rotateWithView : false; + + /** + * @type {number} + */ + var rotation = goog.isDef(options.rotation) ? options.rotation : 0; + + /** + * @type {number} + */ + var scale = goog.isDef(options.scale) ? options.scale : 1; + + /** + * @type {boolean} + */ + var snapToPixel = goog.isDef(options.snapToPixel) ? + options.snapToPixel : true; + + goog.base(this, { + opacity: opacity, + rotation: rotation, + scale: scale, + snapToPixel: snapToPixel, + rotateWithView: rotateWithView + }); -/** - * @protected - * @return {ol.renderer.Map} Map renderer. - */ -ol.renderer.Layer.prototype.getMapRenderer = function() { - return this.mapRenderer_; }; +goog.inherits(ol.style.Icon, ol.style.Image); /** - * Handle changes in image state. - * @param {goog.events.Event} event Image change event. - * @protected + * @inheritDoc + * @api */ -ol.renderer.Layer.prototype.handleImageChange = function(event) { - var image = /** @type {ol.Image} */ (event.target); - if (image.getState() === ol.ImageState.LOADED) { - this.renderIfReadyAndVisible(); +ol.style.Icon.prototype.getAnchor = function() { + if (!goog.isNull(this.normalizedAnchor_)) { + return this.normalizedAnchor_; + } + var anchor = this.anchor_; + var size = this.getSize(); + if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION || + this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) { + if (goog.isNull(size)) { + return null; + } + anchor = this.anchor_.slice(); + if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) { + anchor[0] *= size[0]; + } + if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) { + anchor[1] *= size[1]; + } + } + + if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) { + if (goog.isNull(size)) { + return null; + } + if (anchor === this.anchor_) { + anchor = this.anchor_.slice(); + } + if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT || + this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + anchor[0] = -anchor[0] + size[0]; + } + if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || + this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + anchor[1] = -anchor[1] + size[1]; + } } + this.normalizedAnchor_ = anchor; + return this.normalizedAnchor_; }; /** - * @protected + * Get the image icon. + * @param {number} pixelRatio Pixel ratio. + * @return {Image} Image element. + * @api */ -ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() { - var layer = this.getLayer(); - if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) { - this.getMap().render(); - } +ol.style.Icon.prototype.getImage = function(pixelRatio) { + return this.iconImage_.getImage(pixelRatio); }; /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.source.Tile} tileSource Tile source. - * @protected + * Real Image size used. + * @return {ol.Size} Size. */ -ol.renderer.Layer.prototype.scheduleExpireCache = - function(frameState, tileSource) { - if (tileSource.canExpireCache()) { - frameState.postRenderFunctions.push( - goog.partial( - /** - * @param {ol.source.Tile} tileSource Tile source. - * @param {ol.Map} map Map. - * @param {olx.FrameState} frameState Frame state. - */ - function(tileSource, map, frameState) { - var tileSourceKey = goog.getUid(tileSource).toString(); - tileSource.expireCache(frameState.usedTiles[tileSourceKey]); - }, tileSource)); - } +ol.style.Icon.prototype.getImageSize = function() { + return this.iconImage_.getSize(); }; /** - * @param {Object.<string, ol.Attribution>} attributionsSet Attributions - * set (target). - * @param {Array.<ol.Attribution>} attributions Attributions (source). - * @protected + * @inheritDoc */ -ol.renderer.Layer.prototype.updateAttributions = - function(attributionsSet, attributions) { - if (goog.isDefAndNotNull(attributions)) { - var attribution, i, ii; - for (i = 0, ii = attributions.length; i < ii; ++i) { - attribution = attributions[i]; - attributionsSet[goog.getUid(attribution).toString()] = attribution; - } - } +ol.style.Icon.prototype.getHitDetectionImageSize = function() { + return this.getImageSize(); }; /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.source.Source} source Source. - * @protected + * @inheritDoc */ -ol.renderer.Layer.prototype.updateLogos = function(frameState, source) { - var logo = source.getLogo(); - if (goog.isDef(logo)) { - if (goog.isString(logo)) { - frameState.logos[logo] = ''; - } else if (goog.isObject(logo)) { - goog.asserts.assertString(logo.href); - goog.asserts.assertString(logo.src); - frameState.logos[logo.src] = logo.href; - } - } +ol.style.Icon.prototype.getImageState = function() { + return this.iconImage_.getImageState(); }; /** - * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles. - * @param {ol.source.Tile} tileSource Tile source. - * @param {number} z Z. - * @param {ol.TileRange} tileRange Tile range. - * @protected + * @inheritDoc */ -ol.renderer.Layer.prototype.updateUsedTiles = - function(usedTiles, tileSource, z, tileRange) { - // FIXME should we use tilesToDrawByZ instead? - var tileSourceKey = goog.getUid(tileSource).toString(); - var zKey = z.toString(); - if (tileSourceKey in usedTiles) { - if (zKey in usedTiles[tileSourceKey]) { - usedTiles[tileSourceKey][zKey].extend(tileRange); - } else { - usedTiles[tileSourceKey][zKey] = tileRange; +ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) { + return this.iconImage_.getHitDetectionImage(pixelRatio); +}; + + +/** + * @inheritDoc + * @api + */ +ol.style.Icon.prototype.getOrigin = function() { + if (!goog.isNull(this.origin_)) { + return this.origin_; + } + var offset = this.offset_; + + if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) { + var size = this.getSize(); + var iconImageSize = this.iconImage_.getSize(); + if (goog.isNull(size) || goog.isNull(iconImageSize)) { + return null; + } + offset = offset.slice(); + if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT || + this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + offset[0] = iconImageSize[0] - size[0] - offset[0]; + } + if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || + this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { + offset[1] = iconImageSize[1] - size[1] - offset[1]; } - } else { - usedTiles[tileSourceKey] = {}; - usedTiles[tileSourceKey][zKey] = tileRange; } + this.origin_ = offset; + return this.origin_; }; /** - * @param {function(ol.Tile): boolean} isLoadedFunction Function to - * determine if the tile is loaded. - * @param {ol.source.Tile} tileSource Tile source. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @protected - * @return {function(number, number, number): ol.Tile} Returns a tile if it is - * loaded. + * Get the image URL. + * @return {string|undefined} Image src. + * @api */ -ol.renderer.Layer.prototype.createGetTileIfLoadedFunction = - function(isLoadedFunction, tileSource, pixelRatio, projection) { - return ( - /** - * @param {number} z Z. - * @param {number} x X. - * @param {number} y Y. - * @return {ol.Tile} Tile. - */ - function(z, x, y) { - var tile = tileSource.getTile(z, x, y, pixelRatio, projection); - return isLoadedFunction(tile) ? tile : null; - }); +ol.style.Icon.prototype.getSrc = function() { + return this.iconImage_.getSrc(); }; /** - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {ol.Size} size Size. - * @protected - * @return {ol.Coordinate} Snapped center. + * @inheritDoc + * @api */ -ol.renderer.Layer.prototype.snapCenterToPixel = - function(center, resolution, size) { - return [ - resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2), - resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2) - ]; +ol.style.Icon.prototype.getSize = function() { + return goog.isNull(this.size_) ? this.iconImage_.getSize() : this.size_; }; /** - * Manage tile pyramid. - * This function performs a number of functions related to the tiles at the - * current zoom and lower zoom levels: - * - registers idle tiles in frameState.wantedTiles so that they are not - * discarded by the tile queue - * - enqueues missing tiles - * @param {olx.FrameState} frameState Frame state. - * @param {ol.source.Tile} tileSource Tile source. - * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.Extent} extent Extent. - * @param {number} currentZ Current Z. - * @param {number|undefined} preload Load low resolution tiles up to 'preload' - * levels. - * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback. - * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`. - * @protected - * @template T + * @inheritDoc */ -ol.renderer.Layer.prototype.manageTilePyramid = function( - frameState, tileSource, tileGrid, pixelRatio, projection, extent, - currentZ, preload, opt_tileCallback, opt_this) { - var tileSourceKey = goog.getUid(tileSource).toString(); - if (!(tileSourceKey in frameState.wantedTiles)) { - frameState.wantedTiles[tileSourceKey] = {}; - } - var wantedTiles = frameState.wantedTiles[tileSourceKey]; - var tileQueue = frameState.tileQueue; - var minZoom = tileGrid.getMinZoom(); - var tile, tileRange, tileResolution, x, y, z; - if (!goog.isDef(preload)) { - preload = 0; - } - for (z = currentZ; z >= minZoom; --z) { - tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); - tileResolution = tileGrid.getResolution(z); - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - if (currentZ - z <= preload) { - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - if (tile.getState() == ol.TileState.IDLE) { - wantedTiles[ol.tilecoord.toString(tile.tileCoord)] = true; - if (!tileQueue.isKeyQueued(tile.getKey())) { - tileQueue.enqueue([tile, tileSourceKey, - tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); - } - } - if (goog.isDef(opt_tileCallback)) { - opt_tileCallback.call(opt_this, tile); - } - } else { - tileSource.useTile(z, x, y); - } - } - } - } +ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) { + return goog.events.listen(this.iconImage_, goog.events.EventType.CHANGE, + listener, false, thisArg); }; -goog.provide('ol.style.Image'); -goog.provide('ol.style.ImageState'); - /** - * @enum {number} + * Load not yet loaded URI. */ -ol.style.ImageState = { - IDLE: 0, - LOADING: 1, - LOADED: 2, - ERROR: 3 +ol.style.Icon.prototype.load = function() { + this.iconImage_.load(); }; /** - * @typedef {{opacity: number, - * rotateWithView: boolean, - * rotation: number, - * scale: number, - * snapToPixel: boolean}} + * @inheritDoc */ -ol.style.ImageOptions; +ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) { + goog.events.unlisten(this.iconImage_, goog.events.EventType.CHANGE, + listener, false, thisArg); +}; /** - * @classdesc - * Abstract base class; used for creating subclasses and not instantiated in - * apps. Base class for {@link ol.style.Icon} and {@link ol.style.Circle}. - * * @constructor - * @param {ol.style.ImageOptions} options Options. - * @api + * @param {Image} image Image. + * @param {string|undefined} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.style.ImageState} imageState Image state. + * @extends {goog.events.EventTarget} + * @private */ -ol.style.Image = function(options) { +ol.style.IconImage_ = function(image, src, size, crossOrigin, imageState) { + + goog.base(this); /** * @private - * @type {number} + * @type {Image|HTMLCanvasElement} */ - this.opacity_ = options.opacity; + this.hitDetectionImage_ = null; /** * @private - * @type {boolean} + * @type {Image} */ - this.rotateWithView_ = options.rotateWithView; + this.image_ = goog.isNull(image) ? new Image() : image; + + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; + } /** * @private - * @type {number} + * @type {Array.<goog.events.Key>} */ - this.rotation_ = options.rotation; + this.imageListenerKeys_ = null; /** * @private - * @type {number} + * @type {ol.style.ImageState} */ - this.scale_ = options.scale; + this.imageState_ = imageState; /** * @private - * @type {boolean} + * @type {ol.Size} */ - this.snapToPixel_ = options.snapToPixel; + this.size_ = size; -}; + /** + * @private + * @type {string|undefined} + */ + this.src_ = src; + /** + * @private + * @type {boolean} + */ + this.tainting_ = false; -/** - * @return {number} Opacity. - * @api - */ -ol.style.Image.prototype.getOpacity = function() { - return this.opacity_; }; +goog.inherits(ol.style.IconImage_, goog.events.EventTarget); /** - * @return {boolean} Rotate with map. - * @api + * @param {Image} image Image. + * @param {string} src Src. + * @param {ol.Size} size Size. + * @param {?string} crossOrigin Cross origin. + * @param {ol.style.ImageState} imageState Image state. + * @return {ol.style.IconImage_} Icon image. */ -ol.style.Image.prototype.getRotateWithView = function() { - return this.rotateWithView_; +ol.style.IconImage_.get = function(image, src, size, crossOrigin, imageState) { + var iconImageCache = ol.style.IconImageCache.getInstance(); + var iconImage = iconImageCache.get(src, crossOrigin); + if (goog.isNull(iconImage)) { + iconImage = new ol.style.IconImage_( + image, src, size, crossOrigin, imageState); + iconImageCache.set(src, crossOrigin, iconImage); + } + return iconImage; }; /** - * @return {number} Rotation. - * @api + * @private */ -ol.style.Image.prototype.getRotation = function() { - return this.rotation_; +ol.style.IconImage_.prototype.determineTainting_ = function() { + var context = ol.dom.createCanvasContext2D(1, 1); + context.drawImage(this.image_, 0, 0); + try { + context.getImageData(0, 0, 1, 1); + } catch (e) { + this.tainting_ = true; + } }; /** - * @return {number} Scale. - * @api + * @private */ -ol.style.Image.prototype.getScale = function() { - return this.scale_; +ol.style.IconImage_.prototype.dispatchChangeEvent_ = function() { + this.dispatchEvent(goog.events.EventType.CHANGE); }; /** - * @return {boolean} Snap to pixel? - * @api + * @private */ -ol.style.Image.prototype.getSnapToPixel = function() { - return this.snapToPixel_; +ol.style.IconImage_.prototype.handleImageError_ = function() { + this.imageState_ = ol.style.ImageState.ERROR; + this.unlistenImage_(); + this.dispatchChangeEvent_(); }; /** - * @function - * @return {Array.<number>} Anchor. - */ -ol.style.Image.prototype.getAnchor = goog.abstractMethod; - - -/** - * @function - * @param {number} pixelRatio Pixel ratio. - * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. - * @api + * @private */ -ol.style.Image.prototype.getImage = goog.abstractMethod; +ol.style.IconImage_.prototype.handleImageLoad_ = function() { + this.imageState_ = ol.style.ImageState.LOADED; + this.size_ = [this.image_.width, this.image_.height]; + this.unlistenImage_(); + this.determineTainting_(); + this.dispatchChangeEvent_(); +}; /** * @param {number} pixelRatio Pixel ratio. - * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element. + * @return {Image} Image element. */ -ol.style.Image.prototype.getHitDetectionImage = goog.abstractMethod; +ol.style.IconImage_.prototype.getImage = function(pixelRatio) { + return this.image_; +}; /** * @return {ol.style.ImageState} Image state. */ -ol.style.Image.prototype.getImageState = goog.abstractMethod; +ol.style.IconImage_.prototype.getImageState = function() { + return this.imageState_; +}; /** - * @return {ol.Size} Image size. + * @param {number} pixelRatio Pixel ratio. + * @return {Image|HTMLCanvasElement} Image element. */ -ol.style.Image.prototype.getImageSize = goog.abstractMethod; +ol.style.IconImage_.prototype.getHitDetectionImage = function(pixelRatio) { + if (goog.isNull(this.hitDetectionImage_)) { + if (this.tainting_) { + var width = this.size_[0]; + var height = this.size_[1]; + var context = ol.dom.createCanvasContext2D(width, height); + context.fillRect(0, 0, width, height); + this.hitDetectionImage_ = context.canvas; + } else { + this.hitDetectionImage_ = this.image_; + } + } + return this.hitDetectionImage_; +}; /** - * @return {ol.Size} Size of the hit-detection image. + * @return {ol.Size} Image size. */ -ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod; +ol.style.IconImage_.prototype.getSize = function() { + return this.size_; +}; /** - * @function - * @return {Array.<number>} Origin. + * @return {string|undefined} Image src. */ -ol.style.Image.prototype.getOrigin = goog.abstractMethod; +ol.style.IconImage_.prototype.getSrc = function() { + return this.src_; +}; /** - * @function - * @return {Array.<number>} Origin for the hit-detection image. + * Load not yet loaded URI. */ -ol.style.Image.prototype.getHitDetectionOrigin = goog.abstractMethod; - - -/** - * @function - * @return {ol.Size} Size. - */ -ol.style.Image.prototype.getSize = goog.abstractMethod; +ol.style.IconImage_.prototype.load = function() { + if (this.imageState_ == ol.style.ImageState.IDLE) { + goog.asserts.assert(goog.isDef(this.src_), + 'this.src_ must not be undefined'); + goog.asserts.assert(goog.isNull(this.imageListenerKeys_), + 'no listener keys existing'); + this.imageState_ = ol.style.ImageState.LOADING; + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + try { + this.image_.src = this.src_; + } catch (e) { + this.handleImageError_(); + } + } +}; /** - * Set the opacity. + * Discards event handlers which listen for load completion or errors. * - * @param {number} opacity Opacity. + * @private */ -ol.style.Image.prototype.setOpacity = function(opacity) { - this.opacity_ = opacity; +ol.style.IconImage_.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_), + 'we must have listeners registered'); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; }; + /** - * Set whether to rotate the style with the view. - * - * @param {boolean} rotateWithView Rotate with map. + * @constructor */ -ol.style.Image.prototype.setRotateWithView = function(rotateWithView) { - this.rotateWithView_ = rotateWithView; -}; +ol.style.IconImageCache = function() { + /** + * @type {Object.<string, ol.style.IconImage_>} + * @private + */ + this.cache_ = {}; -/** - * Set the rotation. - * - * @param {number} rotation Rotation. - * @api - */ -ol.style.Image.prototype.setRotation = function(rotation) { - this.rotation_ = rotation; + /** + * @type {number} + * @private + */ + this.cacheSize_ = 0; + + /** + * @const + * @type {number} + * @private + */ + this.maxCacheSize_ = 32; }; +goog.addSingletonGetter(ol.style.IconImageCache); /** - * Set the scale. - * - * @param {number} scale Scale. - * @api + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @return {string} Cache key. */ -ol.style.Image.prototype.setScale = function(scale) { - this.scale_ = scale; +ol.style.IconImageCache.getKey = function(src, crossOrigin) { + goog.asserts.assert(goog.isDef(crossOrigin), + 'argument crossOrigin must be defined'); + return crossOrigin + ':' + src; }; /** - * Set whether to snap the image to the closest pixel. - * - * @param {boolean} snapToPixel Snap to pixel? + * FIXME empty description for jsdoc */ -ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) { - this.snapToPixel_ = snapToPixel; +ol.style.IconImageCache.prototype.clear = function() { + this.cache_ = {}; + this.cacheSize_ = 0; }; /** - * @param {function(this: T, goog.events.Event)} listener Listener function. - * @param {T} thisArg Value to use as `this` when executing `listener`. - * @return {goog.events.Key|undefined} Listener key. - * @template T + * FIXME empty description for jsdoc */ -ol.style.Image.prototype.listenImageChange = goog.abstractMethod; +ol.style.IconImageCache.prototype.expire = function() { + if (this.cacheSize_ > this.maxCacheSize_) { + var i = 0; + var key, iconImage; + for (key in this.cache_) { + iconImage = this.cache_[key]; + if ((i++ & 3) === 0 && !goog.events.hasListener(iconImage)) { + delete this.cache_[key]; + --this.cacheSize_; + } + } + } +}; /** - * Load not yet loaded URI. + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @return {ol.style.IconImage_} Icon image. */ -ol.style.Image.prototype.load = goog.abstractMethod; +ol.style.IconImageCache.prototype.get = function(src, crossOrigin) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin); + return key in this.cache_ ? this.cache_[key] : null; +}; /** - * @param {function(this: T, goog.events.Event)} listener Listener function. - * @param {T} thisArg Value to use as `this` when executing `listener`. - * @template T + * @param {string} src Src. + * @param {?string} crossOrigin Cross origin. + * @param {ol.style.IconImage_} iconImage Icon image. */ -ol.style.Image.prototype.unlistenImageChange = goog.abstractMethod; +ol.style.IconImageCache.prototype.set = function(src, crossOrigin, iconImage) { + var key = ol.style.IconImageCache.getKey(src, crossOrigin); + this.cache_[key] = iconImage; + ++this.cacheSize_; +}; -goog.provide('ol.style.Icon'); -goog.provide('ol.style.IconAnchorUnits'); -goog.provide('ol.style.IconImageCache'); -goog.provide('ol.style.IconOrigin'); +goog.provide('ol.RendererType'); +goog.provide('ol.renderer.Map'); -goog.require('goog.array'); +goog.require('goog.Disposable'); goog.require('goog.asserts'); +goog.require('goog.dispose'); goog.require('goog.events'); -goog.require('goog.events.EventTarget'); goog.require('goog.events.EventType'); -goog.require('ol.dom'); -goog.require('ol.style.Image'); -goog.require('ol.style.ImageState'); - - -/** - * Icon anchor units. One of 'fraction', 'pixels'. - * @enum {string} - * @api - */ -ol.style.IconAnchorUnits = { - FRACTION: 'fraction', - PIXELS: 'pixels' -}; +goog.require('goog.functions'); +goog.require('goog.object'); +goog.require('goog.vec.Mat4'); +goog.require('ol.extent'); +goog.require('ol.layer.Layer'); +goog.require('ol.renderer.Layer'); +goog.require('ol.style.IconImageCache'); +goog.require('ol.vec.Mat4'); /** - * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'. + * Available renderers: `'canvas'`, `'dom'` or `'webgl'`. * @enum {string} - * @api + * @api stable */ -ol.style.IconOrigin = { - BOTTOM_LEFT: 'bottom-left', - BOTTOM_RIGHT: 'bottom-right', - TOP_LEFT: 'top-left', - TOP_RIGHT: 'top-right' +ol.RendererType = { + CANVAS: 'canvas', + DOM: 'dom', + WEBGL: 'webgl' }; /** - * @classdesc - * Set icon style for vector features. - * * @constructor - * @param {olx.style.IconOptions=} opt_options Options. - * @extends {ol.style.Image} - * @api + * @extends {goog.Disposable} + * @param {Element} container Container. + * @param {ol.Map} map Map. + * @suppress {checkStructDictInheritance} + * @struct */ -ol.style.Icon = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; +ol.renderer.Map = function(container, map) { - /** - * @private - * @type {Array.<number>} - */ - this.anchor_ = goog.isDef(options.anchor) ? options.anchor : [0.5, 0.5]; + goog.base(this); - /** - * @private - * @type {Array.<number>} - */ - this.normalizedAnchor_ = null; /** * @private - * @type {ol.style.IconOrigin} + * @type {ol.Map} */ - this.anchorOrigin_ = goog.isDef(options.anchorOrigin) ? - options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT; + this.map_ = map; /** * @private - * @type {ol.style.IconAnchorUnits} + * @type {Object.<string, ol.renderer.Layer>} */ - this.anchorXUnits_ = goog.isDef(options.anchorXUnits) ? - options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION; + this.layerRenderers_ = {}; /** * @private - * @type {ol.style.IconAnchorUnits} - */ - this.anchorYUnits_ = goog.isDef(options.anchorYUnits) ? - options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION; - - /** - * @type {?string} - */ - var crossOrigin = - goog.isDef(options.crossOrigin) ? options.crossOrigin : null; - - /** - * @type {Image} + * @type {Object.<string, goog.events.Key>} */ - var image = goog.isDef(options.img) ? options.img : null; + this.layerRendererListeners_ = {}; - /** - * @type {string|undefined} - */ - var src = options.src; +}; +goog.inherits(ol.renderer.Map, goog.Disposable); - if ((!goog.isDef(src) || src.length === 0) && !goog.isNull(image)) { - src = image.src; - } - goog.asserts.assert(goog.isDef(src) && src.length > 0); - /** - * @type {ol.style.ImageState} - */ - var imageState = goog.isDef(options.src) ? - ol.style.ImageState.IDLE : ol.style.ImageState.LOADED; +/** + * @param {olx.FrameState} frameState FrameState. + * @protected + */ +ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) { + var viewState = frameState.viewState; + var coordinateToPixelMatrix = frameState.coordinateToPixelMatrix; + goog.asserts.assert(!goog.isNull(coordinateToPixelMatrix), + 'frameState has non-null coordinateToPixelMatrix'); + ol.vec.Mat4.makeTransform2D(coordinateToPixelMatrix, + frameState.size[0] / 2, frameState.size[1] / 2, + 1 / viewState.resolution, -1 / viewState.resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); + var inverted = goog.vec.Mat4.invert( + coordinateToPixelMatrix, frameState.pixelToCoordinateMatrix); + goog.asserts.assert(inverted, 'matrix could be inverted'); +}; - /** - * @private - * @type {ol.style.IconImage_} - */ - this.iconImage_ = ol.style.IconImage_.get( - image, src, crossOrigin, imageState); - /** - * @private - * @type {Array.<number>} - */ - this.offset_ = goog.isDef(options.offset) ? options.offset : [0, 0]; +/** + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} layerRenderer Layer renderer. + */ +ol.renderer.Map.prototype.createLayerRenderer = goog.abstractMethod; - /** - * @private - * @type {ol.style.IconOrigin} - */ - this.offsetOrigin_ = goog.isDef(options.offsetOrigin) ? - options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT; - /** - * @private - * @type {Array.<number>} - */ - this.origin_ = null; +/** + * @inheritDoc + */ +ol.renderer.Map.prototype.disposeInternal = function() { + goog.object.forEach(this.layerRenderers_, goog.dispose); + goog.base(this, 'disposeInternal'); +}; - /** - * @private - * @type {ol.Size} - */ - this.size_ = goog.isDef(options.size) ? options.size : null; - /** - * @type {number} - */ - var opacity = goog.isDef(options.opacity) ? options.opacity : 1; +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.Map.expireIconCache_ = function(map, frameState) { + ol.style.IconImageCache.getInstance().expire(); +}; - /** - * @type {boolean} - */ - var rotateWithView = goog.isDef(options.rotateWithView) ? - options.rotateWithView : false; - /** - * @type {number} - */ - var rotation = goog.isDef(options.rotation) ? options.rotation : 0; +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; - /** - * @type {number} - */ - var scale = goog.isDef(options.scale) ? options.scale : 1; + /** @type {Object.<string, boolean>} */ + var features = {}; /** - * @type {boolean} + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. */ - var snapToPixel = goog.isDef(options.snapToPixel) ? - options.snapToPixel : true; - - goog.base(this, { - opacity: opacity, - rotation: rotation, - scale: scale, - snapToPixel: snapToPixel, - rotateWithView: rotateWithView - }); - -}; -goog.inherits(ol.style.Icon, ol.style.Image); + function forEachFeatureAtCoordinate(feature) { + goog.asserts.assert(goog.isDef(feature), 'received a feature'); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, null); + } + } + var projection = viewState.projection; -/** - * @inheritDoc - * @api - */ -ol.style.Icon.prototype.getAnchor = function() { - if (!goog.isNull(this.normalizedAnchor_)) { - return this.normalizedAnchor_; - } - var anchor = this.anchor_; - var size = this.getSize(); - if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION || - this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) { - if (goog.isNull(size)) { - return null; - } - anchor = this.anchor_.slice(); - if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) { - anchor[0] *= size[0]; - } - if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) { - anchor[1] *= size[1]; + var translatedCoordinate = coordinate; + if (projection.canWrapX()) { + var projectionExtent = projection.getExtent(); + var worldWidth = ol.extent.getWidth(projectionExtent); + var x = coordinate[0]; + if (x < projectionExtent[0] || x > projectionExtent[2]) { + var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); + translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]]; } } - if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) { - if (goog.isNull(size)) { - return null; - } - if (anchor === this.anchor_) { - anchor = this.anchor_.slice(); - } - if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT || - this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { - anchor[0] = -anchor[0] + size[0]; - } - if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || - this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { - anchor[1] = -anchor[1] + size[1]; + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (!layerState.managed || + (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer))) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachFeatureAtCoordinate( + layer.getSource().getWrapX() ? translatedCoordinate : coordinate, + frameState, callback, thisArg); + if (result) { + return result; + } } } - this.normalizedAnchor_ = anchor; - return this.normalizedAnchor_; + return undefined; }; /** - * @param {number} pixelRatio Pixel ratio. - * @return {Image} Image element. - * @api + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U */ -ol.style.Icon.prototype.getImage = function(pixelRatio) { - return this.iconImage_.getImage(pixelRatio); +ol.renderer.Map.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; }; /** - * Real Image size used. - * @return {ol.Size} Size. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg Value to use as `this` when executing `layerFilter`. + * @return {boolean} Is there a feature at the given coordinate? + * @template U */ -ol.style.Icon.prototype.getImageSize = function() { - return this.iconImage_.getSize(); +ol.renderer.Map.prototype.hasFeatureAtCoordinate = + function(coordinate, frameState, layerFilter, thisArg) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this, layerFilter, thisArg); + + return goog.isDef(hasFeature); }; /** - * @inheritDoc + * @param {ol.layer.Layer} layer Layer. + * @protected + * @return {ol.renderer.Layer} Layer renderer. */ -ol.style.Icon.prototype.getHitDetectionImageSize = function() { - return this.getImageSize(); +ol.renderer.Map.prototype.getLayerRenderer = function(layer) { + var layerKey = goog.getUid(layer).toString(); + if (layerKey in this.layerRenderers_) { + return this.layerRenderers_[layerKey]; + } else { + var layerRenderer = this.createLayerRenderer(layer); + this.layerRenderers_[layerKey] = layerRenderer; + this.layerRendererListeners_[layerKey] = goog.events.listen(layerRenderer, + goog.events.EventType.CHANGE, this.handleLayerRendererChange_, + false, this); + + return layerRenderer; + } }; /** - * @inheritDoc + * @param {string} layerKey Layer key. + * @protected + * @return {ol.renderer.Layer} Layer renderer. */ -ol.style.Icon.prototype.getImageState = function() { - return this.iconImage_.getImageState(); +ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) { + goog.asserts.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + return this.layerRenderers_[layerKey]; }; /** - * @inheritDoc + * @protected + * @return {Object.<number, ol.renderer.Layer>} Layer renderers. */ -ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) { - return this.iconImage_.getHitDetectionImage(pixelRatio); +ol.renderer.Map.prototype.getLayerRenderers = function() { + return this.layerRenderers_; }; /** - * @inheritDoc - * @api + * @return {ol.Map} Map. */ -ol.style.Icon.prototype.getOrigin = function() { - if (!goog.isNull(this.origin_)) { - return this.origin_; - } - var offset = this.offset_; - - if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) { - var size = this.getSize(); - var iconImageSize = this.iconImage_.getSize(); - if (goog.isNull(size) || goog.isNull(iconImageSize)) { - return null; - } - offset = offset.slice(); - if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT || - this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { - offset[0] = iconImageSize[0] - size[0] - offset[0]; - } - if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT || - this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) { - offset[1] = iconImageSize[1] - size[1] - offset[1]; - } - } - this.origin_ = offset; - return this.origin_; +ol.renderer.Map.prototype.getMap = function() { + return this.map_; }; /** - * @inheritDoc + * @return {string} Type */ -ol.style.Icon.prototype.getHitDetectionOrigin = function() { - return this.getOrigin(); -}; +ol.renderer.Map.prototype.getType = goog.abstractMethod; /** - * @return {string|undefined} Image src. - * @api + * Handle changes in a layer renderer. + * @private */ -ol.style.Icon.prototype.getSrc = function() { - return this.iconImage_.getSrc(); +ol.renderer.Map.prototype.handleLayerRendererChange_ = function() { + this.map_.render(); }; /** - * @inheritDoc - * @api + * @param {string} layerKey Layer key. + * @return {ol.renderer.Layer} Layer renderer. + * @private */ -ol.style.Icon.prototype.getSize = function() { - return goog.isNull(this.size_) ? this.iconImage_.getSize() : this.size_; +ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) { + goog.asserts.assert(layerKey in this.layerRenderers_, + 'given layerKey (%s) exists in layerRenderers', layerKey); + var layerRenderer = this.layerRenderers_[layerKey]; + delete this.layerRenderers_[layerKey]; + + goog.asserts.assert(layerKey in this.layerRendererListeners_, + 'given layerKey (%s) exists in layerRendererListeners', layerKey); + goog.events.unlistenByKey(this.layerRendererListeners_[layerKey]); + delete this.layerRendererListeners_[layerKey]; + + return layerRenderer; }; /** - * @inheritDoc + * Render. + * @param {?olx.FrameState} frameState Frame state. */ -ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) { - return goog.events.listen(this.iconImage_, goog.events.EventType.CHANGE, - listener, false, thisArg); +ol.renderer.Map.prototype.renderFrame = goog.nullFunction; + + +/** + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = + function(map, frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (goog.isNull(frameState) || !(layerKey in frameState.layerStates)) { + goog.dispose(this.removeLayerRendererByKey_(layerKey)); + } + } }; /** - * Load not yet loaded URI. + * @param {olx.FrameState} frameState Frame state. + * @protected */ -ol.style.Icon.prototype.load = function() { - this.iconImage_.load(); +ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) { + frameState.postRenderFunctions.push(ol.renderer.Map.expireIconCache_); }; /** - * @inheritDoc + * @param {!olx.FrameState} frameState Frame state. + * @protected */ -ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) { - goog.events.unlisten(this.iconImage_, goog.events.EventType.CHANGE, - listener, false, thisArg); +ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = + function(frameState) { + var layerKey; + for (layerKey in this.layerRenderers_) { + if (!(layerKey in frameState.layerStates)) { + frameState.postRenderFunctions.push( + goog.bind(this.removeUnusedLayerRenderers_, this)); + return; + } + } }; +goog.provide('ol.structs.PriorityQueue'); + +goog.require('goog.asserts'); +goog.require('goog.object'); + /** + * Priority queue. + * + * The implementation is inspired from the Closure Library's Heap class and + * Python's heapq module. + * + * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html + * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py + * * @constructor - * @param {Image} image Image. - * @param {string|undefined} src Src. - * @param {?string} crossOrigin Cross origin. - * @param {ol.style.ImageState} imageState Image state. - * @extends {goog.events.EventTarget} - * @private + * @param {function(T): number} priorityFunction Priority function. + * @param {function(T): string} keyFunction Key function. + * @struct + * @template T */ -ol.style.IconImage_ = function(image, src, crossOrigin, imageState) { - - goog.base(this); +ol.structs.PriorityQueue = function(priorityFunction, keyFunction) { /** + * @type {function(T): number} * @private - * @type {Image|HTMLCanvasElement} */ - this.hitDetectionImage_ = null; + this.priorityFunction_ = priorityFunction; /** + * @type {function(T): string} * @private - * @type {Image} */ - this.image_ = goog.isNull(image) ? new Image() : image; - - if (!goog.isNull(crossOrigin)) { - this.image_.crossOrigin = crossOrigin; - } + this.keyFunction_ = keyFunction; /** + * @type {Array.<T>} * @private - * @type {Array.<number>} */ - this.imageListenerKeys_ = null; + this.elements_ = []; /** + * @type {Array.<number>} * @private - * @type {ol.style.ImageState} */ - this.imageState_ = imageState; + this.priorities_ = []; /** + * @type {Object.<string, boolean>} * @private - * @type {ol.Size} */ - this.size_ = null; + this.queuedElements_ = {}; - /** - * @private - * @type {string|undefined} - */ - this.src_ = src; +}; - /** - * @private - * @type {boolean} - */ - this.tainting_ = false; +/** + * @const + * @type {number} + */ +ol.structs.PriorityQueue.DROP = Infinity; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.assertValid = function() { + var elements = this.elements_; + var priorities = this.priorities_; + var n = elements.length; + goog.asserts.assert(priorities.length == n); + var i, priority; + for (i = 0; i < (n >> 1) - 1; ++i) { + priority = priorities[i]; + goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)], + 'priority smaller than or equal to priority of left child (%s <= %s)', + priority, priorities[this.getLeftChildIndex_(i)]); + goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)], + 'priority smaller than or equal to priority of right child (%s <= %s)', + priority, priorities[this.getRightChildIndex_(i)]); + } +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.PriorityQueue.prototype.clear = function() { + this.elements_.length = 0; + this.priorities_.length = 0; + goog.object.clear(this.queuedElements_); }; -goog.inherits(ol.style.IconImage_, goog.events.EventTarget); /** - * @param {Image} image Image. - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @param {ol.style.ImageState} imageState Image state. - * @return {ol.style.IconImage_} Icon image. + * Remove and return the highest-priority element. O(log N). + * @return {T} Element. */ -ol.style.IconImage_.get = function(image, src, crossOrigin, imageState) { - var iconImageCache = ol.style.IconImageCache.getInstance(); - var iconImage = iconImageCache.get(src, crossOrigin); - if (goog.isNull(iconImage)) { - iconImage = new ol.style.IconImage_(image, src, crossOrigin, imageState); - iconImageCache.set(src, crossOrigin, iconImage); +ol.structs.PriorityQueue.prototype.dequeue = function() { + var elements = this.elements_; + goog.asserts.assert(elements.length > 0, + 'must have elements in order to be able to dequeue'); + var priorities = this.priorities_; + var element = elements[0]; + if (elements.length == 1) { + elements.length = 0; + priorities.length = 0; + } else { + elements[0] = elements.pop(); + priorities[0] = priorities.pop(); + this.siftUp_(0); } - return iconImage; + var elementKey = this.keyFunction_(element); + goog.asserts.assert(elementKey in this.queuedElements_, + 'key %s is not listed as queued', elementKey); + delete this.queuedElements_[elementKey]; + return element; }; /** - * @private + * Enqueue an element. O(log N). + * @param {T} element Element. */ -ol.style.IconImage_.prototype.determineTainting_ = function() { - var context = ol.dom.createCanvasContext2D(1, 1); - context.drawImage(this.image_, 0, 0); - try { - context.getImageData(0, 0, 1, 1); - } catch (e) { - this.tainting_ = true; +ol.structs.PriorityQueue.prototype.enqueue = function(element) { + goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_), + 'key %s is already listed as queued', this.keyFunction_(element)); + var priority = this.priorityFunction_(element); + if (priority != ol.structs.PriorityQueue.DROP) { + this.elements_.push(element); + this.priorities_.push(priority); + this.queuedElements_[this.keyFunction_(element)] = true; + this.siftDown_(0, this.elements_.length - 1); } }; /** + * @return {number} Count. + */ +ol.structs.PriorityQueue.prototype.getCount = function() { + return this.elements_.length; +}; + + +/** + * Gets the index of the left child of the node at the given index. + * @param {number} index The index of the node to get the left child for. + * @return {number} The index of the left child. * @private */ -ol.style.IconImage_.prototype.dispatchChangeEvent_ = function() { - this.dispatchEvent(goog.events.EventType.CHANGE); +ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) { + return index * 2 + 1; }; /** + * Gets the index of the right child of the node at the given index. + * @param {number} index The index of the node to get the right child for. + * @return {number} The index of the right child. * @private */ -ol.style.IconImage_.prototype.handleImageError_ = function() { - this.imageState_ = ol.style.ImageState.ERROR; - this.unlistenImage_(); - this.dispatchChangeEvent_(); +ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) { + return index * 2 + 2; }; /** + * Gets the index of the parent of the node at the given index. + * @param {number} index The index of the node to get the parent for. + * @return {number} The index of the parent. * @private */ -ol.style.IconImage_.prototype.handleImageLoad_ = function() { - this.imageState_ = ol.style.ImageState.LOADED; - this.size_ = [this.image_.width, this.image_.height]; - this.unlistenImage_(); - this.determineTainting_(); - this.dispatchChangeEvent_(); +ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) { + return (index - 1) >> 1; }; /** - * @param {number} pixelRatio Pixel ratio. - * @return {Image} Image element. + * Make this a heap. O(N). + * @private */ -ol.style.IconImage_.prototype.getImage = function(pixelRatio) { - return this.image_; +ol.structs.PriorityQueue.prototype.heapify_ = function() { + var i; + for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { + this.siftUp_(i); + } }; /** - * @return {ol.style.ImageState} Image state. + * @return {boolean} Is empty. */ -ol.style.IconImage_.prototype.getImageState = function() { - return this.imageState_; +ol.structs.PriorityQueue.prototype.isEmpty = function() { + return this.elements_.length === 0; }; /** - * @param {number} pixelRatio Pixel ratio. - * @return {Image|HTMLCanvasElement} Image element. + * @param {string} key Key. + * @return {boolean} Is key queued. */ -ol.style.IconImage_.prototype.getHitDetectionImage = function(pixelRatio) { - if (goog.isNull(this.hitDetectionImage_)) { - if (this.tainting_) { - var width = this.size_[0]; - var height = this.size_[1]; - var context = ol.dom.createCanvasContext2D(width, height); - context.fillRect(0, 0, width, height); - this.hitDetectionImage_ = context.canvas; - } else { - this.hitDetectionImage_ = this.image_; - } - } - return this.hitDetectionImage_; +ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) { + return key in this.queuedElements_; }; /** - * @return {ol.Size} Image size. + * @param {T} element Element. + * @return {boolean} Is queued. */ -ol.style.IconImage_.prototype.getSize = function() { - return this.size_; +ol.structs.PriorityQueue.prototype.isQueued = function(element) { + return this.isKeyQueued(this.keyFunction_(element)); }; /** - * @return {string|undefined} Image src. + * @param {number} index The index of the node to move down. + * @private */ -ol.style.IconImage_.prototype.getSrc = function() { - return this.src_; +ol.structs.PriorityQueue.prototype.siftUp_ = function(index) { + var elements = this.elements_; + var priorities = this.priorities_; + var count = elements.length; + var element = elements[index]; + var priority = priorities[index]; + var startIndex = index; + + while (index < (count >> 1)) { + var lIndex = this.getLeftChildIndex_(index); + var rIndex = this.getRightChildIndex_(index); + + var smallerChildIndex = rIndex < count && + priorities[rIndex] < priorities[lIndex] ? + rIndex : lIndex; + + elements[index] = elements[smallerChildIndex]; + priorities[index] = priorities[smallerChildIndex]; + index = smallerChildIndex; + } + + elements[index] = element; + priorities[index] = priority; + this.siftDown_(startIndex, index); }; /** - * Load not yet loaded URI. + * @param {number} startIndex The index of the root. + * @param {number} index The index of the node to move up. + * @private */ -ol.style.IconImage_.prototype.load = function() { - if (this.imageState_ == ol.style.ImageState.IDLE) { - goog.asserts.assert(goog.isDef(this.src_)); - goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); - this.imageState_ = ol.style.ImageState.LOADING; - this.imageListenerKeys_ = [ - goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, - this.handleImageError_, false, this), - goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, - this.handleImageLoad_, false, this) - ]; - try { - this.image_.src = this.src_; - } catch (e) { - this.handleImageError_(); +ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) { + var elements = this.elements_; + var priorities = this.priorities_; + var element = elements[index]; + var priority = priorities[index]; + + while (index > startIndex) { + var parentIndex = this.getParentIndex_(index); + if (priorities[parentIndex] > priority) { + elements[index] = elements[parentIndex]; + priorities[index] = priorities[parentIndex]; + index = parentIndex; + } else { + break; } } + elements[index] = element; + priorities[index] = priority; }; /** - * Discards event handlers which listen for load completion or errors. - * - * @private + * FIXME empty description for jsdoc */ -ol.style.IconImage_.prototype.unlistenImage_ = function() { - goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); - goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); - this.imageListenerKeys_ = null; +ol.structs.PriorityQueue.prototype.reprioritize = function() { + var priorityFunction = this.priorityFunction_; + var elements = this.elements_; + var priorities = this.priorities_; + var index = 0; + var n = elements.length; + var element, i, priority; + for (i = 0; i < n; ++i) { + element = elements[i]; + priority = priorityFunction(element); + if (priority == ol.structs.PriorityQueue.DROP) { + delete this.queuedElements_[this.keyFunction_(element)]; + } else { + priorities[index] = priority; + elements[index++] = element; + } + } + elements.length = index; + priorities.length = index; + this.heapify_(); }; +goog.provide('ol.TilePriorityFunction'); +goog.provide('ol.TileQueue'); + +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.Coordinate'); +goog.require('ol.TileState'); +goog.require('ol.structs.PriorityQueue'); + + +/** + * @typedef {function(ol.Tile, string, ol.Coordinate, number): number} + */ +ol.TilePriorityFunction; + /** * @constructor + * @extends {ol.structs.PriorityQueue.<Array>} + * @param {ol.TilePriorityFunction} tilePriorityFunction + * Tile priority function. + * @param {function(): ?} tileChangeCallback + * Function called on each tile change event. + * @struct */ -ol.style.IconImageCache = function() { +ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) { - /** - * @type {Object.<string, ol.style.IconImage_>} - * @private - */ - this.cache_ = {}; + goog.base( + this, + /** + * @param {Array} element Element. + * @return {number} Priority. + */ + function(element) { + return tilePriorityFunction.apply(null, element); + }, + /** + * @param {Array} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); /** - * @type {number} * @private + * @type {function(): ?} */ - this.cacheSize_ = 0; + this.tileChangeCallback_ = tileChangeCallback; /** - * @const - * @type {number} * @private + * @type {number} */ - this.maxCacheSize_ = 32; + this.tilesLoading_ = 0; + }; -goog.addSingletonGetter(ol.style.IconImageCache); +goog.inherits(ol.TileQueue, ol.structs.PriorityQueue); /** - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @return {string} Cache key. + * @return {number} Number of tiles loading. */ -ol.style.IconImageCache.getKey = function(src, crossOrigin) { - goog.asserts.assert(goog.isDef(crossOrigin)); - return crossOrigin + ':' + src; +ol.TileQueue.prototype.getTilesLoading = function() { + return this.tilesLoading_; }; /** - * FIXME empty description for jsdoc + * @param {goog.events.Event} event Event. + * @protected */ -ol.style.IconImageCache.prototype.clear = function() { - this.cache_ = {}; - this.cacheSize_ = 0; +ol.TileQueue.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + var state = tile.getState(); + if (state === ol.TileState.LOADED || state === ol.TileState.ERROR || + state === ol.TileState.EMPTY) { + goog.events.unlisten(tile, goog.events.EventType.CHANGE, + this.handleTileChange, false, this); + --this.tilesLoading_; + this.tileChangeCallback_(); + } }; /** - * FIXME empty description for jsdoc + * @param {number} maxTotalLoading Maximum number tiles to load simultaneously. + * @param {number} maxNewLoads Maximum number of new tiles to load. */ -ol.style.IconImageCache.prototype.expire = function() { - if (this.cacheSize_ > this.maxCacheSize_) { - var i = 0; - var key, iconImage; - for (key in this.cache_) { - iconImage = this.cache_[key]; - if ((i++ & 3) === 0 && !goog.events.hasListener(iconImage)) { - delete this.cache_[key]; - --this.cacheSize_; - } +ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) { + var newLoads = 0; + var tile; + while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads && + this.getCount() > 0) { + tile = /** @type {ol.Tile} */ (this.dequeue()[0]); + if (tile.getState() === ol.TileState.IDLE) { + goog.events.listen(tile, goog.events.EventType.CHANGE, + this.handleTileChange, false, this); + tile.load(); + ++this.tilesLoading_; + ++newLoads; } } }; +goog.provide('ol.Kinetic'); + +goog.require('ol.Coordinate'); +goog.require('ol.PreRenderFunction'); +goog.require('ol.animation'); -/** - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @return {ol.style.IconImage_} Icon image. - */ -ol.style.IconImageCache.prototype.get = function(src, crossOrigin) { - var key = ol.style.IconImageCache.getKey(src, crossOrigin); - return key in this.cache_ ? this.cache_[key] : null; -}; /** - * @param {string} src Src. - * @param {?string} crossOrigin Cross origin. - * @param {ol.style.IconImage_} iconImage Icon image. + * @classdesc + * Implementation of inertial deceleration for map movement. + * + * @constructor + * @param {number} decay Rate of decay (must be negative). + * @param {number} minVelocity Minimum velocity (pixels/millisecond). + * @param {number} delay Delay to consider to calculate the kinetic + * initial values (milliseconds). + * @struct + * @api */ -ol.style.IconImageCache.prototype.set = function(src, crossOrigin, iconImage) { - var key = ol.style.IconImageCache.getKey(src, crossOrigin); - this.cache_[key] = iconImage; - ++this.cacheSize_; -}; +ol.Kinetic = function(decay, minVelocity, delay) { -goog.provide('ol.vec.Mat4'); + /** + * @private + * @type {number} + */ + this.decay_ = decay; -goog.require('goog.vec.Mat4'); + /** + * @private + * @type {number} + */ + this.minVelocity_ = minVelocity; - -/** - * @param {!goog.vec.Mat4.Number} mat Matrix. - * @param {number} translateX1 Translate X1. - * @param {number} translateY1 Translate Y1. - * @param {number} scaleX Scale X. - * @param {number} scaleY Scale Y. - * @param {number} rotation Rotation. - * @param {number} translateX2 Translate X2. - * @param {number} translateY2 Translate Y2. - * @return {!goog.vec.Mat4.Number} Matrix. - */ -ol.vec.Mat4.makeTransform2D = function(mat, translateX1, translateY1, - scaleX, scaleY, rotation, translateX2, translateY2) { - goog.vec.Mat4.makeIdentity(mat); - if (translateX1 !== 0 || translateY1 !== 0) { - goog.vec.Mat4.translate(mat, translateX1, translateY1, 0); - } - if (scaleX != 1 || scaleY != 1) { - goog.vec.Mat4.scale(mat, scaleX, scaleY, 1); - } - if (rotation !== 0) { - goog.vec.Mat4.rotateZ(mat, rotation); - } - if (translateX2 !== 0 || translateY2 !== 0) { - goog.vec.Mat4.translate(mat, translateX2, translateY2, 0); - } - return mat; -}; - - -/** - * Returns true if mat1 and mat2 represent the same 2D transformation. - * @param {goog.vec.Mat4.Number} mat1 Matrix 1. - * @param {goog.vec.Mat4.Number} mat2 Matrix 2. - * @return {boolean} Equal 2D. - */ -ol.vec.Mat4.equals2D = function(mat1, mat2) { - return ( - goog.vec.Mat4.getElement(mat1, 0, 0) == - goog.vec.Mat4.getElement(mat2, 0, 0) && - goog.vec.Mat4.getElement(mat1, 1, 0) == - goog.vec.Mat4.getElement(mat2, 1, 0) && - goog.vec.Mat4.getElement(mat1, 0, 1) == - goog.vec.Mat4.getElement(mat2, 0, 1) && - goog.vec.Mat4.getElement(mat1, 1, 1) == - goog.vec.Mat4.getElement(mat2, 1, 1) && - goog.vec.Mat4.getElement(mat1, 0, 3) == - goog.vec.Mat4.getElement(mat2, 0, 3) && - goog.vec.Mat4.getElement(mat1, 1, 3) == - goog.vec.Mat4.getElement(mat2, 1, 3)); -}; - - -/** - * Transforms the given vector with the given matrix storing the resulting, - * transformed vector into resultVec. The input vector is multiplied against the - * upper 2x4 matrix omitting the projective component. - * - * @param {goog.vec.Mat4.Number} mat The matrix supplying the transformation. - * @param {Array.<number>} vec The 3 element vector to transform. - * @param {Array.<number>} resultVec The 3 element vector to receive the results - * (may be vec). - * @return {Array.<number>} return resultVec so that operations can be - * chained together. - */ -ol.vec.Mat4.multVec2 = function(mat, vec, resultVec) { - var m00 = goog.vec.Mat4.getElement(mat, 0, 0); - var m10 = goog.vec.Mat4.getElement(mat, 1, 0); - var m01 = goog.vec.Mat4.getElement(mat, 0, 1); - var m11 = goog.vec.Mat4.getElement(mat, 1, 1); - var m03 = goog.vec.Mat4.getElement(mat, 0, 3); - var m13 = goog.vec.Mat4.getElement(mat, 1, 3); - var x = vec[0], y = vec[1]; - resultVec[0] = m00 * x + m01 * y + m03; - resultVec[1] = m10 * x + m11 * y + m13; - return resultVec; -}; - -goog.provide('ol.RendererType'); -goog.provide('ol.renderer.Map'); - -goog.require('goog.Disposable'); -goog.require('goog.asserts'); -goog.require('goog.dispose'); -goog.require('goog.object'); -goog.require('goog.vec.Mat4'); -goog.require('ol.layer.Layer'); -goog.require('ol.renderer.Layer'); -goog.require('ol.style.IconImageCache'); -goog.require('ol.vec.Mat4'); - - -/** - * Available renderers: `'canvas'`, `'dom'` or `'webgl'`. - * @enum {string} - * @api stable - */ -ol.RendererType = { - CANVAS: 'canvas', - DOM: 'dom', - WEBGL: 'webgl' -}; - - - -/** - * @constructor - * @extends {goog.Disposable} - * @param {Element} container Container. - * @param {ol.Map} map Map. - * @suppress {checkStructDictInheritance} - * @struct - */ -ol.renderer.Map = function(container, map) { - - goog.base(this); + /** + * @private + * @type {number} + */ + this.delay_ = delay; /** * @private - * @type {ol.Map} + * @type {Array.<number>} */ - this.map_ = map; + this.points_ = []; /** - * @protected - * @type {ol.render.IReplayGroup} + * @private + * @type {number} */ - this.replayGroup = null; + this.angle_ = 0; /** * @private - * @type {Object.<string, ol.renderer.Layer>} + * @type {number} */ - this.layerRenderers_ = {}; - -}; -goog.inherits(ol.renderer.Map, goog.Disposable); - - -/** - * @param {olx.FrameState} frameState FrameState. - * @protected - */ -ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) { - var viewState = frameState.viewState; - var coordinateToPixelMatrix = frameState.coordinateToPixelMatrix; - goog.asserts.assert(!goog.isNull(coordinateToPixelMatrix)); - ol.vec.Mat4.makeTransform2D(coordinateToPixelMatrix, - frameState.size[0] / 2, frameState.size[1] / 2, - 1 / viewState.resolution, -1 / viewState.resolution, - -viewState.rotation, - -viewState.center[0], -viewState.center[1]); - var inverted = goog.vec.Mat4.invert( - coordinateToPixelMatrix, frameState.pixelToCoordinateMatrix); - goog.asserts.assert(inverted); -}; - - -/** - * @param {ol.layer.Layer} layer Layer. - * @protected - * @return {ol.renderer.Layer} layerRenderer Layer renderer. - */ -ol.renderer.Map.prototype.createLayerRenderer = function(layer) { - return new ol.renderer.Layer(this, layer); + this.initialVelocity_ = 0; }; /** - * @inheritDoc + * FIXME empty description for jsdoc */ -ol.renderer.Map.prototype.disposeInternal = function() { - goog.object.forEach(this.layerRenderers_, goog.dispose); - goog.base(this, 'disposeInternal'); +ol.Kinetic.prototype.begin = function() { + this.points_.length = 0; + this.angle_ = 0; + this.initialVelocity_ = 0; }; /** - * @param {ol.Map} map Map. - * @param {olx.FrameState} frameState Frame state. - * @private + * @param {number} x X. + * @param {number} y Y. */ -ol.renderer.Map.expireIconCache_ = function(map, frameState) { - ol.style.IconImageCache.getInstance().expire(); +ol.Kinetic.prototype.update = function(x, y) { + this.points_.push(x, y, goog.now()); }; /** - * @param {ol.Coordinate} coordinate Coordinate. - * @param {olx.FrameState} frameState FrameState. - * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature - * callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. - * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter - * function, only layers which are visible and for which this function - * returns `true` will be tested for features. By default, all visible - * layers will be tested. - * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. - * @return {T|undefined} Callback result. - * @template S,T,U + * @return {boolean} Whether we should do kinetic animation. */ -ol.renderer.Map.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg, - layerFilter, thisArg2) { - var result; - var viewState = frameState.viewState; - var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; - if (!goog.isNull(this.replayGroup)) { - /** @type {Object.<string, boolean>} */ - var features = {}; - result = this.replayGroup.forEachGeometryAtPixel(viewResolution, - viewRotation, coordinate, {}, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - goog.asserts.assert(goog.isDef(feature)); - var key = goog.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback.call(thisArg, feature, null); - } - }); - if (result) { - return result; - } +ol.Kinetic.prototype.end = function() { + if (this.points_.length < 6) { + // at least 2 points are required (i.e. there must be at least 6 elements + // in the array) + return false; } - var layerStates = this.map_.getLayerGroup().getLayerStatesArray(); - var numLayers = layerStates.length; - var i; - for (i = numLayers - 1; i >= 0; --i) { - var layerState = layerStates[i]; - var layer = layerState.layer; - if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && - layerFilter.call(thisArg2, layer)) { - var layerRenderer = this.getLayerRenderer(layer); - result = layerRenderer.forEachFeatureAtPixel( - coordinate, frameState, callback, thisArg); - if (result) { - return result; - } - } + var delay = goog.now() - this.delay_; + var lastIndex = this.points_.length - 3; + if (this.points_[lastIndex + 2] < delay) { + // the last tracked point is too old, which means that the user stopped + // panning before releasing the map + return false; } - return undefined; -}; - -/** - * @param {ol.layer.Layer} layer Layer. - * @protected - * @return {ol.renderer.Layer} Layer renderer. - */ -ol.renderer.Map.prototype.getLayerRenderer = function(layer) { - var layerKey = goog.getUid(layer).toString(); - if (layerKey in this.layerRenderers_) { - return this.layerRenderers_[layerKey]; - } else { - var layerRenderer = this.createLayerRenderer(layer); - this.layerRenderers_[layerKey] = layerRenderer; - return layerRenderer; + // get the first point which still falls into the delay time + var firstIndex = lastIndex - 3; + while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) { + firstIndex -= 3; } + var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2]; + var dx = this.points_[lastIndex] - this.points_[firstIndex]; + var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1]; + this.angle_ = Math.atan2(dy, dx); + this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration; + return this.initialVelocity_ > this.minVelocity_; }; /** - * @param {string} layerKey Layer key. - * @protected - * @return {ol.renderer.Layer} Layer renderer. + * @param {ol.Coordinate} source Source coordinate for the animation. + * @return {ol.PreRenderFunction} Pre-render function for kinetic animation. */ -ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) { - goog.asserts.assert(layerKey in this.layerRenderers_); - return this.layerRenderers_[layerKey]; +ol.Kinetic.prototype.pan = function(source) { + var decay = this.decay_; + var initialVelocity = this.initialVelocity_; + var velocity = this.minVelocity_ - initialVelocity; + var duration = this.getDuration_(); + var easingFunction = ( + /** + * @param {number} t T. + * @return {number} Easing. + */ + function(t) { + return initialVelocity * (Math.exp((decay * t) * duration) - 1) / + velocity; + }); + return ol.animation.pan({ + source: source, + duration: duration, + easing: easingFunction + }); }; /** - * @protected - * @return {Object.<number, ol.renderer.Layer>} Layer renderers. + * @private + * @return {number} Duration of animation (milliseconds). */ -ol.renderer.Map.prototype.getLayerRenderers = function() { - return this.layerRenderers_; +ol.Kinetic.prototype.getDuration_ = function() { + return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_; }; /** - * @return {ol.Map} Map. + * @return {number} Total distance travelled (pixels). */ -ol.renderer.Map.prototype.getMap = function() { - return this.map_; +ol.Kinetic.prototype.getDistance = function() { + return (this.minVelocity_ - this.initialVelocity_) / this.decay_; }; /** - * @return {string} Type - */ -ol.renderer.Map.prototype.getType = goog.abstractMethod; - - -/** - * @param {string} layerKey Layer key. - * @return {ol.renderer.Layer} Layer renderer. - * @private + * @return {number} Angle of the kinetic panning animation (radians). */ -ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) { - goog.asserts.assert(layerKey in this.layerRenderers_); - var layerRenderer = this.layerRenderers_[layerKey]; - delete this.layerRenderers_[layerKey]; - return layerRenderer; +ol.Kinetic.prototype.getAngle = function() { + return this.angle_; }; +// FIXME factor out key precondition (shift et. al) -/** - * Render. - * @param {?olx.FrameState} frameState Frame state. - */ -ol.renderer.Map.prototype.renderFrame = goog.nullFunction; - - -/** - * @param {ol.Map} map Map. - * @param {olx.FrameState} frameState Frame state. - * @private - */ -ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = - function(map, frameState) { - var layerKey; - for (layerKey in this.layerRenderers_) { - if (goog.isNull(frameState) || !(layerKey in frameState.layerStates)) { - goog.dispose(this.removeLayerRendererByKey_(layerKey)); - } - } -}; - +goog.provide('ol.interaction.Interaction'); +goog.provide('ol.interaction.InteractionProperty'); -/** - * @param {olx.FrameState} frameState Frame state. - * @protected - */ -ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) { - frameState.postRenderFunctions.push(ol.renderer.Map.expireIconCache_); -}; +goog.require('ol.MapBrowserEvent'); +goog.require('ol.Object'); +goog.require('ol.animation'); +goog.require('ol.easing'); /** - * @param {!olx.FrameState} frameState Frame state. - * @protected + * @enum {string} */ -ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = - function(frameState) { - var layerKey; - for (layerKey in this.layerRenderers_) { - if (!(layerKey in frameState.layerStates)) { - frameState.postRenderFunctions.push( - goog.bind(this.removeUnusedLayerRenderers_, this)); - return; - } - } +ol.interaction.InteractionProperty = { + ACTIVE: 'active' }; -goog.provide('ol.structs.PriorityQueue'); - -goog.require('goog.asserts'); -goog.require('goog.object'); - /** - * Priority queue. - * - * The implementation is inspired from the Closure Library's Heap class and - * Python's heapq module. - * - * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html - * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * User actions that change the state of the map. Some are similar to controls, + * but are not associated with a DOM element. + * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as + * {@link ol.control.Zoom}, but triggered by a keyboard event not a button + * element event. + * Although interactions do not have a DOM element, some of them do render + * vectors and so are visible on the screen. * * @constructor - * @param {function(T): number} priorityFunction Priority function. - * @param {function(T): string} keyFunction Key function. - * @struct - * @template T + * @param {olx.interaction.InteractionOptions} options Options. + * @extends {ol.Object} + * @api */ -ol.structs.PriorityQueue = function(priorityFunction, keyFunction) { - - /** - * @type {function(T): number} - * @private - */ - this.priorityFunction_ = priorityFunction; +ol.interaction.Interaction = function(options) { - /** - * @type {function(T): string} - * @private - */ - this.keyFunction_ = keyFunction; + goog.base(this); /** - * @type {Array.<T>} * @private + * @type {ol.Map} */ - this.elements_ = []; + this.map_ = null; - /** - * @type {Array.<number>} - * @private - */ - this.priorities_ = []; + this.setActive(true); /** - * @type {Object.<string, boolean>} - * @private + * @type {function(ol.MapBrowserEvent):boolean} */ - this.queuedElements_ = {}; + this.handleEvent = options.handleEvent; }; +goog.inherits(ol.interaction.Interaction, ol.Object); /** - * @const - * @type {number} + * Return whether the interaction is currently active. + * @return {boolean} `true` if the interaction is active, `false` otherwise. + * @observable + * @api */ -ol.structs.PriorityQueue.DROP = Infinity; +ol.interaction.Interaction.prototype.getActive = function() { + return /** @type {boolean} */ ( + this.get(ol.interaction.InteractionProperty.ACTIVE)); +}; /** - * FIXME empty desciption for jsdoc + * Get the map associated with this interaction. + * @return {ol.Map} Map. */ -ol.structs.PriorityQueue.prototype.assertValid = function() { - var elements = this.elements_; - var priorities = this.priorities_; - var n = elements.length; - goog.asserts.assert(priorities.length == n); - var i, priority; - for (i = 0; i < (n >> 1) - 1; ++i) { - priority = priorities[i]; - goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)]); - goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)]); - } +ol.interaction.Interaction.prototype.getMap = function() { + return this.map_; }; /** - * FIXME empty description for jsdoc + * Activate or deactivate the interaction. + * @param {boolean} active Active. + * @observable + * @api */ -ol.structs.PriorityQueue.prototype.clear = function() { - this.elements_.length = 0; - this.priorities_.length = 0; - goog.object.clear(this.queuedElements_); +ol.interaction.Interaction.prototype.setActive = function(active) { + this.set(ol.interaction.InteractionProperty.ACTIVE, active); }; /** - * Remove and return the highest-priority element. O(log N). - * @return {T} Element. + * Remove the interaction from its current map and attach it to the new map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {ol.Map} map Map. */ -ol.structs.PriorityQueue.prototype.dequeue = function() { - var elements = this.elements_; - goog.asserts.assert(elements.length > 0); - var priorities = this.priorities_; - var element = elements[0]; - if (elements.length == 1) { - elements.length = 0; - priorities.length = 0; - } else { - elements[0] = elements.pop(); - priorities[0] = priorities.pop(); - this.siftUp_(0); - } - var elementKey = this.keyFunction_(element); - goog.asserts.assert(elementKey in this.queuedElements_); - delete this.queuedElements_[elementKey]; - return element; +ol.interaction.Interaction.prototype.setMap = function(map) { + this.map_ = map; }; /** - * Enqueue an element. O(log N). - * @param {T} element Element. + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {ol.Coordinate} delta Delta. + * @param {number=} opt_duration Duration. */ -ol.structs.PriorityQueue.prototype.enqueue = function(element) { - goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_)); - var priority = this.priorityFunction_(element); - if (priority != ol.structs.PriorityQueue.DROP) { - this.elements_.push(element); - this.priorities_.push(priority); - this.queuedElements_[this.keyFunction_(element)] = true; - this.siftDown_(0, this.elements_.length - 1); +ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) { + var currentCenter = view.getCenter(); + if (goog.isDef(currentCenter)) { + if (goog.isDef(opt_duration) && opt_duration > 0) { + map.beforeRender(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: ol.easing.linear + })); + } + var center = view.constrainCenter( + [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]); + view.setCenter(center); } }; /** - * @return {number} Count. + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.structs.PriorityQueue.prototype.getCount = function() { - return this.elements_.length; +ol.interaction.Interaction.rotate = + function(map, view, rotation, opt_anchor, opt_duration) { + rotation = view.constrainRotation(rotation, 0); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation, opt_anchor, opt_duration); }; /** - * Gets the index of the left child of the node at the given index. - * @param {number} index The index of the node to get the left child for. - * @return {number} The index of the left child. - * @private + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) { - return index * 2 + 1; +ol.interaction.Interaction.rotateWithoutConstraints = + function(map, view, rotation, opt_anchor, opt_duration) { + if (goog.isDefAndNotNull(rotation)) { + var currentRotation = view.getRotation(); + var currentCenter = view.getCenter(); + if (goog.isDef(currentRotation) && goog.isDef(currentCenter) && + goog.isDef(opt_duration) && opt_duration > 0) { + map.beforeRender(ol.animation.rotate({ + rotation: currentRotation, + duration: opt_duration, + easing: ol.easing.easeOut + })); + if (goog.isDef(opt_anchor)) { + map.beforeRender(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: ol.easing.easeOut + })); + } + } + view.rotate(rotation, opt_anchor); + } }; /** - * Gets the index of the right child of the node at the given index. - * @param {number} index The index of the node to get the right child for. - * @return {number} The index of the right child. - * @private + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. + * @param {number=} opt_direction Zooming direction; > 0 indicates + * zooming out, in which case the constraints system will select + * the largest nearest resolution; < 0 indicates zooming in, in + * which case the constraints system will select the smallest + * nearest resolution; == 0 indicates that the zooming direction + * is unknown/not relevant, in which case the constraints system + * will select the nearest resolution. If not defined 0 is + * assumed. */ -ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) { - return index * 2 + 2; +ol.interaction.Interaction.zoom = + function(map, view, resolution, opt_anchor, opt_duration, opt_direction) { + resolution = view.constrainResolution(resolution, 0, opt_direction); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); }; /** - * Gets the index of the parent of the node at the given index. - * @param {number} index The index of the node to get the parent for. - * @return {number} The index of the parent. - * @private + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number} delta Delta from previous zoom level. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) { - return (index - 1) >> 1; +ol.interaction.Interaction.zoomByDelta = + function(map, view, delta, opt_anchor, opt_duration) { + var currentResolution = view.getResolution(); + var resolution = view.constrainResolution(currentResolution, delta, 0); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution, opt_anchor, opt_duration); }; /** - * Make this a heap. O(N). - * @private + * @param {ol.Map} map Map. + * @param {ol.View} view View. + * @param {number|undefined} resolution Resolution to go to. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.structs.PriorityQueue.prototype.heapify_ = function() { - var i; - for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) { - this.siftUp_(i); +ol.interaction.Interaction.zoomWithoutConstraints = + function(map, view, resolution, opt_anchor, opt_duration) { + if (goog.isDefAndNotNull(resolution)) { + var currentResolution = view.getResolution(); + var currentCenter = view.getCenter(); + if (goog.isDef(currentResolution) && goog.isDef(currentCenter) && + goog.isDef(opt_duration) && opt_duration > 0) { + map.beforeRender(ol.animation.zoom({ + resolution: currentResolution, + duration: opt_duration, + easing: ol.easing.easeOut + })); + if (goog.isDef(opt_anchor)) { + map.beforeRender(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: ol.easing.easeOut + })); + } + } + if (goog.isDefAndNotNull(opt_anchor)) { + var center = view.calculateCenterZoom(resolution, opt_anchor); + view.setCenter(center); + } + view.setResolution(resolution); } }; +goog.provide('ol.interaction.DoubleClickZoom'); + +goog.require('goog.asserts'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.interaction.Interaction'); -/** - * @return {boolean} Is empty. - */ -ol.structs.PriorityQueue.prototype.isEmpty = function() { - return this.elements_.length === 0; -}; /** - * @param {string} key Key. - * @return {boolean} Is key queued. + * @classdesc + * Allows the user to zoom by double-clicking on the map. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options. + * @api stable */ -ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) { - return key in this.queuedElements_; +ol.interaction.DoubleClickZoom = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + /** + * @private + * @type {number} + */ + this.delta_ = goog.isDef(options.delta) ? options.delta : 1; + + goog.base(this, { + handleEvent: ol.interaction.DoubleClickZoom.handleEvent + }); + + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 250; + }; +goog.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction); /** - * @param {T} element Element. - * @return {boolean} Is queued. + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * doubleclick) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DoubleClickZoom} + * @api */ -ol.structs.PriorityQueue.prototype.isQueued = function(element) { - return this.isKeyQueued(this.keyFunction_(element)); +ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + var browserEvent = mapBrowserEvent.browserEvent; + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) { + var map = mapBrowserEvent.map; + var anchor = mapBrowserEvent.coordinate; + var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_; + var view = map.getView(); + goog.asserts.assert(!goog.isNull(view), 'view should not be null'); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, anchor, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + return !stopEvent; }; +goog.provide('ol.events.ConditionType'); +goog.provide('ol.events.condition'); + +goog.require('goog.asserts'); +goog.require('goog.dom.TagName'); +goog.require('goog.functions'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.MapBrowserPointerEvent'); + /** - * @param {number} index The index of the node to move down. - * @private + * A function that takes an {@link ol.MapBrowserEvent} and returns a + * `{boolean}`. If the condition is met, true should be returned. + * + * @typedef {function(ol.MapBrowserEvent): boolean} + * @api stable */ -ol.structs.PriorityQueue.prototype.siftUp_ = function(index) { - var elements = this.elements_; - var priorities = this.priorities_; - var count = elements.length; - var element = elements[index]; - var priority = priorities[index]; - var startIndex = index; +ol.events.ConditionType; - while (index < (count >> 1)) { - var lIndex = this.getLeftChildIndex_(index); - var rIndex = this.getRightChildIndex_(index); - var smallerChildIndex = rIndex < count && - priorities[rIndex] < priorities[lIndex] ? - rIndex : lIndex; +/** + * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when + * additionally the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt key is pressed. + * @api stable + */ +ol.events.condition.altKeyOnly = function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent; + return ( + browserEvent.altKey && + !browserEvent.platformModifierKey && + !browserEvent.shiftKey); +}; - elements[index] = elements[smallerChildIndex]; - priorities[index] = priorities[smallerChildIndex]; - index = smallerChildIndex; - } - elements[index] = element; - priorities[index] = priority; - this.siftDown_(startIndex, index); +/** + * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise + * (e.g. when additionally the platform-modifier-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the alt and shift keys are pressed. + * @api stable + */ +ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent; + return ( + browserEvent.altKey && + !browserEvent.platformModifierKey && + browserEvent.shiftKey); }; /** - * @param {number} startIndex The index of the root. - * @param {number} index The index of the node to move up. - * @private + * Return always true. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True. + * @function + * @api stable */ -ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) { - var elements = this.elements_; - var priorities = this.priorities_; - var element = elements[index]; - var priority = priorities[index]; - - while (index > startIndex) { - var parentIndex = this.getParentIndex_(index); - if (priorities[parentIndex] > priority) { - elements[index] = elements[parentIndex]; - priorities[index] = priorities[parentIndex]; - index = parentIndex; - } else { - break; - } - } - elements[index] = element; - priorities[index] = priority; -}; +ol.events.condition.always = goog.functions.TRUE; /** - * FIXME empty description for jsdoc + * Return `true` if the event is a `click` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `click` event. + * @api stable */ -ol.structs.PriorityQueue.prototype.reprioritize = function() { - var priorityFunction = this.priorityFunction_; - var elements = this.elements_; - var priorities = this.priorities_; - var index = 0; - var n = elements.length; - var element, i, priority; - for (i = 0; i < n; ++i) { - element = elements[i]; - priority = priorityFunction(element); - if (priority == ol.structs.PriorityQueue.DROP) { - delete this.queuedElements_[this.keyFunction_(element)]; - } else { - priorities[index] = priority; - elements[index++] = element; - } - } - elements.length = index; - priorities.length = index; - this.heapify_(); +ol.events.condition.click = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK; }; -goog.provide('ol.TilePriorityFunction'); -goog.provide('ol.TileQueue'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.Coordinate'); -goog.require('ol.structs.PriorityQueue'); +/** + * Return always false. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} False. + * @function + * @api stable + */ +ol.events.condition.never = goog.functions.FALSE; /** - * @typedef {function(ol.Tile, string, ol.Coordinate, number): number} + * Return `true` if the browser event is a `pointermove` event, `false` + * otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the browser event is a `pointermove` event. + * @api */ -ol.TilePriorityFunction; +ol.events.condition.pointerMove = function(mapBrowserEvent) { + return mapBrowserEvent.type == 'pointermove'; +}; + +/** + * Return `true` if the event is a map `singleclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `singleclick` event. + * @api stable + */ +ol.events.condition.singleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK; +}; /** - * @constructor - * @extends {ol.structs.PriorityQueue.<Array>} - * @param {ol.TilePriorityFunction} tilePriorityFunction - * Tile priority function. - * @param {function(): ?} tileChangeCallback - * Function called on each tile change event. - * @struct + * Return `true` if the event is a map `dblclick` event, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event is a map `dblclick` event. + * @api stable */ -ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) { +ol.events.condition.doubleClick = function(mapBrowserEvent) { + return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK; +}; - goog.base( - this, - /** - * @param {Array} element Element. - * @return {number} Priority. - */ - function(element) { - return tilePriorityFunction.apply(null, element); - }, - /** - * @param {Array} element Element. - * @return {string} Key. - */ - function(element) { - return /** @type {ol.Tile} */ (element[0]).getKey(); - }); - /** - * @private - * @type {function(): ?} - */ - this.tileChangeCallback_ = tileChangeCallback; +/** + * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is + * pressed. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if there no modifier keys are pressed. + * @api stable + */ +ol.events.condition.noModifierKeys = function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent; + return ( + !browserEvent.altKey && + !browserEvent.platformModifierKey && + !browserEvent.shiftKey); +}; - /** - * @private - * @type {number} - */ - this.tilesLoading_ = 0; +/** + * Return `true` if only the platform-modifier-key (e.g. the windows-key) is + * pressed, `false` otherwise (e.g. when additionally the shift-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the platform modifier key is pressed. + * @api stable + */ +ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent; + return ( + !browserEvent.altKey && + browserEvent.platformModifierKey && + !browserEvent.shiftKey); }; -goog.inherits(ol.TileQueue, ol.structs.PriorityQueue); /** - * @return {number} Number of tiles loading. + * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when + * additionally the alt-key is pressed). + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if only the shift key is pressed. + * @api stable */ -ol.TileQueue.prototype.getTilesLoading = function() { - return this.tilesLoading_; +ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent; + return ( + !browserEvent.altKey && + !browserEvent.platformModifierKey && + browserEvent.shiftKey); }; /** - * @protected + * Return `true` if the target element is not editable, i.e. not a `<input>`-, + * `<select>`- or `<textarea>`-element, `false` otherwise. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True only if the target element is not editable. + * @api */ -ol.TileQueue.prototype.handleTileChange = function() { - --this.tilesLoading_; - this.tileChangeCallback_(); +ol.events.condition.targetNotEditable = function(mapBrowserEvent) { + var target = mapBrowserEvent.browserEvent.target; + goog.asserts.assertInstanceof(target, Element, + 'target should be an Element'); + var tagName = target.tagName; + return ( + tagName !== goog.dom.TagName.INPUT && + tagName !== goog.dom.TagName.SELECT && + tagName !== goog.dom.TagName.TEXTAREA); }; /** - * @param {number} maxTotalLoading Maximum number tiles to load simultaneously. - * @param {number} maxNewLoads Maximum number of new tiles to load. + * Return `true` if the event originates from a mouse device. + * + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} True if the event originates from a mouse device. + * @api stable */ -ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) { - var newLoads = Math.min( - maxTotalLoading - this.getTilesLoading(), maxNewLoads, this.getCount()); - var i, tile; - for (i = 0; i < newLoads; ++i) { - tile = /** @type {ol.Tile} */ (this.dequeue()[0]); - goog.events.listenOnce(tile, goog.events.EventType.CHANGE, - this.handleTileChange, false, this); - tile.load(); - } - this.tilesLoading_ += newLoads; +ol.events.condition.mouseOnly = function(mapBrowserEvent) { + goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent, + 'mapBrowserEvent should be an instance of ol.MapBrowserPointerEvent'); + /* pointerId must be 1 for mouse devices, + * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface + */ + return mapBrowserEvent.pointerEvent.pointerId == 1; }; -goog.provide('ol.Kinetic'); +goog.provide('ol.interaction.Pointer'); -goog.require('ol.Coordinate'); -goog.require('ol.PreRenderFunction'); -goog.require('ol.animation'); +goog.require('goog.functions'); +goog.require('goog.object'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.MapBrowserPointerEvent'); +goog.require('ol.Pixel'); +goog.require('ol.interaction.Interaction'); /** * @classdesc - * Implementation of inertial deceleration for map movement. + * Base class that calls user-defined functions on `down`, `move` and `up` + * events. This class also manages "drag sequences". + * + * When the `handleDownEvent` user function returns `true` a drag sequence is + * started. During a drag sequence the `handleDragEvent` user function is + * called on `move` events. The drag sequence ends when the `handleUpEvent` + * user function is called and returns `false`. * * @constructor - * @param {number} decay Rate of decay (must be negative). - * @param {number} minVelocity Minimum velocity (pixels/millisecond). - * @param {number} delay Delay to consider to calculate the kinetic - * initial values (milliseconds). - * @struct + * @param {olx.interaction.PointerOptions=} opt_options Options. + * @extends {ol.interaction.Interaction} * @api */ -ol.Kinetic = function(decay, minVelocity, delay) { +ol.interaction.Pointer = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + var handleEvent = goog.isDef(options.handleEvent) ? + options.handleEvent : ol.interaction.Pointer.handleEvent; + + goog.base(this, { + handleEvent: handleEvent + }); /** + * @type {function(ol.MapBrowserPointerEvent):boolean} * @private - * @type {number} */ - this.decay_ = decay; + this.handleDownEvent_ = goog.isDef(options.handleDownEvent) ? + options.handleDownEvent : ol.interaction.Pointer.handleDownEvent; /** + * @type {function(ol.MapBrowserPointerEvent)} * @private - * @type {number} */ - this.minVelocity_ = minVelocity; + this.handleDragEvent_ = goog.isDef(options.handleDragEvent) ? + options.handleDragEvent : ol.interaction.Pointer.handleDragEvent; /** + * @type {function(ol.MapBrowserPointerEvent)} * @private - * @type {number} */ - this.delay_ = delay; + this.handleMoveEvent_ = goog.isDef(options.handleMoveEvent) ? + options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent; /** + * @type {function(ol.MapBrowserPointerEvent):boolean} * @private - * @type {Array.<number>} */ - this.points_ = []; + this.handleUpEvent_ = goog.isDef(options.handleUpEvent) ? + options.handleUpEvent : ol.interaction.Pointer.handleUpEvent; /** - * @private - * @type {number} + * @type {boolean} + * @protected */ - this.angle_ = 0; + this.handlingDownUpSequence = false; /** + * @type {Object.<number, ol.pointer.PointerEvent>} * @private - * @type {number} */ - this.initialVelocity_ = 0; + this.trackedPointers_ = {}; + + /** + * @type {Array.<ol.pointer.PointerEvent>} + * @protected + */ + this.targetPointers = []; + }; +goog.inherits(ol.interaction.Pointer, ol.interaction.Interaction); /** - * FIXME empty description for jsdoc + * @param {Array.<ol.pointer.PointerEvent>} pointerEvents + * @return {ol.Pixel} Centroid pixel. */ -ol.Kinetic.prototype.begin = function() { - this.points_.length = 0; - this.angle_ = 0; - this.initialVelocity_ = 0; +ol.interaction.Pointer.centroid = function(pointerEvents) { + var length = pointerEvents.length; + var clientX = 0; + var clientY = 0; + for (var i = 0; i < length; i++) { + clientX += pointerEvents[i].clientX; + clientY += pointerEvents[i].clientY; + } + return [clientX / length, clientY / length]; }; /** - * @param {number} x X. - * @param {number} y Y. - */ -ol.Kinetic.prototype.update = function(x, y) { - this.points_.push(x, y, goog.now()); + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Whether the event is a pointerdown, pointerdrag + * or pointerup event. + * @private + */ +ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = + function(mapBrowserEvent) { + var type = mapBrowserEvent.type; + return ( + type === ol.MapBrowserEvent.EventType.POINTERDOWN || + type === ol.MapBrowserEvent.EventType.POINTERDRAG || + type === ol.MapBrowserEvent.EventType.POINTERUP); }; /** - * @return {boolean} Whether we should do kinetic animation. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @private */ -ol.Kinetic.prototype.end = function() { - if (this.points_.length < 6) { - // at least 2 points are required (i.e. there must be at least 6 elements - // in the array) - return false; - } - var delay = goog.now() - this.delay_; - var lastIndex = this.points_.length - 3; - if (this.points_[lastIndex + 2] < delay) { - // the last tracked point is too old, which means that the user stopped - // panning before releasing the map - return false; - } +ol.interaction.Pointer.prototype.updateTrackedPointers_ = + function(mapBrowserEvent) { + if (this.isPointerDraggingEvent_(mapBrowserEvent)) { + var event = mapBrowserEvent.pointerEvent; - // get the first point which still falls into the delay time - var firstIndex = lastIndex - 3; - while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) { - firstIndex -= 3; + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { + delete this.trackedPointers_[event.pointerId]; + } else if (mapBrowserEvent.type == + ol.MapBrowserEvent.EventType.POINTERDOWN) { + this.trackedPointers_[event.pointerId] = event; + } else if (event.pointerId in this.trackedPointers_) { + // update only when there was a pointerdown event for this pointer + this.trackedPointers_[event.pointerId] = event; + } + this.targetPointers = goog.object.getValues(this.trackedPointers_); } - var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2]; - var dx = this.points_[lastIndex] - this.points_[firstIndex]; - var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1]; - this.angle_ = Math.atan2(dy, dx); - this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration; - return this.initialVelocity_ > this.minVelocity_; }; /** - * @param {ol.Coordinate} source Source coordinate for the animation. - * @return {ol.PreRenderFunction} Pre-render function for kinetic animation. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.Pointer} */ -ol.Kinetic.prototype.pan = function(source) { - var decay = this.decay_; - var initialVelocity = this.initialVelocity_; - var velocity = this.minVelocity_ - initialVelocity; - var duration = this.getDuration_(); - var easingFunction = ( - /** - * @param {number} t T. - * @return {number} Easing. - */ - function(t) { - return initialVelocity * (Math.exp((decay * t) * duration) - 1) / - velocity; - }); - return ol.animation.pan({ - source: source, - duration: duration, - easing: easingFunction - }); -}; +ol.interaction.Pointer.handleDragEvent = goog.nullFunction; /** - * @private - * @return {number} Duration of animation (milliseconds). + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Capture dragging. + * @this {ol.interaction.Pointer} */ -ol.Kinetic.prototype.getDuration_ = function() { - return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_; -}; +ol.interaction.Pointer.handleUpEvent = goog.functions.FALSE; /** - * @return {number} Total distance travelled (pixels). + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Capture dragging. + * @this {ol.interaction.Pointer} */ -ol.Kinetic.prototype.getDistance = function() { - return (this.minVelocity_ - this.initialVelocity_) / this.decay_; -}; +ol.interaction.Pointer.handleDownEvent = goog.functions.FALSE; /** - * @return {number} Angle of the kinetic panning animation (radians). + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.Pointer} */ -ol.Kinetic.prototype.getAngle = function() { - return this.angle_; -}; +ol.interaction.Pointer.handleMoveEvent = goog.nullFunction; -// FIXME factor out key precondition (shift et. al) -goog.provide('ol.interaction.Interaction'); -goog.provide('ol.interaction.InteractionProperty'); +/** + * Handles the {@link ol.MapBrowserEvent map browser event} and may call into + * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are + * detected. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Pointer} + * @api + */ +ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { + return true; + } -goog.require('goog.asserts'); -goog.require('ol.MapBrowserEvent'); -goog.require('ol.Object'); -goog.require('ol.animation'); -goog.require('ol.easing'); + var stopEvent = false; + this.updateTrackedPointers_(mapBrowserEvent); + if (this.handlingDownUpSequence) { + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDRAG) { + this.handleDragEvent_(mapBrowserEvent); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { + this.handlingDownUpSequence = this.handleUpEvent_(mapBrowserEvent); + } + } + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { + var handled = this.handleDownEvent_(mapBrowserEvent); + this.handlingDownUpSequence = handled; + stopEvent = this.shouldStopEvent(handled); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) { + this.handleMoveEvent_(mapBrowserEvent); + } + return !stopEvent; +}; /** - * @enum {string} + * This method is used to determine if "down" events should be propagated to + * other interactions or should be stopped. + * + * The method receives the return code of the "handleDownEvent" function. + * + * By default this function is the "identity" function. It's overidden in + * child classes. + * + * @param {boolean} handled Was the event handled by the interaction? + * @return {boolean} Should the event be stopped? + * @protected */ -ol.interaction.InteractionProperty = { - ACTIVE: 'active' -}; +ol.interaction.Pointer.prototype.shouldStopEvent = goog.functions.identity; + +goog.provide('ol.interaction.DragPan'); + +goog.require('goog.asserts'); +goog.require('ol.Kinetic'); +goog.require('ol.Pixel'); +goog.require('ol.PreRenderFunction'); +goog.require('ol.ViewHint'); +goog.require('ol.coordinate'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Pointer'); /** * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * User actions that change the state of the map. Some are similar to controls, - * but are not associated with a DOM element. - * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as - * {@link ol.control.Zoom}, but triggered by a keyboard event not a button - * element event. - * Although interactions do not have a DOM element, some of them do render - * vectors and so are visible on the screen. + * Allows the user to pan the map by dragging the map. * * @constructor - * @param {olx.interaction.InteractionOptions} options Options. - * @extends {ol.Object} - * @api + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragPanOptions=} opt_options Options. + * @api stable */ -ol.interaction.Interaction = function(options) { +ol.interaction.DragPan = function(opt_options) { - goog.base(this); + goog.base(this, { + handleDownEvent: ol.interaction.DragPan.handleDownEvent_, + handleDragEvent: ol.interaction.DragPan.handleDragEvent_, + handleUpEvent: ol.interaction.DragPan.handleUpEvent_ + }); + + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {ol.Map} + * @type {ol.Kinetic|undefined} */ - this.map_ = null; - - this.setActive(true); + this.kinetic_ = options.kinetic; /** - * @type {function(ol.MapBrowserEvent):boolean} + * @private + * @type {?ol.PreRenderFunction} */ - this.handleEvent = options.handleEvent; - -}; -goog.inherits(ol.interaction.Interaction, ol.Object); - - -/** - * @return {boolean} `true` if the interaction is active, `false` otherwise. - * @observable - * @api - */ -ol.interaction.Interaction.prototype.getActive = function() { - return /** @type {boolean} */ ( - this.get(ol.interaction.InteractionProperty.ACTIVE)); -}; -goog.exportProperty( - ol.interaction.Interaction.prototype, - 'getActive', - ol.interaction.Interaction.prototype.getActive); - - -/** - * Get the map associated with this interaction. - * @return {ol.Map} Map. - */ -ol.interaction.Interaction.prototype.getMap = function() { - return this.map_; -}; + this.kineticPreRenderFn_ = null; + /** + * @type {ol.Pixel} + */ + this.lastCentroid = null; -/** - * Activate or deactivate the interaction. - * @param {boolean} active Active. - * @observable - * @api - */ -ol.interaction.Interaction.prototype.setActive = function(active) { - this.set(ol.interaction.InteractionProperty.ACTIVE, active); -}; -goog.exportProperty( - ol.interaction.Interaction.prototype, - 'setActive', - ol.interaction.Interaction.prototype.setActive); + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.noModifierKeys; + /** + * @private + * @type {boolean} + */ + this.noKinetic_ = false; -/** - * Remove the interaction from its current map and attach it to the new map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {ol.Map} map Map. - */ -ol.interaction.Interaction.prototype.setMap = function(map) { - this.map_ = map; }; +goog.inherits(ol.interaction.DragPan, ol.interaction.Pointer); /** - * @param {ol.Map} map Map. - * @param {ol.View} view View. - * @param {ol.Coordinate} delta Delta. - * @param {number=} opt_duration Duration. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragPan} + * @private */ -ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) { - var currentCenter = view.getCenter(); - if (goog.isDef(currentCenter)) { - if (goog.isDef(opt_duration) && opt_duration > 0) { - map.beforeRender(ol.animation.pan({ - source: currentCenter, - duration: opt_duration, - easing: ol.easing.linear - })); - } - var center = view.constrainCenter( - [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]); +ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) { + goog.asserts.assert(this.targetPointers.length >= 1, + 'the length of this.targetPointers should be more than 1'); + var centroid = + ol.interaction.Pointer.centroid(this.targetPointers); + if (this.kinetic_) { + this.kinetic_.update(centroid[0], centroid[1]); + } + if (!goog.isNull(this.lastCentroid)) { + var deltaX = this.lastCentroid[0] - centroid[0]; + var deltaY = centroid[1] - this.lastCentroid[1]; + var map = mapBrowserEvent.map; + var view = map.getView(); + var viewState = view.getState(); + var center = [deltaX, deltaY]; + ol.coordinate.scale(center, viewState.resolution); + ol.coordinate.rotate(center, viewState.rotation); + ol.coordinate.add(center, viewState.center); + center = view.constrainCenter(center); + map.render(); view.setCenter(center); } + this.lastCentroid = centroid; }; /** - * @param {ol.Map} map Map. - * @param {ol.View} view View. - * @param {number|undefined} rotation Rotation. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. - */ -ol.interaction.Interaction.rotate = - function(map, view, rotation, opt_anchor, opt_duration) { - rotation = view.constrainRotation(rotation, 0); - ol.interaction.Interaction.rotateWithoutConstraints( - map, view, rotation, opt_anchor, opt_duration); -}; - - -/** - * @param {ol.Map} map Map. - * @param {ol.View} view View. - * @param {number|undefined} rotation Rotation. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragPan} + * @private */ -ol.interaction.Interaction.rotateWithoutConstraints = - function(map, view, rotation, opt_anchor, opt_duration) { - if (goog.isDefAndNotNull(rotation)) { - var currentRotation = view.getRotation(); - var currentCenter = view.getCenter(); - if (goog.isDef(currentRotation) && goog.isDef(currentCenter) && - goog.isDef(opt_duration) && opt_duration > 0) { - map.beforeRender(ol.animation.rotate({ - rotation: currentRotation, - duration: opt_duration, - easing: ol.easing.easeOut - })); - if (goog.isDef(opt_anchor)) { - map.beforeRender(ol.animation.pan({ - source: currentCenter, - duration: opt_duration, - easing: ol.easing.easeOut - })); - } +ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) { + var map = mapBrowserEvent.map; + var view = map.getView(); + if (this.targetPointers.length === 0) { + if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) { + var distance = this.kinetic_.getDistance(); + var angle = this.kinetic_.getAngle(); + var center = view.getCenter(); + goog.asserts.assert(goog.isDef(center), 'center should be defined'); + this.kineticPreRenderFn_ = this.kinetic_.pan(center); + map.beforeRender(this.kineticPreRenderFn_); + var centerpx = map.getPixelFromCoordinate(center); + var dest = map.getCoordinateFromPixel([ + centerpx[0] - distance * Math.cos(angle), + centerpx[1] - distance * Math.sin(angle) + ]); + dest = view.constrainCenter(dest); + view.setCenter(dest); } - view.rotate(rotation, opt_anchor); + view.setHint(ol.ViewHint.INTERACTING, -1); + map.render(); + return false; + } else { + this.lastCentroid = null; + return true; } }; /** - * @param {ol.Map} map Map. - * @param {ol.View} view View. - * @param {number|undefined} resolution Resolution to go to. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. - * @param {number=} opt_direction Zooming direction; > 0 indicates - * zooming out, in which case the constraints system will select - * the largest nearest resolution; < 0 indicates zooming in, in - * which case the constraints system will select the smallest - * nearest resolution; == 0 indicates that the zooming direction - * is unknown/not relevant, in which case the constraints system - * will select the nearest resolution. If not defined 0 is - * assumed. - */ -ol.interaction.Interaction.zoom = - function(map, view, resolution, opt_anchor, opt_duration, opt_direction) { - resolution = view.constrainResolution(resolution, 0, opt_direction); - ol.interaction.Interaction.zoomWithoutConstraints( - map, view, resolution, opt_anchor, opt_duration); -}; - - -/** - * @param {ol.Map} map Map. - * @param {ol.View} view View. - * @param {number} delta Delta from previous zoom level. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragPan} + * @private */ -ol.interaction.Interaction.zoomByDelta = - function(map, view, delta, opt_anchor, opt_duration) { - var currentResolution = view.getResolution(); - var resolution = view.constrainResolution(currentResolution, delta, 0); - ol.interaction.Interaction.zoomWithoutConstraints( - map, view, resolution, opt_anchor, opt_duration); +ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) { + var map = mapBrowserEvent.map; + var view = map.getView(); + this.lastCentroid = null; + if (!this.handlingDownUpSequence) { + view.setHint(ol.ViewHint.INTERACTING, 1); + } + map.render(); + if (!goog.isNull(this.kineticPreRenderFn_) && + map.removePreRenderFunction(this.kineticPreRenderFn_)) { + view.setCenter(mapBrowserEvent.frameState.viewState.center); + this.kineticPreRenderFn_ = null; + } + if (this.kinetic_) { + this.kinetic_.begin(); + } + // No kinetic as soon as more than one pointer on the screen is + // detected. This is to prevent nasty pans after pinch. + this.noKinetic_ = this.targetPointers.length > 1; + return true; + } else { + return false; + } }; /** - * @param {ol.Map} map Map. - * @param {ol.View} view View. - * @param {number|undefined} resolution Resolution to go to. - * @param {ol.Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. + * @inheritDoc */ -ol.interaction.Interaction.zoomWithoutConstraints = - function(map, view, resolution, opt_anchor, opt_duration) { - if (goog.isDefAndNotNull(resolution)) { - var currentResolution = view.getResolution(); - var currentCenter = view.getCenter(); - if (goog.isDef(currentResolution) && goog.isDef(currentCenter) && - goog.isDef(opt_duration) && opt_duration > 0) { - map.beforeRender(ol.animation.zoom({ - resolution: currentResolution, - duration: opt_duration, - easing: ol.easing.easeOut - })); - if (goog.isDef(opt_anchor)) { - map.beforeRender(ol.animation.pan({ - source: currentCenter, - duration: opt_duration, - easing: ol.easing.easeOut - })); - } - } - if (goog.isDefAndNotNull(opt_anchor)) { - var center = view.calculateCenterZoom(resolution, opt_anchor); - view.setCenter(center); - } - view.setResolution(resolution); - } -}; +ol.interaction.DragPan.prototype.shouldStopEvent = goog.functions.FALSE; -goog.provide('ol.interaction.DoubleClickZoom'); +goog.provide('ol.interaction.DragRotate'); -goog.require('goog.asserts'); -goog.require('ol.MapBrowserEvent'); -goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.ViewHint'); +goog.require('ol.events.ConditionType'); +goog.require('ol.events.condition'); goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); /** * @classdesc - * Allows the user to zoom by double-clicking on the map. + * Allows the user to rotate the map by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when the alt and shift keys are held down. + * + * This interaction is only supported for mouse devices. * * @constructor - * @extends {ol.interaction.Interaction} - * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options. + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragRotateOptions=} opt_options Options. * @api stable */ -ol.interaction.DoubleClickZoom = function(opt_options) { +ol.interaction.DragRotate = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; + goog.base(this, { + handleDownEvent: ol.interaction.DragRotate.handleDownEvent_, + handleDragEvent: ol.interaction.DragRotate.handleDragEvent_, + handleUpEvent: ol.interaction.DragRotate.handleUpEvent_ + }); + /** * @private - * @type {number} + * @type {ol.events.ConditionType} */ - this.delta_ = goog.isDef(options.delta) ? options.delta : 1; + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.altShiftKeysOnly; - goog.base(this, { - handleEvent: ol.interaction.DoubleClickZoom.handleEvent - }); + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; /** * @private * @type {number} */ this.duration_ = goog.isDef(options.duration) ? options.duration : 250; - }; -goog.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction); +goog.inherits(ol.interaction.DragRotate, ol.interaction.Pointer); /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.DoubleClickZoom} - * @api - */ -ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) { - var stopEvent = false; - var browserEvent = mapBrowserEvent.browserEvent; - if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) { - var map = mapBrowserEvent.map; - var anchor = mapBrowserEvent.coordinate; - var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_; + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragRotate} + * @private + */ +ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + var map = mapBrowserEvent.map; + var size = map.getSize(); + var offset = mapBrowserEvent.pixel; + var theta = + Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2); + if (goog.isDef(this.lastAngle_)) { + var delta = theta - this.lastAngle_; var view = map.getView(); - goog.asserts.assert(!goog.isNull(view)); - ol.interaction.Interaction.zoomByDelta( - map, view, delta, anchor, this.duration_); - mapBrowserEvent.preventDefault(); - stopEvent = true; + var rotation = view.getRotation(); + map.render(); + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, rotation - delta); } - return !stopEvent; + this.lastAngle_ = theta; }; -goog.provide('ol.events.ConditionType'); -goog.provide('ol.events.condition'); - -goog.require('goog.asserts'); -goog.require('goog.dom.TagName'); -goog.require('goog.functions'); -goog.require('ol.MapBrowserEvent.EventType'); -goog.require('ol.MapBrowserPointerEvent'); - /** - * A function that takes an {@link ol.MapBrowserEvent} and returns a - * `{boolean}`. If the condition is met, true should be returned. - * - * @typedef {function(ol.MapBrowserEvent): boolean} - * @api stable + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragRotate} + * @private */ -ol.events.ConditionType; +ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.ViewHint.INTERACTING, -1); + var rotation = view.getRotation(); + ol.interaction.Interaction.rotate(map, view, rotation, + undefined, this.duration_); + return false; +}; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if only the alt key is pressed. - * @api stable + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragRotate} + * @private */ -ol.events.condition.altKeyOnly = function(mapBrowserEvent) { +ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + var browserEvent = mapBrowserEvent.browserEvent; - return ( - browserEvent.altKey && - !browserEvent.platformModifierKey && - !browserEvent.shiftKey); + if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) { + var map = mapBrowserEvent.map; + map.getView().setHint(ol.ViewHint.INTERACTING, 1); + map.render(); + this.lastAngle_ = undefined; + return true; + } else { + return false; + } }; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if only the alt and shift keys are pressed. - * @api stable + * @inheritDoc */ -ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) { - var browserEvent = mapBrowserEvent.browserEvent; - return ( - browserEvent.altKey && - !browserEvent.platformModifierKey && - browserEvent.shiftKey); -}; +ol.interaction.DragRotate.prototype.shouldStopEvent = goog.functions.FALSE; + +// FIXME add rotation + +goog.provide('ol.render.Box'); + +goog.require('goog.Disposable'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('ol.geom.Polygon'); +goog.require('ol.render.EventType'); + /** - * Always true. - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True. - * @function - * @api stable + * @constructor + * @extends {goog.Disposable} + * @param {ol.style.Style} style Style. */ -ol.events.condition.always = goog.functions.TRUE; +ol.render.Box = function(style) { + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @private + * @type {goog.events.Key} + */ + this.postComposeListenerKey_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.startPixel_ = null; + + /** + * @private + * @type {ol.Pixel} + */ + this.endPixel_ = null; + + /** + * @private + * @type {ol.geom.Polygon} + */ + this.geometry_ = null; + + /** + * @private + * @type {ol.style.Style} + */ + this.style_ = style; + +}; +goog.inherits(ol.render.Box, goog.Disposable); /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if the event is a map `click` event. - * @api stable + * @private + * @return {ol.geom.Polygon} Geometry. */ -ol.events.condition.click = function(mapBrowserEvent) { - return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK; +ol.render.Box.prototype.createGeometry_ = function() { + goog.asserts.assert(!goog.isNull(this.startPixel_), + 'this.startPixel_ should not be null'); + goog.asserts.assert(!goog.isNull(this.endPixel_), + 'this.endPixel_ should not be null'); + goog.asserts.assert(!goog.isNull(this.map_), 'this.map_ should not be null'); + var startPixel = this.startPixel_; + var endPixel = this.endPixel_; + var pixels = [ + startPixel, + [startPixel[0], endPixel[1]], + endPixel, + [endPixel[0], startPixel[1]] + ]; + var coordinates = goog.array.map(pixels, + this.map_.getCoordinateFromPixel, this.map_); + // close the polygon + coordinates[4] = coordinates[0].slice(); + return new ol.geom.Polygon([coordinates]); }; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if the browser event is a `mousemove` event. - * @api + * @inheritDoc */ -ol.events.condition.mouseMove = function(mapBrowserEvent) { - return mapBrowserEvent.originalEvent.type == 'mousemove'; +ol.render.Box.prototype.disposeInternal = function() { + this.setMap(null); }; /** - * Always false. - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} False. - * @function - * @api stable + * @param {ol.render.Event} event Event. + * @private */ -ol.events.condition.never = goog.functions.FALSE; +ol.render.Box.prototype.handleMapPostCompose_ = function(event) { + var geometry = this.geometry_; + goog.asserts.assert(goog.isDefAndNotNull(geometry), + 'geometry should be defined'); + var style = this.style_; + goog.asserts.assert(!goog.isNull(style), 'style should not be null'); + // use drawAsync(Infinity) to draw above everything + event.vectorContext.drawAsync(Infinity, function(render) { + render.setFillStrokeStyle(style.getFill(), style.getStroke()); + render.setTextStyle(style.getText()); + render.drawPolygonGeometry(geometry, null); + }); +}; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if the event is a map `singleclick` event. - * @api stable + * @return {ol.geom.Polygon} Geometry. */ -ol.events.condition.singleClick = function(mapBrowserEvent) { - return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK; +ol.render.Box.prototype.getGeometry = function() { + return this.geometry_; }; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True only if there no modifier keys are pressed. - * @api stable + * @private */ -ol.events.condition.noModifierKeys = function(mapBrowserEvent) { - var browserEvent = mapBrowserEvent.browserEvent; - return ( - !browserEvent.altKey && - !browserEvent.platformModifierKey && - !browserEvent.shiftKey); +ol.render.Box.prototype.requestMapRenderFrame_ = function() { + if (!goog.isNull(this.map_) && + !goog.isNull(this.startPixel_) && + !goog.isNull(this.endPixel_)) { + this.map_.render(); + } }; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if only the platform modifier key is pressed. - * @api stable + * @param {ol.Map} map Map. */ -ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) { - var browserEvent = mapBrowserEvent.browserEvent; - return ( - !browserEvent.altKey && - browserEvent.platformModifierKey && - !browserEvent.shiftKey); +ol.render.Box.prototype.setMap = function(map) { + if (!goog.isNull(this.postComposeListenerKey_)) { + goog.events.unlistenByKey(this.postComposeListenerKey_); + this.postComposeListenerKey_ = null; + this.map_.render(); + this.map_ = null; + } + this.map_ = map; + if (!goog.isNull(this.map_)) { + this.postComposeListenerKey_ = goog.events.listen( + map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false, + this); + this.requestMapRenderFrame_(); + } }; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if only the shift key is pressed. - * @api stable + * @param {ol.Pixel} startPixel Start pixel. + * @param {ol.Pixel} endPixel End pixel. */ -ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) { - var browserEvent = mapBrowserEvent.browserEvent; - return ( - !browserEvent.altKey && - !browserEvent.platformModifierKey && - browserEvent.shiftKey); +ol.render.Box.prototype.setPixels = function(startPixel, endPixel) { + this.startPixel_ = startPixel; + this.endPixel_ = endPixel; + this.geometry_ = this.createGeometry_(); + this.requestMapRenderFrame_(); }; +// FIXME draw drag box +goog.provide('ol.DragBoxEvent'); +goog.provide('ol.interaction.DragBox'); + +goog.require('goog.events.Event'); +goog.require('ol'); +goog.require('ol.events.ConditionType'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.render.Box'); + /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True only if the target element is not editable. - * @api + * @const + * @type {number} */ -ol.events.condition.targetNotEditable = function(mapBrowserEvent) { - var target = mapBrowserEvent.browserEvent.target; - goog.asserts.assertInstanceof(target, Element); - var tagName = target.tagName; - return ( - tagName !== goog.dom.TagName.INPUT && - tagName !== goog.dom.TagName.SELECT && - tagName !== goog.dom.TagName.TEXTAREA); -}; +ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED = + ol.DRAG_BOX_HYSTERESIS_PIXELS * + ol.DRAG_BOX_HYSTERESIS_PIXELS; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} True if the event originates from a mouse device. - * @api stable + * @enum {string} */ -ol.events.condition.mouseOnly = function(mapBrowserEvent) { - goog.asserts.assertInstanceof(mapBrowserEvent, ol.MapBrowserPointerEvent); - /* pointerId must be 1 for mouse devices, - * see: http://www.w3.org/Submission/pointer-events/#pointerevent-interface +ol.DragBoxEventType = { + /** + * Triggered upon drag box start. + * @event ol.DragBoxEvent#boxstart + * @api stable */ - return mapBrowserEvent.pointerEvent.pointerId == 1; + BOXSTART: 'boxstart', + /** + * Triggered upon drag box end. + * @event ol.DragBoxEvent#boxend + * @api stable + */ + BOXEND: 'boxend' }; -goog.provide('ol.interaction.Pointer'); -goog.require('goog.asserts'); -goog.require('goog.functions'); -goog.require('goog.object'); -goog.require('ol.MapBrowserEvent.EventType'); -goog.require('ol.MapBrowserPointerEvent'); -goog.require('ol.Pixel'); -goog.require('ol.interaction.Interaction'); + +/** + * @classdesc + * Events emitted by {@link ol.interaction.DragBox} instances are instances of + * this type. + * + * @param {string} type The event type. + * @param {ol.Coordinate} coordinate The event coordinate. + * @extends {goog.events.Event} + * @constructor + * @implements {oli.DragBoxEvent} + */ +ol.DragBoxEvent = function(type, coordinate) { + goog.base(this, type); + + /** + * The coordinate of the drag event. + * @const + * @type {ol.Coordinate} + * @api stable + */ + this.coordinate = coordinate; + +}; +goog.inherits(ol.DragBoxEvent, goog.events.Event); /** * @classdesc - * Base class that calls user-defined functions on `down`, `move` and `up` - * events. This class also manages "drag sequences". + * Allows the user to draw a vector box by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when the shift or other key is held down. This is used, for example, + * for zooming to a specific area of the map + * (see {@link ol.interaction.DragZoom} and + * {@link ol.interaction.DragRotateAndZoom}). * - * When the `handleDownEvent` user function returns `true` a drag sequence is - * started. During a drag sequence the `handleDragEvent` user function is - * called on `move` events. The drag sequence ends when the `handleUpEvent` - * user function is called and returns `false`. + * This interaction is only supported for mouse devices. * * @constructor - * @param {olx.interaction.PointerOptions=} opt_options Options. - * @extends {ol.interaction.Interaction} - * @api + * @extends {ol.interaction.Pointer} + * @fires ol.DragBoxEvent + * @param {olx.interaction.DragBoxOptions=} opt_options Options. + * @api stable */ -ol.interaction.Pointer = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - var handleEvent = goog.isDef(options.handleEvent) ? - options.handleEvent : ol.interaction.Pointer.handleEvent; +ol.interaction.DragBox = function(opt_options) { goog.base(this, { - handleEvent: handleEvent + handleDownEvent: ol.interaction.DragBox.handleDownEvent_, + handleDragEvent: ol.interaction.DragBox.handleDragEvent_, + handleUpEvent: ol.interaction.DragBox.handleUpEvent_ }); - /** - * @type {function(ol.MapBrowserPointerEvent):boolean} - * @private - */ - this.handleDownEvent_ = goog.isDef(options.handleDownEvent) ? - options.handleDownEvent : ol.interaction.Pointer.handleDownEvent; + var options = goog.isDef(opt_options) ? opt_options : {}; /** - * @type {function(ol.MapBrowserPointerEvent)} * @private + * @type {ol.style.Style} */ - this.handleDragEvent_ = goog.isDef(options.handleDragEvent) ? - options.handleDragEvent : ol.interaction.Pointer.handleDragEvent; + var style = goog.isDef(options.style) ? options.style : null; /** - * @type {function(ol.MapBrowserPointerEvent)} + * @type {ol.render.Box} * @private */ - this.handleMoveEvent_ = goog.isDef(options.handleMoveEvent) ? - options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent; + this.box_ = new ol.render.Box(style); /** - * @type {function(ol.MapBrowserPointerEvent):boolean} + * @type {ol.Pixel} * @private */ - this.handleUpEvent_ = goog.isDef(options.handleUpEvent) ? - options.handleUpEvent : ol.interaction.Pointer.handleUpEvent; - - /** - * @type {boolean} - * @protected - */ - this.handlingDownUpSequence = false; + this.startPixel_ = null; /** - * @type {Object.<number, ol.pointer.PointerEvent>} * @private + * @type {ol.events.ConditionType} */ - this.trackedPointers_ = {}; - - /** - * @type {Array.<ol.pointer.PointerEvent>} - * @protected - */ - this.targetPointers = []; - -}; -goog.inherits(ol.interaction.Pointer, ol.interaction.Interaction); - + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.always; -/** - * @param {Array.<ol.pointer.PointerEvent>} pointerEvents - * @return {ol.Pixel} Centroid pixel. - */ -ol.interaction.Pointer.centroid = function(pointerEvents) { - var length = pointerEvents.length; - var clientX = 0; - var clientY = 0; - for (var i = 0; i < length; i++) { - clientX += pointerEvents[i].clientX; - clientY += pointerEvents[i].clientY; - } - return [clientX / length, clientY / length]; }; +goog.inherits(ol.interaction.DragBox, ol.interaction.Pointer); /** * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Whether the event is a pointerdown, pointerdrag - * or pointerup event. + * @this {ol.interaction.DragBox} * @private */ -ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = - function(mapBrowserEvent) { - var type = mapBrowserEvent.type; - return ( - type === ol.MapBrowserEvent.EventType.POINTERDOWN || - type === ol.MapBrowserEvent.EventType.POINTERDRAG || - type === ol.MapBrowserEvent.EventType.POINTERUP); +ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel); }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @private + * Returns geometry of last drawn box. + * @return {ol.geom.Polygon} Geometry. + * @api stable */ -ol.interaction.Pointer.prototype.updateTrackedPointers_ = - function(mapBrowserEvent) { - if (this.isPointerDraggingEvent_(mapBrowserEvent)) { - var event = mapBrowserEvent.pointerEvent; - - if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) { - delete this.trackedPointers_[event.pointerId]; - } else if (mapBrowserEvent.type == - ol.MapBrowserEvent.EventType.POINTERDOWN) { - this.trackedPointers_[event.pointerId] = event; - } else if (event.pointerId in this.trackedPointers_) { - // update only when there was a pointerdown event for this pointer - this.trackedPointers_[event.pointerId] = event; - } - this.targetPointers = goog.object.getValues(this.trackedPointers_); - } +ol.interaction.DragBox.prototype.getGeometry = function() { + return this.box_.getGeometry(); }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.Pointer} + * To be overriden by child classes. + * FIXME: use constructor option instead of relying on overridding. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @protected */ -ol.interaction.Pointer.handleDragEvent = goog.nullFunction; +ol.interaction.DragBox.prototype.onBoxEnd = goog.nullFunction; /** * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Capture dragging. - * @this {ol.interaction.Pointer} + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragBox} + * @private */ -ol.interaction.Pointer.handleUpEvent = goog.functions.FALSE; +ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + this.box_.setMap(null); -/** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Capture dragging. - * @this {ol.interaction.Pointer} - */ -ol.interaction.Pointer.handleDownEvent = goog.functions.FALSE; + var deltaX = mapBrowserEvent.pixel[0] - this.startPixel_[0]; + var deltaY = mapBrowserEvent.pixel[1] - this.startPixel_[1]; + + if (deltaX * deltaX + deltaY * deltaY >= + ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED) { + this.onBoxEnd(mapBrowserEvent); + this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXEND, + mapBrowserEvent.coordinate)); + } + return false; +}; /** * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.Pointer} - */ -ol.interaction.Pointer.handleMoveEvent = goog.nullFunction; - - -/** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.Pointer} - * @api + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragBox} + * @private */ -ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) { - if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) { - return true; +ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; } - var stopEvent = false; - this.updateTrackedPointers_(mapBrowserEvent); - if (this.handlingDownUpSequence) { - if (mapBrowserEvent.type == - ol.MapBrowserEvent.EventType.POINTERDRAG) { - this.handleDragEvent_(mapBrowserEvent); - } else if (mapBrowserEvent.type == - ol.MapBrowserEvent.EventType.POINTERUP) { - this.handlingDownUpSequence = - this.handleUpEvent_(mapBrowserEvent); - } - } - if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) { - var handled = this.handleDownEvent_(mapBrowserEvent); - this.handlingDownUpSequence = handled; - stopEvent = this.shouldStopEvent(handled); - } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) { - this.handleMoveEvent_(mapBrowserEvent); + var browserEvent = mapBrowserEvent.browserEvent; + if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) { + this.startPixel_ = mapBrowserEvent.pixel; + this.box_.setMap(mapBrowserEvent.map); + this.box_.setPixels(this.startPixel_, this.startPixel_); + this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXSTART, + mapBrowserEvent.coordinate)); + return true; + } else { + return false; } - return !stopEvent; }; +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * This method is used to determine if "down" events should be propagated to - * other interactions or should be stopped. - * - * The method receives the return code of the "handleDownEvent" function. - * - * By default this function is the "identity" function. It's overidden in - * child classes. - * - * @param {boolean} handled Was the event handled by the interaction? - * @return {boolean} Should the event be stopped? - * @protected + * @fileoverview Namespace with crypto related helper functions. */ -ol.interaction.Pointer.prototype.shouldStopEvent = goog.functions.identity; -goog.provide('ol.interaction.DragPan'); +goog.provide('goog.crypt'); +goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('ol.Kinetic'); -goog.require('ol.Pixel'); -goog.require('ol.PreRenderFunction'); -goog.require('ol.ViewHint'); -goog.require('ol.coordinate'); -goog.require('ol.events.condition'); -goog.require('ol.interaction.Pointer'); - /** - * @classdesc - * Allows the user to pan the map by dragging the map. - * - * @constructor - * @extends {ol.interaction.Pointer} - * @param {olx.interaction.DragPanOptions=} opt_options Options. - * @api stable + * Turns a string into an array of bytes; a "byte" being a JS number in the + * range 0-255. + * @param {string} str String value to arrify. + * @return {!Array<number>} Array of numbers corresponding to the + * UCS character codes of each character in str. */ -ol.interaction.DragPan = function(opt_options) { - - goog.base(this, { - handleDownEvent: ol.interaction.DragPan.handleDownEvent_, - handleDragEvent: ol.interaction.DragPan.handleDragEvent_, - handleUpEvent: ol.interaction.DragPan.handleUpEvent_ - }); +goog.crypt.stringToByteArray = function(str) { + var output = [], p = 0; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + while (c > 0xff) { + output[p++] = c & 0xff; + c >>= 8; + } + output[p++] = c; + } + return output; +}; - var options = goog.isDef(opt_options) ? opt_options : {}; - /** - * @private - * @type {ol.Kinetic|undefined} - */ - this.kinetic_ = options.kinetic; +/** + * Turns an array of numbers into the string given by the concatenation of the + * characters to which the numbers correspond. + * @param {Array<number>} bytes Array of numbers representing characters. + * @return {string} Stringification of the array. + */ +goog.crypt.byteArrayToString = function(bytes) { + var CHUNK_SIZE = 8192; - /** - * @private - * @type {?ol.PreRenderFunction} - */ - this.kineticPreRenderFn_ = null; + // Special-case the simple case for speed's sake. + if (bytes.length < CHUNK_SIZE) { + return String.fromCharCode.apply(null, bytes); + } - /** - * @type {ol.Pixel} - */ - this.lastCentroid = null; + // The remaining logic splits conversion by chunks since + // Function#apply() has a maximum parameter count. + // See discussion: http://goo.gl/LrWmZ9 - /** - * @private - * @type {ol.events.ConditionType} - */ - this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.events.condition.noModifierKeys; + var str = ''; + for (var i = 0; i < bytes.length; i += CHUNK_SIZE) { + var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE); + str += String.fromCharCode.apply(null, chunk); + } + return str; +}; - /** - * @private - * @type {boolean} - */ - this.noKinetic_ = false; +/** + * Turns an array of numbers into the hex string given by the concatenation of + * the hex values to which the numbers correspond. + * @param {Uint8Array|Array<number>} array Array of numbers representing + * characters. + * @return {string} Hex string. + */ +goog.crypt.byteArrayToHex = function(array) { + return goog.array.map(array, function(numByte) { + var hexByte = numByte.toString(16); + return hexByte.length > 1 ? hexByte : '0' + hexByte; + }).join(''); }; -goog.inherits(ol.interaction.DragPan, ol.interaction.Pointer); /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.DragPan} - * @private + * Converts a hex string into an integer array. + * @param {string} hexString Hex string of 16-bit integers (two characters + * per integer). + * @return {!Array<number>} Array of {0,255} integers for the given string. */ -ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) { - goog.asserts.assert(this.targetPointers.length >= 1); - var centroid = - ol.interaction.Pointer.centroid(this.targetPointers); - if (this.kinetic_) { - this.kinetic_.update(centroid[0], centroid[1]); - } - if (!goog.isNull(this.lastCentroid)) { - var deltaX = this.lastCentroid[0] - centroid[0]; - var deltaY = centroid[1] - this.lastCentroid[1]; - var map = mapBrowserEvent.map; - var view = map.getView(); - var viewState = view.getState(); - var center = [deltaX, deltaY]; - ol.coordinate.scale(center, viewState.resolution); - ol.coordinate.rotate(center, viewState.rotation); - ol.coordinate.add(center, viewState.center); - center = view.constrainCenter(center); - map.render(); - view.setCenter(center); +goog.crypt.hexToByteArray = function(hexString) { + goog.asserts.assert(hexString.length % 2 == 0, + 'Key string length must be multiple of 2'); + var arr = []; + for (var i = 0; i < hexString.length; i += 2) { + arr.push(parseInt(hexString.substring(i, i + 2), 16)); } - this.lastCentroid = centroid; + return arr; }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.DragPan} - * @private + * Converts a JS string to a UTF-8 "byte" array. + * @param {string} str 16-bit unicode string. + * @return {!Array<number>} UTF-8 byte array. */ -ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) { - var map = mapBrowserEvent.map; - var view = map.getView(); - if (this.targetPointers.length === 0) { - if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) { - var distance = this.kinetic_.getDistance(); - var angle = this.kinetic_.getAngle(); - var center = view.getCenter(); - goog.asserts.assert(goog.isDef(center)); - this.kineticPreRenderFn_ = this.kinetic_.pan(center); - map.beforeRender(this.kineticPreRenderFn_); - var centerpx = map.getPixelFromCoordinate(center); - var dest = map.getCoordinateFromPixel([ - centerpx[0] - distance * Math.cos(angle), - centerpx[1] - distance * Math.sin(angle) - ]); - dest = view.constrainCenter(dest); - view.setCenter(dest); +goog.crypt.stringToUtf8ByteArray = function(str) { + // TODO(user): Use native implementations if/when available + str = str.replace(/\r\n/g, '\n'); + var out = [], p = 0; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + if (c < 128) { + out[p++] = c; + } else if (c < 2048) { + out[p++] = (c >> 6) | 192; + out[p++] = (c & 63) | 128; + } else { + out[p++] = (c >> 12) | 224; + out[p++] = ((c >> 6) & 63) | 128; + out[p++] = (c & 63) | 128; } - view.setHint(ol.ViewHint.INTERACTING, -1); - map.render(); - return false; - } else { - this.lastCentroid = null; - return true; } + return out; }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.DragPan} - * @private + * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode. + * @param {Uint8Array|Array<number>} bytes UTF-8 byte array. + * @return {string} 16-bit Unicode string. */ -ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) { - if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) { - var map = mapBrowserEvent.map; - var view = map.getView(); - this.lastCentroid = null; - if (!this.handlingDownUpSequence) { - view.setHint(ol.ViewHint.INTERACTING, 1); - } - map.render(); - if (!goog.isNull(this.kineticPreRenderFn_) && - map.removePreRenderFunction(this.kineticPreRenderFn_)) { - view.setCenter(mapBrowserEvent.frameState.viewState.center); - this.kineticPreRenderFn_ = null; - } - if (this.kinetic_) { - this.kinetic_.begin(); +goog.crypt.utf8ByteArrayToString = function(bytes) { + // TODO(user): Use native implementations if/when available + var out = [], pos = 0, c = 0; + while (pos < bytes.length) { + var c1 = bytes[pos++]; + if (c1 < 128) { + out[c++] = String.fromCharCode(c1); + } else if (c1 > 191 && c1 < 224) { + var c2 = bytes[pos++]; + out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63); + } else { + var c2 = bytes[pos++]; + var c3 = bytes[pos++]; + out[c++] = String.fromCharCode( + (c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63); } - // No kinetic as soon as more than one pointer on the screen is - // detected. This is to prevent nasty pans after pinch. - this.noKinetic_ = this.targetPointers.length > 1; - return true; - } else { - return false; } + return out.join(''); }; /** - * @inheritDoc + * XOR two byte arrays. + * @param {!ArrayBufferView|!Array<number>} bytes1 Byte array 1. + * @param {!ArrayBufferView|!Array<number>} bytes2 Byte array 2. + * @return {!Array<number>} Resulting XOR of the two byte arrays. */ -ol.interaction.DragPan.prototype.shouldStopEvent = goog.functions.FALSE; - -goog.provide('ol.interaction.DragRotate'); - -goog.require('goog.asserts'); -goog.require('ol'); -goog.require('ol.ViewHint'); -goog.require('ol.events.ConditionType'); -goog.require('ol.events.condition'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.interaction.Pointer'); +goog.crypt.xorByteArray = function(bytes1, bytes2) { + goog.asserts.assert( + bytes1.length == bytes2.length, + 'XOR array lengths must match'); + var result = []; + for (var i = 0; i < bytes1.length; i++) { + result.push(bytes1[i] ^ bytes2[i]); + } + return result; +}; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @classdesc - * Allows the user to rotate the map by clicking and dragging on the map, - * normally combined with an {@link ol.events.condition} that limits - * it to when the alt and shift keys are held down. + * @fileoverview Abstract cryptographic hash interface. * - * This interaction is only supported for mouse devices. + * See goog.crypt.Sha1 and goog.crypt.Md5 for sample implementations. * - * @constructor - * @extends {ol.interaction.Pointer} - * @param {olx.interaction.DragRotateOptions=} opt_options Options. - * @api stable */ -ol.interaction.DragRotate = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; +goog.provide('goog.crypt.Hash'); - goog.base(this, { - handleDownEvent: ol.interaction.DragRotate.handleDownEvent_, - handleDragEvent: ol.interaction.DragRotate.handleDragEvent_, - handleUpEvent: ol.interaction.DragRotate.handleUpEvent_ - }); - /** - * @private - * @type {ol.events.ConditionType} - */ - this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.events.condition.altShiftKeysOnly; +/** + * Create a cryptographic hash instance. + * + * @constructor + * @struct + */ +goog.crypt.Hash = function() { /** - * @private - * @type {number|undefined} + * The block size for the hasher. + * @type {number} */ - this.lastAngle_ = undefined; - + this.blockSize = -1; }; -goog.inherits(ol.interaction.DragRotate, ol.interaction.Pointer); /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.DragRotate} - * @private + * Resets the internal accumulator. */ -ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return; - } - - var map = mapBrowserEvent.map; - var size = map.getSize(); - var offset = mapBrowserEvent.pixel; - var theta = - Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2); - if (goog.isDef(this.lastAngle_)) { - var delta = theta - this.lastAngle_; - var view = map.getView(); - var viewState = view.getState(); - map.render(); - ol.interaction.Interaction.rotateWithoutConstraints( - map, view, viewState.rotation - delta); - } - this.lastAngle_ = theta; -}; +goog.crypt.Hash.prototype.reset = goog.abstractMethod; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.DragRotate} - * @private + * Adds a byte array (array with values in [0-255] range) or a string (might + * only contain 8-bit, i.e., Latin1 characters) to the internal accumulator. + * + * Many hash functions operate on blocks of data and implement optimizations + * when a full chunk of data is readily available. Hence it is often preferable + * to provide large chunks of data (a kilobyte or more) than to repeatedly + * call the update method with few tens of bytes. If this is not possible, or + * not feasible, it might be good to provide data in multiplies of hash block + * size (often 64 bytes). Please see the implementation and performance tests + * of your favourite hash. + * + * @param {Array<number>|Uint8Array|string} bytes Data used for the update. + * @param {number=} opt_length Number of bytes to use. */ -ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return true; - } - - var map = mapBrowserEvent.map; - var view = map.getView(); - view.setHint(ol.ViewHint.INTERACTING, -1); - var viewState = view.getState(); - ol.interaction.Interaction.rotate(map, view, viewState.rotation, - undefined, ol.DRAGROTATE_ANIMATION_DURATION); - return false; -}; +goog.crypt.Hash.prototype.update = goog.abstractMethod; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.DragRotate} - * @private + * @return {!Array<number>} The finalized hash computed + * from the internal accumulator. */ -ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return false; - } - - var browserEvent = mapBrowserEvent.browserEvent; - if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) { - var map = mapBrowserEvent.map; - map.getView().setHint(ol.ViewHint.INTERACTING, 1); - map.render(); - this.lastAngle_ = undefined; - return true; - } else { - return false; - } -}; +goog.crypt.Hash.prototype.digest = goog.abstractMethod; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @inheritDoc + * @fileoverview MD5 cryptographic hash. + * Implementation of http://tools.ietf.org/html/rfc1321 with common + * optimizations and tweaks (see http://en.wikipedia.org/wiki/MD5). + * + * Usage: + * var md5 = new goog.crypt.Md5(); + * md5.update(bytes); + * var hash = md5.digest(); + * + * Performance: + * Chrome 23 ~680 Mbit/s + * Chrome 13 (in a VM) ~250 Mbit/s + * Firefox 6.0 (in a VM) ~100 Mbit/s + * IE9 (in a VM) ~27 Mbit/s + * Firefox 3.6 ~15 Mbit/s + * IE8 (in a VM) ~13 Mbit/s + * */ -ol.interaction.DragRotate.prototype.shouldStopEvent = goog.functions.FALSE; - -goog.provide('ol.geom.Geometry'); -goog.provide('ol.geom.GeometryType'); -goog.require('goog.asserts'); -goog.require('goog.functions'); -goog.require('ol.Observable'); -goog.require('ol.proj'); +goog.provide('goog.crypt.Md5'); +goog.require('goog.crypt.Hash'); -/** - * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`, - * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`, - * `'GeometryCollection'`, `'Circle'`. - * @enum {string} - * @api stable - */ -ol.geom.GeometryType = { - POINT: 'Point', - LINE_STRING: 'LineString', - LINEAR_RING: 'LinearRing', - POLYGON: 'Polygon', - MULTI_POINT: 'MultiPoint', - MULTI_LINE_STRING: 'MultiLineString', - MULTI_POLYGON: 'MultiPolygon', - GEOMETRY_COLLECTION: 'GeometryCollection', - CIRCLE: 'Circle' -}; /** - * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z') - * or measure ('M') coordinate is available. Supported values are `'XY'`, - * `'XYZ'`, `'XYM'`, `'XYZM'`. - * @enum {string} - * @api stable - */ -ol.geom.GeometryLayout = { - XY: 'XY', - XYZ: 'XYZ', - XYM: 'XYM', - XYZM: 'XYZM' -}; - - - -/** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for vector geometries. - * + * MD5 cryptographic hash constructor. * @constructor - * @extends {ol.Observable} - * @fires change Triggered when the geometry changes. - * @api stable + * @extends {goog.crypt.Hash} + * @final + * @struct */ -ol.geom.Geometry = function() { - - goog.base(this); +goog.crypt.Md5 = function() { + goog.crypt.Md5.base(this, 'constructor'); - /** - * @protected - * @type {ol.Extent|undefined} - */ - this.extent = undefined; + this.blockSize = 512 / 8; /** - * @protected - * @type {number} + * Holds the current values of accumulated A-D variables (MD buffer). + * @type {!Array<number>} + * @private */ - this.extentRevision = -1; + this.chain_ = new Array(4); /** - * @protected - * @type {Object.<string, ol.geom.Geometry>} + * A buffer holding the data until the whole block can be processed. + * @type {!Array<number>} + * @private */ - this.simplifiedGeometryCache = {}; + this.block_ = new Array(this.blockSize); /** - * @protected + * The length of yet-unprocessed data as collected in the block. * @type {number} + * @private */ - this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.blockLength_ = 0; /** - * @protected + * The total length of the message so far. * @type {number} + * @private */ - this.simplifiedGeometryRevision = 0; - -}; -goog.inherits(ol.geom.Geometry, ol.Observable); - - -/** - * Make a complete copy of the geometry. - * @function - * @return {!ol.geom.Geometry} Clone. - * @api stable - */ -ol.geom.Geometry.prototype.clone = goog.abstractMethod; - - -/** - * @param {number} x X. - * @param {number} y Y. - * @param {ol.Coordinate} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @return {number} Minimum squared distance. - */ -ol.geom.Geometry.prototype.closestPointXY = goog.abstractMethod; - - -/** - * @param {ol.Coordinate} point Point. - * @param {ol.Coordinate=} opt_closestPoint Closest point. - * @return {ol.Coordinate} Closest point. - * @api stable - */ -ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) { - var closestPoint = goog.isDef(opt_closestPoint) ? - opt_closestPoint : [NaN, NaN]; - this.closestPointXY(point[0], point[1], closestPoint, Infinity); - return closestPoint; -}; - + this.totalLength_ = 0; -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @return {boolean} Contains coordinate. - */ -ol.geom.Geometry.prototype.containsCoordinate = function(coordinate) { - return this.containsXY(coordinate[0], coordinate[1]); + this.reset(); }; +goog.inherits(goog.crypt.Md5, goog.crypt.Hash); /** - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). - */ -ol.geom.Geometry.prototype.containsXY = goog.functions.FALSE; - - -/** - * Get the extent of the geometry. - * @function - * @param {ol.Extent=} opt_extent Extent. - * @return {ol.Extent} extent Extent. - * @api stable - */ -ol.geom.Geometry.prototype.getExtent = goog.abstractMethod; - - -/** - * Create a simplified version of this geometry using the Douglas Peucker - * algorithm. - * @see http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm - * @function - * @param {number} squaredTolerance Squared tolerance. - * @return {ol.geom.Geometry} Simplified geometry. - */ -ol.geom.Geometry.prototype.getSimplifiedGeometry = goog.abstractMethod; - - -/** - * Get the type of this geometry. - * @function - * @return {ol.geom.GeometryType} Geometry type. - * @api stable - */ -ol.geom.Geometry.prototype.getType = goog.abstractMethod; - - -/** - * Apply a transform function to each coordinate of the geometry. - * The geometry is modified in place. - * If you do not want the geometry modified in place, first clone() it and - * then use this function on the clone. - * @function - * @param {ol.TransformFunction} transformFn Transform. - * @api stable + * Integer rotation constants used by the abbreviated implementation. + * They are hardcoded in the unrolled implementation, so it is left + * here commented out. + * @type {Array<number>} + * @private + * +goog.crypt.Md5.S_ = [ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 +]; */ -ol.geom.Geometry.prototype.applyTransform = goog.abstractMethod; - /** - * Test if the geometry and the passed extent intersect. - * @param {ol.Extent} extent Extent. - * @return {boolean} `true` if the geometry and the extent intersect. - * @function - * @api + * Sine function constants used by the abbreviated implementation. + * They are hardcoded in the unrolled implementation, so it is left + * here commented out. + * @type {Array<number>} + * @private + * +goog.crypt.Md5.T_ = [ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +]; */ -ol.geom.Geometry.prototype.intersectsExtent = goog.abstractMethod; - -/** - * Translate the geometry. - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @function - * @api - */ -ol.geom.Geometry.prototype.translate = goog.abstractMethod; +/** @override */ +goog.crypt.Md5.prototype.reset = function() { + this.chain_[0] = 0x67452301; + this.chain_[1] = 0xefcdab89; + this.chain_[2] = 0x98badcfe; + this.chain_[3] = 0x10325476; -/** - * Transform each coordinate of the geometry from one coordinate reference - * system to another. The geometry is modified in place. - * For example, a line will be transformed to a line and a circle to a circle. - * If you do not want the geometry modified in place, first clone() it and - * then use this function on the clone. - * - * @param {ol.proj.ProjectionLike} source The current projection. Can be a - * string identifier or a {@link ol.proj.Projection} object. - * @param {ol.proj.ProjectionLike} destination The desired projection. Can be a - * string identifier or a {@link ol.proj.Projection} object. - * @return {ol.geom.Geometry} This geometry. Note that original geometry is - * modified in place. - * @api stable - */ -ol.geom.Geometry.prototype.transform = function(source, destination) { - this.applyTransform(ol.proj.getTransform(source, destination)); - return this; + this.blockLength_ = 0; + this.totalLength_ = 0; }; -goog.provide('ol.geom.flat.transform'); - -goog.require('goog.vec.Mat4'); - /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {Array.<number>=} opt_dest Destination. - * @return {Array.<number>} Transformed coordinates. + * Internal compress helper function. It takes a block of data (64 bytes) + * and updates the accumulator. + * @param {Array<number>|Uint8Array|string} buf The block to compress. + * @param {number=} opt_offset Offset of the block in the buffer. + * @private */ -ol.geom.flat.transform.transform2D = - function(flatCoordinates, offset, end, stride, transform, opt_dest) { - var m00 = goog.vec.Mat4.getElement(transform, 0, 0); - var m10 = goog.vec.Mat4.getElement(transform, 1, 0); - var m01 = goog.vec.Mat4.getElement(transform, 0, 1); - var m11 = goog.vec.Mat4.getElement(transform, 1, 1); - var m03 = goog.vec.Mat4.getElement(transform, 0, 3); - var m13 = goog.vec.Mat4.getElement(transform, 1, 3); - var dest = goog.isDef(opt_dest) ? opt_dest : []; - var i = 0; - var j; - for (j = offset; j < end; j += stride) { - var x = flatCoordinates[j]; - var y = flatCoordinates[j + 1]; - dest[i++] = m00 * x + m01 * y + m03; - dest[i++] = m10 * x + m11 * y + m13; - } - if (goog.isDef(opt_dest) && dest.length != i) { - dest.length = i; +goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) { + if (!opt_offset) { + opt_offset = 0; } - return dest; -}; + // We allocate the array every time, but it's cheap in practice. + var X = new Array(16); -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @param {Array.<number>=} opt_dest Destination. - * @return {Array.<number>} Transformed coordinates. - */ -ol.geom.flat.transform.translate = - function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) { - var dest = goog.isDef(opt_dest) ? opt_dest : []; - var i = 0; - var j, k; - for (j = offset; j < end; j += stride) { - dest[i++] = flatCoordinates[j] + deltaX; - dest[i++] = flatCoordinates[j + 1] + deltaY; - for (k = j + 2; k < j + stride; ++k) { - dest[i++] = flatCoordinates[k]; + // Get 16 little endian words. It is not worth unrolling this for Chrome 11. + if (goog.isString(buf)) { + for (var i = 0; i < 16; ++i) { + X[i] = (buf.charCodeAt(opt_offset++)) | + (buf.charCodeAt(opt_offset++) << 8) | + (buf.charCodeAt(opt_offset++) << 16) | + (buf.charCodeAt(opt_offset++) << 24); + } + } else { + for (var i = 0; i < 16; ++i) { + X[i] = (buf[opt_offset++]) | + (buf[opt_offset++] << 8) | + (buf[opt_offset++] << 16) | + (buf[opt_offset++] << 24); } } - if (goog.isDef(opt_dest) && dest.length != i) { - dest.length = i; - } - return dest; -}; - -goog.provide('ol.geom.SimpleGeometry'); - -goog.require('goog.asserts'); -goog.require('goog.functions'); -goog.require('goog.object'); -goog.require('ol.extent'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.flat.transform'); + var A = this.chain_[0]; + var B = this.chain_[1]; + var C = this.chain_[2]; + var D = this.chain_[3]; + var sum = 0; + /* + * This is an abbreviated implementation, it is left here commented out for + * reference purposes. See below for an unrolled version in use. + * + var f, n, tmp; + for (var i = 0; i < 64; ++i) { -/** - * @classdesc - * Abstract base class; only used for creating subclasses; do not instantiate - * in apps, as cannot be rendered. - * - * @constructor - * @extends {ol.geom.Geometry} - * @api stable - */ -ol.geom.SimpleGeometry = function() { - - goog.base(this); - - /** - * @protected - * @type {ol.geom.GeometryLayout} - */ - this.layout = ol.geom.GeometryLayout.XY; + if (i < 16) { + f = (D ^ (B & (C ^ D))); + n = i; + } else if (i < 32) { + f = (C ^ (D & (B ^ C))); + n = (5 * i + 1) % 16; + } else if (i < 48) { + f = (B ^ C ^ D); + n = (3 * i + 5) % 16; + } else { + f = (C ^ (B | (~D))); + n = (7 * i) % 16; + } - /** - * @protected - * @type {number} + tmp = D; + D = C; + C = B; + sum = (A + f + goog.crypt.Md5.T_[i] + X[n]) & 0xffffffff; + B += ((sum << goog.crypt.Md5.S_[i]) & 0xffffffff) | + (sum >>> (32 - goog.crypt.Md5.S_[i])); + A = tmp; + } */ - this.stride = 2; - /** - * @protected - * @type {Array.<number>} + /* + * This is an unrolled MD5 implementation, which gives ~30% speedup compared + * to the abbreviated implementation above, as measured on Chrome 11. It is + * important to keep 32-bit croppings to minimum and inline the integer + * rotation. */ - this.flatCoordinates = null; + sum = (A + (D ^ (B & (C ^ D))) + X[0] + 0xd76aa478) & 0xffffffff; + A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); + sum = (D + (C ^ (A & (B ^ C))) + X[1] + 0xe8c7b756) & 0xffffffff; + D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); + sum = (C + (B ^ (D & (A ^ B))) + X[2] + 0x242070db) & 0xffffffff; + C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); + sum = (B + (A ^ (C & (D ^ A))) + X[3] + 0xc1bdceee) & 0xffffffff; + B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); + sum = (A + (D ^ (B & (C ^ D))) + X[4] + 0xf57c0faf) & 0xffffffff; + A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); + sum = (D + (C ^ (A & (B ^ C))) + X[5] + 0x4787c62a) & 0xffffffff; + D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); + sum = (C + (B ^ (D & (A ^ B))) + X[6] + 0xa8304613) & 0xffffffff; + C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); + sum = (B + (A ^ (C & (D ^ A))) + X[7] + 0xfd469501) & 0xffffffff; + B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); + sum = (A + (D ^ (B & (C ^ D))) + X[8] + 0x698098d8) & 0xffffffff; + A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); + sum = (D + (C ^ (A & (B ^ C))) + X[9] + 0x8b44f7af) & 0xffffffff; + D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); + sum = (C + (B ^ (D & (A ^ B))) + X[10] + 0xffff5bb1) & 0xffffffff; + C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); + sum = (B + (A ^ (C & (D ^ A))) + X[11] + 0x895cd7be) & 0xffffffff; + B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); + sum = (A + (D ^ (B & (C ^ D))) + X[12] + 0x6b901122) & 0xffffffff; + A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); + sum = (D + (C ^ (A & (B ^ C))) + X[13] + 0xfd987193) & 0xffffffff; + D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); + sum = (C + (B ^ (D & (A ^ B))) + X[14] + 0xa679438e) & 0xffffffff; + C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); + sum = (B + (A ^ (C & (D ^ A))) + X[15] + 0x49b40821) & 0xffffffff; + B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); + sum = (A + (C ^ (D & (B ^ C))) + X[1] + 0xf61e2562) & 0xffffffff; + A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); + sum = (D + (B ^ (C & (A ^ B))) + X[6] + 0xc040b340) & 0xffffffff; + D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); + sum = (C + (A ^ (B & (D ^ A))) + X[11] + 0x265e5a51) & 0xffffffff; + C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); + sum = (B + (D ^ (A & (C ^ D))) + X[0] + 0xe9b6c7aa) & 0xffffffff; + B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); + sum = (A + (C ^ (D & (B ^ C))) + X[5] + 0xd62f105d) & 0xffffffff; + A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); + sum = (D + (B ^ (C & (A ^ B))) + X[10] + 0x02441453) & 0xffffffff; + D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); + sum = (C + (A ^ (B & (D ^ A))) + X[15] + 0xd8a1e681) & 0xffffffff; + C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); + sum = (B + (D ^ (A & (C ^ D))) + X[4] + 0xe7d3fbc8) & 0xffffffff; + B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); + sum = (A + (C ^ (D & (B ^ C))) + X[9] + 0x21e1cde6) & 0xffffffff; + A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); + sum = (D + (B ^ (C & (A ^ B))) + X[14] + 0xc33707d6) & 0xffffffff; + D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); + sum = (C + (A ^ (B & (D ^ A))) + X[3] + 0xf4d50d87) & 0xffffffff; + C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); + sum = (B + (D ^ (A & (C ^ D))) + X[8] + 0x455a14ed) & 0xffffffff; + B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); + sum = (A + (C ^ (D & (B ^ C))) + X[13] + 0xa9e3e905) & 0xffffffff; + A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); + sum = (D + (B ^ (C & (A ^ B))) + X[2] + 0xfcefa3f8) & 0xffffffff; + D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); + sum = (C + (A ^ (B & (D ^ A))) + X[7] + 0x676f02d9) & 0xffffffff; + C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); + sum = (B + (D ^ (A & (C ^ D))) + X[12] + 0x8d2a4c8a) & 0xffffffff; + B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); + sum = (A + (B ^ C ^ D) + X[5] + 0xfffa3942) & 0xffffffff; + A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); + sum = (D + (A ^ B ^ C) + X[8] + 0x8771f681) & 0xffffffff; + D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); + sum = (C + (D ^ A ^ B) + X[11] + 0x6d9d6122) & 0xffffffff; + C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); + sum = (B + (C ^ D ^ A) + X[14] + 0xfde5380c) & 0xffffffff; + B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); + sum = (A + (B ^ C ^ D) + X[1] + 0xa4beea44) & 0xffffffff; + A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); + sum = (D + (A ^ B ^ C) + X[4] + 0x4bdecfa9) & 0xffffffff; + D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); + sum = (C + (D ^ A ^ B) + X[7] + 0xf6bb4b60) & 0xffffffff; + C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); + sum = (B + (C ^ D ^ A) + X[10] + 0xbebfbc70) & 0xffffffff; + B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); + sum = (A + (B ^ C ^ D) + X[13] + 0x289b7ec6) & 0xffffffff; + A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); + sum = (D + (A ^ B ^ C) + X[0] + 0xeaa127fa) & 0xffffffff; + D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); + sum = (C + (D ^ A ^ B) + X[3] + 0xd4ef3085) & 0xffffffff; + C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); + sum = (B + (C ^ D ^ A) + X[6] + 0x04881d05) & 0xffffffff; + B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); + sum = (A + (B ^ C ^ D) + X[9] + 0xd9d4d039) & 0xffffffff; + A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); + sum = (D + (A ^ B ^ C) + X[12] + 0xe6db99e5) & 0xffffffff; + D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); + sum = (C + (D ^ A ^ B) + X[15] + 0x1fa27cf8) & 0xffffffff; + C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); + sum = (B + (C ^ D ^ A) + X[2] + 0xc4ac5665) & 0xffffffff; + B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); + sum = (A + (C ^ (B | (~D))) + X[0] + 0xf4292244) & 0xffffffff; + A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); + sum = (D + (B ^ (A | (~C))) + X[7] + 0x432aff97) & 0xffffffff; + D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); + sum = (C + (A ^ (D | (~B))) + X[14] + 0xab9423a7) & 0xffffffff; + C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); + sum = (B + (D ^ (C | (~A))) + X[5] + 0xfc93a039) & 0xffffffff; + B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); + sum = (A + (C ^ (B | (~D))) + X[12] + 0x655b59c3) & 0xffffffff; + A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); + sum = (D + (B ^ (A | (~C))) + X[3] + 0x8f0ccc92) & 0xffffffff; + D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); + sum = (C + (A ^ (D | (~B))) + X[10] + 0xffeff47d) & 0xffffffff; + C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); + sum = (B + (D ^ (C | (~A))) + X[1] + 0x85845dd1) & 0xffffffff; + B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); + sum = (A + (C ^ (B | (~D))) + X[8] + 0x6fa87e4f) & 0xffffffff; + A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); + sum = (D + (B ^ (A | (~C))) + X[15] + 0xfe2ce6e0) & 0xffffffff; + D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); + sum = (C + (A ^ (D | (~B))) + X[6] + 0xa3014314) & 0xffffffff; + C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); + sum = (B + (D ^ (C | (~A))) + X[13] + 0x4e0811a1) & 0xffffffff; + B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); + sum = (A + (C ^ (B | (~D))) + X[4] + 0xf7537e82) & 0xffffffff; + A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); + sum = (D + (B ^ (A | (~C))) + X[11] + 0xbd3af235) & 0xffffffff; + D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); + sum = (C + (A ^ (D | (~B))) + X[2] + 0x2ad7d2bb) & 0xffffffff; + C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); + sum = (B + (D ^ (C | (~A))) + X[9] + 0xeb86d391) & 0xffffffff; + B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); + this.chain_[0] = (this.chain_[0] + A) & 0xffffffff; + this.chain_[1] = (this.chain_[1] + B) & 0xffffffff; + this.chain_[2] = (this.chain_[2] + C) & 0xffffffff; + this.chain_[3] = (this.chain_[3] + D) & 0xffffffff; }; -goog.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry); -/** - * @param {number} stride Stride. - * @private - * @return {ol.geom.GeometryLayout} layout Layout. - */ -ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) { - if (stride == 2) { - return ol.geom.GeometryLayout.XY; - } else if (stride == 3) { - return ol.geom.GeometryLayout.XYZ; - } else if (stride == 4) { - return ol.geom.GeometryLayout.XYZM; - } else { - goog.asserts.fail('unsupported stride: ' + stride); - } -}; - - -/** - * @param {ol.geom.GeometryLayout} layout Layout. - * @private - * @return {number} Stride. - */ -ol.geom.SimpleGeometry.getStrideForLayout_ = function(layout) { - if (layout == ol.geom.GeometryLayout.XY) { - return 2; - } else if (layout == ol.geom.GeometryLayout.XYZ) { - return 3; - } else if (layout == ol.geom.GeometryLayout.XYM) { - return 3; - } else if (layout == ol.geom.GeometryLayout.XYZM) { - return 4; - } else { - goog.asserts.fail('unsupported layout: ' + layout); - } -}; - - -/** - * @inheritDoc - */ -ol.geom.SimpleGeometry.prototype.containsXY = goog.functions.FALSE; - - -/** - * @inheritDoc - * @api stable - */ -ol.geom.SimpleGeometry.prototype.getExtent = function(opt_extent) { - if (this.extentRevision != this.getRevision()) { - this.extent = ol.extent.createOrUpdateFromFlatCoordinates( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - this.extent); - this.extentRevision = this.getRevision(); - } - goog.asserts.assert(goog.isDef(this.extent)); - return ol.extent.returnOrUpdate(this.extent, opt_extent); -}; - - -/** - * @return {ol.Coordinate} First coordinate. - * @api stable - */ -ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() { - return this.flatCoordinates.slice(0, this.stride); -}; - - -/** - * @return {Array.<number>} Flat coordinates. - */ -ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() { - return this.flatCoordinates; -}; - - -/** - * @return {ol.Coordinate} Last point. - * @api stable - */ -ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() { - return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride); -}; - - -/** - * @return {ol.geom.GeometryLayout} Layout. - * @api stable - */ -ol.geom.SimpleGeometry.prototype.getLayout = function() { - return this.layout; -}; - - -/** - * @inheritDoc - */ -ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = - function(squaredTolerance) { - if (this.simplifiedGeometryRevision != this.getRevision()) { - goog.object.clear(this.simplifiedGeometryCache); - this.simplifiedGeometryMaxMinSquaredTolerance = 0; - this.simplifiedGeometryRevision = this.getRevision(); - } - // If squaredTolerance is negative or if we know that simplification will not - // have any effect then just return this. - if (squaredTolerance < 0 || - (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && - squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) { - return this; - } - var key = squaredTolerance.toString(); - if (this.simplifiedGeometryCache.hasOwnProperty(key)) { - return this.simplifiedGeometryCache[key]; - } else { - var simplifiedGeometry = - this.getSimplifiedGeometryInternal(squaredTolerance); - var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates(); - if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) { - this.simplifiedGeometryCache[key] = simplifiedGeometry; - return simplifiedGeometry; - } else { - // Simplification did not actually remove any coordinates. We now know - // that any calls to getSimplifiedGeometry with a squaredTolerance less - // than or equal to the current squaredTolerance will also not have any - // effect. This allows us to short circuit simplification (saving CPU - // cycles) and prevents the cache of simplified geometries from filling - // up with useless identical copies of this geometry (saving memory). - this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; - return this; - } - } -}; - - -/** - * @param {number} squaredTolerance Squared tolerance. - * @return {ol.geom.SimpleGeometry} Simplified geometry. - * @protected - */ -ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = - function(squaredTolerance) { - return this; -}; - - -/** - * @return {number} Stride. - */ -ol.geom.SimpleGeometry.prototype.getStride = function() { - return this.stride; -}; - - -/** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @protected - */ -ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = - function(layout, flatCoordinates) { - this.stride = ol.geom.SimpleGeometry.getStrideForLayout_(layout); - this.layout = layout; - this.flatCoordinates = flatCoordinates; -}; - - -/** - * @param {ol.geom.GeometryLayout|undefined} layout Layout. - * @param {Array} coordinates Coordinates. - * @param {number} nesting Nesting. - * @protected - */ -ol.geom.SimpleGeometry.prototype.setLayout = - function(layout, coordinates, nesting) { - /** @type {number} */ - var stride; - if (goog.isDef(layout)) { - stride = ol.geom.SimpleGeometry.getStrideForLayout_(layout); - } else { - var i; - for (i = 0; i < nesting; ++i) { - if (coordinates.length === 0) { - this.layout = ol.geom.GeometryLayout.XY; - this.stride = 2; - return; - } else { - coordinates = /** @type {Array} */ (coordinates[0]); - } - } - stride = (/** @type {Array} */ (coordinates)).length; - layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride); - } - this.layout = layout; - this.stride = stride; -}; - - -/** - * @inheritDoc - * @api stable - */ -ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) { - if (!goog.isNull(this.flatCoordinates)) { - transformFn(this.flatCoordinates, this.flatCoordinates, this.stride); - this.changed(); - } -}; - - -/** - * Translate the geometry. - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @api - */ -ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) { - var flatCoordinates = this.getFlatCoordinates(); - if (!goog.isNull(flatCoordinates)) { - var stride = this.getStride(); - ol.geom.flat.transform.translate( - flatCoordinates, 0, flatCoordinates.length, stride, - deltaX, deltaY, flatCoordinates); - this.changed(); - } -}; - - -/** - * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {Array.<number>=} opt_dest Destination. - * @return {Array.<number>} Transformed flat coordinates. - */ -ol.geom.transformSimpleGeometry2D = - function(simpleGeometry, transform, opt_dest) { - var flatCoordinates = simpleGeometry.getFlatCoordinates(); - if (goog.isNull(flatCoordinates)) { - return null; - } else { - var stride = simpleGeometry.getStride(); - return ol.geom.flat.transform.transform2D( - flatCoordinates, 0, flatCoordinates.length, stride, - transform, opt_dest); - } -}; - -goog.provide('ol.geom.flat.area'); - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} Area. - */ -ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) { - var twiceArea = 0; - var x1 = flatCoordinates[end - stride]; - var y1 = flatCoordinates[end - stride + 1]; - for (; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - twiceArea += y1 * x2 - x1 * y2; - x1 = x2; - y1 = y2; - } - return twiceArea / 2; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @return {number} Area. - */ -ol.geom.flat.area.linearRings = - function(flatCoordinates, offset, ends, stride) { - var area = 0; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride); - offset = end; - } - return area; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @return {number} Area. - */ -ol.geom.flat.area.linearRingss = - function(flatCoordinates, offset, endss, stride) { - var area = 0; - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - area += - ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride); - offset = ends[ends.length - 1]; - } - return area; -}; - -goog.provide('ol.math'); - -goog.require('goog.asserts'); - - -/** - * @param {number} x X. - * @return {number} Hyperbolic cosine of x. - */ -ol.math.cosh = function(x) { - return (Math.exp(x) + Math.exp(-x)) / 2; -}; - - -/** - * @param {number} x X. - * @return {number} Hyperbolic cotangent of x. - */ -ol.math.coth = function(x) { - var expMinusTwoX = Math.exp(-2 * x); - return (1 + expMinusTwoX) / (1 - expMinusTwoX); -}; - - -/** - * @param {number} x X. - * @return {number} Hyperbolic cosecant of x. - */ -ol.math.csch = function(x) { - return 2 / (Math.exp(x) - Math.exp(-x)); -}; - - -/** - * @param {number} x X. - * @return {number} The smallest power of two greater than or equal to x. - */ -ol.math.roundUpToPowerOfTwo = function(x) { - goog.asserts.assert(0 < x); - return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2)); -}; - - -/** - * @param {number} x X. - * @return {number} Hyperbolic secant of x. - */ -ol.math.sech = function(x) { - return 2 / (Math.exp(x) + Math.exp(-x)); -}; - - -/** - * @param {number} x X. - * @return {number} Hyperbolic sine of x. - */ -ol.math.sinh = function(x) { - return (Math.exp(x) - Math.exp(-x)) / 2; -}; - - -/** - * Returns the square of the closest distance between the point (x, y) and the - * line segment (x1, y1) to (x2, y2). - * @param {number} x X. - * @param {number} y Y. - * @param {number} x1 X1. - * @param {number} y1 Y1. - * @param {number} x2 X2. - * @param {number} y2 Y2. - * @return {number} Squared distance. - */ -ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) { - var dx = x2 - x1; - var dy = y2 - y1; - if (dx !== 0 || dy !== 0) { - var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); - if (t > 1) { - x1 = x2; - y1 = y2; - } else if (t > 0) { - x1 += dx * t; - y1 += dy * t; - } - } - return ol.math.squaredDistance(x, y, x1, y1); -}; - - -/** - * Returns the square of the distance between the points (x1, y1) and (x2, y2). - * @param {number} x1 X1. - * @param {number} y1 Y1. - * @param {number} x2 X2. - * @param {number} y2 Y2. - * @return {number} Squared distance. - */ -ol.math.squaredDistance = function(x1, y1, x2, y2) { - var dx = x2 - x1; - var dy = y2 - y1; - return dx * dx + dy * dy; -}; - - -/** - * @param {number} x X. - * @return {number} Hyperbolic tangent of x. - */ -ol.math.tanh = function(x) { - var expMinusTwoX = Math.exp(-2 * x); - return (1 - expMinusTwoX) / (1 + expMinusTwoX); -}; - -goog.provide('ol.geom.flat.closest'); - -goog.require('goog.asserts'); -goog.require('goog.math'); -goog.require('ol.math'); - - -/** - * Returns the point on the 2D line segment flatCoordinates[offset1] to - * flatCoordinates[offset2] that is closest to the point (x, y). Extra - * dimensions are linearly interpolated. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset1 Offset 1. - * @param {number} offset2 Offset 2. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @param {Array.<number>} closestPoint Closest point. - */ -ol.geom.flat.closest.point = - function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) { - var x1 = flatCoordinates[offset1]; - var y1 = flatCoordinates[offset1 + 1]; - var dx = flatCoordinates[offset2] - x1; - var dy = flatCoordinates[offset2 + 1] - y1; - var i, offset; - if (dx === 0 && dy === 0) { - offset = offset1; - } else { - var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy); - if (t > 1) { - offset = offset2; - } else if (t > 0) { - for (i = 0; i < stride; ++i) { - closestPoint[i] = goog.math.lerp(flatCoordinates[offset1 + i], - flatCoordinates[offset2 + i], t); - } - closestPoint.length = stride; - return; - } else { - offset = offset1; - } - } - for (i = 0; i < stride; ++i) { - closestPoint[i] = flatCoordinates[offset + i]; - } - closestPoint.length = stride; -}; - - -/** - * Return the squared of the largest distance between any pair of consecutive - * coordinates. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} maxSquaredDelta Max squared delta. - * @return {number} Max squared delta. - */ -ol.geom.flat.closest.getMaxSquaredDelta = - function(flatCoordinates, offset, end, stride, maxSquaredDelta) { - var x1 = flatCoordinates[offset]; - var y1 = flatCoordinates[offset + 1]; - for (offset += stride; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - var squaredDelta = ol.math.squaredDistance(x1, y1, x2, y2); - if (squaredDelta > maxSquaredDelta) { - maxSquaredDelta = squaredDelta; - } - x1 = x2; - y1 = y2; - } - return maxSquaredDelta; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {number} maxSquaredDelta Max squared delta. - * @return {number} Max squared delta. - */ -ol.geom.flat.closest.getsMaxSquaredDelta = - function(flatCoordinates, offset, ends, stride, maxSquaredDelta) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta( - flatCoordinates, offset, end, stride, maxSquaredDelta); - offset = end; - } - return maxSquaredDelta; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {number} maxSquaredDelta Max squared delta. - * @return {number} Max squared delta. - */ -ol.geom.flat.closest.getssMaxSquaredDelta = - function(flatCoordinates, offset, endss, stride, maxSquaredDelta) { - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta( - flatCoordinates, offset, ends, stride, maxSquaredDelta); - offset = ends[ends.length - 1]; - } - return maxSquaredDelta; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} maxDelta Max delta. - * @param {boolean} isRing Is ring. - * @param {number} x X. - * @param {number} y Y. - * @param {Array.<number>} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @param {Array.<number>=} opt_tmpPoint Temporary point object. - * @return {number} Minimum squared distance. - */ -ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end, - stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, - opt_tmpPoint) { - if (offset == end) { - return minSquaredDistance; - } - var i, squaredDistance; - if (maxDelta === 0) { - // All points are identical, so just test the first point. - squaredDistance = ol.math.squaredDistance( - x, y, flatCoordinates[offset], flatCoordinates[offset + 1]); - if (squaredDistance < minSquaredDistance) { - for (i = 0; i < stride; ++i) { - closestPoint[i] = flatCoordinates[offset + i]; - } - closestPoint.length = stride; - return squaredDistance; - } else { - return minSquaredDistance; - } - } - goog.asserts.assert(maxDelta > 0); - var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN]; - var index = offset + stride; - while (index < end) { - ol.geom.flat.closest.point( - flatCoordinates, index - stride, index, stride, x, y, tmpPoint); - squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); - if (squaredDistance < minSquaredDistance) { - minSquaredDistance = squaredDistance; - for (i = 0; i < stride; ++i) { - closestPoint[i] = tmpPoint[i]; - } - closestPoint.length = stride; - index += stride; - } else { - // Skip ahead multiple points, because we know that all the skipped - // points cannot be any closer than the closest point we have found so - // far. We know this because we know how close the current point is, how - // close the closest point we have found so far is, and the maximum - // distance between consecutive points. For example, if we're currently - // at distance 10, the best we've found so far is 3, and that the maximum - // distance between consecutive points is 2, then we'll need to skip at - // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of - // finding a closer point. We use Math.max(..., 1) to ensure that we - // always advance at least one point, to avoid an infinite loop. - index += stride * Math.max( - ((Math.sqrt(squaredDistance) - - Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1); - } - } - if (isRing) { - // Check the closing segment. - ol.geom.flat.closest.point( - flatCoordinates, end - stride, offset, stride, x, y, tmpPoint); - squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]); - if (squaredDistance < minSquaredDistance) { - minSquaredDistance = squaredDistance; - for (i = 0; i < stride; ++i) { - closestPoint[i] = tmpPoint[i]; - } - closestPoint.length = stride; - } - } - return minSquaredDistance; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {number} maxDelta Max delta. - * @param {boolean} isRing Is ring. - * @param {number} x X. - * @param {number} y Y. - * @param {Array.<number>} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @param {Array.<number>=} opt_tmpPoint Temporary point object. - * @return {number} Minimum squared distance. - */ -ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends, - stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, - opt_tmpPoint) { - var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN]; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - minSquaredDistance = ol.geom.flat.closest.getClosestPoint( - flatCoordinates, offset, end, stride, - maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); - offset = end; - } - return minSquaredDistance; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {number} maxDelta Max delta. - * @param {boolean} isRing Is ring. - * @param {number} x X. - * @param {number} y Y. - * @param {Array.<number>} closestPoint Closest point. - * @param {number} minSquaredDistance Minimum squared distance. - * @param {Array.<number>=} opt_tmpPoint Temporary point object. - * @return {number} Minimum squared distance. - */ -ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset, - endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance, - opt_tmpPoint) { - var tmpPoint = goog.isDef(opt_tmpPoint) ? opt_tmpPoint : [NaN, NaN]; - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - minSquaredDistance = ol.geom.flat.closest.getsClosestPoint( - flatCoordinates, offset, ends, stride, - maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint); - offset = ends[ends.length - 1]; - } - return minSquaredDistance; -}; - -goog.provide('ol.geom.flat.deflate'); - -goog.require('goog.asserts'); - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} stride Stride. - * @return {number} offset Offset. - */ -ol.geom.flat.deflate.coordinate = - function(flatCoordinates, offset, coordinate, stride) { - goog.asserts.assert(coordinate.length == stride); - var i, ii; - for (i = 0, ii = coordinate.length; i < ii; ++i) { - flatCoordinates[offset++] = coordinate[i]; - } - return offset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {number} stride Stride. - * @return {number} offset Offset. - */ -ol.geom.flat.deflate.coordinates = - function(flatCoordinates, offset, coordinates, stride) { - var i, ii; - for (i = 0, ii = coordinates.length; i < ii; ++i) { - var coordinate = coordinates[i]; - goog.asserts.assert(coordinate.length == stride); - var j; - for (j = 0; j < stride; ++j) { - flatCoordinates[offset++] = coordinate[j]; - } - } - return offset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess. - * @param {number} stride Stride. - * @param {Array.<number>=} opt_ends Ends. - * @return {Array.<number>} Ends. - */ -ol.geom.flat.deflate.coordinatess = - function(flatCoordinates, offset, coordinatess, stride, opt_ends) { - var ends = goog.isDef(opt_ends) ? opt_ends : []; - var i = 0; - var j, jj; - for (j = 0, jj = coordinatess.length; j < jj; ++j) { - var end = ol.geom.flat.deflate.coordinates( - flatCoordinates, offset, coordinatess[j], stride); - ends[i++] = end; - offset = end; - } - ends.length = i; - return ends; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss. - * @param {number} stride Stride. - * @param {Array.<Array.<number>>=} opt_endss Endss. - * @return {Array.<Array.<number>>} Endss. - */ -ol.geom.flat.deflate.coordinatesss = - function(flatCoordinates, offset, coordinatesss, stride, opt_endss) { - var endss = goog.isDef(opt_endss) ? opt_endss : []; - var i = 0; - var j, jj; - for (j = 0, jj = coordinatesss.length; j < jj; ++j) { - var ends = ol.geom.flat.deflate.coordinatess( - flatCoordinates, offset, coordinatesss[j], stride, endss[i]); - endss[i++] = ends; - offset = ends[ends.length - 1]; - } - endss.length = i; - return endss; -}; - -goog.provide('ol.geom.flat.inflate'); - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates. - * @return {Array.<ol.Coordinate>} Coordinates. - */ -ol.geom.flat.inflate.coordinates = - function(flatCoordinates, offset, end, stride, opt_coordinates) { - var coordinates = goog.isDef(opt_coordinates) ? opt_coordinates : []; - var i = 0; - var j; - for (j = offset; j < end; j += stride) { - coordinates[i++] = flatCoordinates.slice(j, j + stride); - } - coordinates.length = i; - return coordinates; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess. - * @return {Array.<Array.<ol.Coordinate>>} Coordinatess. - */ -ol.geom.flat.inflate.coordinatess = - function(flatCoordinates, offset, ends, stride, opt_coordinatess) { - var coordinatess = goog.isDef(opt_coordinatess) ? opt_coordinatess : []; - var i = 0; - var j, jj; - for (j = 0, jj = ends.length; j < jj; ++j) { - var end = ends[j]; - coordinatess[i++] = ol.geom.flat.inflate.coordinates( - flatCoordinates, offset, end, stride, coordinatess[i]); - offset = end; +/** @override */ +goog.crypt.Md5.prototype.update = function(bytes, opt_length) { + if (!goog.isDef(opt_length)) { + opt_length = bytes.length; } - coordinatess.length = i; - return coordinatess; -}; - + var lengthMinusBlock = opt_length - this.blockSize; -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss - * Coordinatesss. - * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss. - */ -ol.geom.flat.inflate.coordinatesss = - function(flatCoordinates, offset, endss, stride, opt_coordinatesss) { - var coordinatesss = goog.isDef(opt_coordinatesss) ? opt_coordinatesss : []; + // Copy some object properties to local variables in order to save on access + // time from inside the loop (~10% speedup was observed on Chrome 11). + var block = this.block_; + var blockLength = this.blockLength_; var i = 0; - var j, jj; - for (j = 0, jj = endss.length; j < jj; ++j) { - var ends = endss[j]; - coordinatesss[i++] = ol.geom.flat.inflate.coordinatess( - flatCoordinates, offset, ends, stride, coordinatesss[i]); - offset = ends[ends.length - 1]; - } - coordinatesss.length = i; - return coordinatesss; -}; - -// Based on simplify-js https://github.com/mourner/simplify-js -// Copyright (c) 2012, Vladimir Agafonkin -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -goog.provide('ol.geom.flat.simplify'); - -goog.require('ol.math'); - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} squaredTolerance Squared tolerance. - * @param {boolean} highQuality Highest quality. - * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat - * coordinates. - * @return {Array.<number>} Simplified line string. - */ -ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end, - stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) { - var simplifiedFlatCoordinates = goog.isDef(opt_simplifiedFlatCoordinates) ? - opt_simplifiedFlatCoordinates : []; - if (!highQuality) { - end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end, - stride, squaredTolerance, - simplifiedFlatCoordinates, 0); - flatCoordinates = simplifiedFlatCoordinates; - offset = 0; - stride = 2; - } - simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( - flatCoordinates, offset, end, stride, squaredTolerance, - simplifiedFlatCoordinates, 0); - return simplifiedFlatCoordinates; -}; - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} squaredTolerance Squared tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end, - stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { - var n = (end - offset) / stride; - if (n < 3) { - for (; offset < end; offset += stride) { - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset]; - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + 1]; - } - return simplifiedOffset; - } - /** @type {Array.<number>} */ - var markers = new Array(n); - markers[0] = 1; - markers[n - 1] = 1; - /** @type {Array.<number>} */ - var stack = [offset, end - stride]; - var index = 0; - var i; - while (stack.length > 0) { - var last = stack.pop(); - var first = stack.pop(); - var maxSquaredDistance = 0; - var x1 = flatCoordinates[first]; - var y1 = flatCoordinates[first + 1]; - var x2 = flatCoordinates[last]; - var y2 = flatCoordinates[last + 1]; - for (i = first + stride; i < last; i += stride) { - var x = flatCoordinates[i]; - var y = flatCoordinates[i + 1]; - var squaredDistance = ol.math.squaredSegmentDistance( - x, y, x1, y1, x2, y2); - if (squaredDistance > maxSquaredDistance) { - index = i; - maxSquaredDistance = squaredDistance; + // The outer while loop should execute at most twice. + while (i < opt_length) { + // When we have no data in the block to top up, we can directly process the + // input buffer (assuming it contains sufficient data). This gives ~30% + // speedup on Chrome 14 and ~70% speedup on Firefox 6.0, but requires that + // the data is provided in large chunks (or in multiples of 64 bytes). + if (blockLength == 0) { + while (i <= lengthMinusBlock) { + this.compress_(bytes, i); + i += this.blockSize; } } - if (maxSquaredDistance > squaredTolerance) { - markers[(index - offset) / stride] = 1; - if (first + stride < index) { - stack.push(first, index); + + if (goog.isString(bytes)) { + while (i < opt_length) { + block[blockLength++] = bytes.charCodeAt(i++); + if (blockLength == this.blockSize) { + this.compress_(block); + blockLength = 0; + // Jump to the outer loop so we use the full-block optimization. + break; + } } - if (index + stride < last) { - stack.push(index, last); + } else { + while (i < opt_length) { + block[blockLength++] = bytes[i++]; + if (blockLength == this.blockSize) { + this.compress_(block); + blockLength = 0; + // Jump to the outer loop so we use the full-block optimization. + break; + } } - } - } - for (i = 0; i < n; ++i) { - if (markers[i]) { - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + i * stride]; - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + i * stride + 1]; - } - } - return simplifiedOffset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {number} squaredTolerance Squared tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @param {Array.<number>} simplifiedEnds Simplified ends. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset, - ends, stride, squaredTolerance, simplifiedFlatCoordinates, - simplifiedOffset, simplifiedEnds) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - simplifiedOffset = ol.geom.flat.simplify.douglasPeucker( - flatCoordinates, offset, end, stride, squaredTolerance, - simplifiedFlatCoordinates, simplifiedOffset); - simplifiedEnds.push(simplifiedOffset); - offset = end; - } - return simplifiedOffset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {number} squaredTolerance Squared tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.douglasPeuckerss = function( - flatCoordinates, offset, endss, stride, squaredTolerance, - simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - var simplifiedEnds = []; - simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers( - flatCoordinates, offset, ends, stride, squaredTolerance, - simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); - simplifiedEndss.push(simplifiedEnds); - offset = ends[ends.length - 1]; - } - return simplifiedOffset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} squaredTolerance Squared tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end, - stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) { - if (end <= offset + stride) { - // zero or one point, no simplification possible, so copy and return - for (; offset < end; offset += stride) { - simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset]; - simplifiedFlatCoordinates[simplifiedOffset++] = - flatCoordinates[offset + 1]; - } - return simplifiedOffset; - } - var x1 = flatCoordinates[offset]; - var y1 = flatCoordinates[offset + 1]; - // copy first point - simplifiedFlatCoordinates[simplifiedOffset++] = x1; - simplifiedFlatCoordinates[simplifiedOffset++] = y1; - var x2 = x1; - var y2 = y1; - for (offset += stride; offset < end; offset += stride) { - x2 = flatCoordinates[offset]; - y2 = flatCoordinates[offset + 1]; - if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) { - // copy point at offset - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - x1 = x2; - y1 = y2; - } - } - if (x2 != x1 || y2 != y1) { - // copy last point - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - } - return simplifiedOffset; -}; - - -/** - * @param {number} value Value. - * @param {number} tolerance Tolerance. - * @return {number} Rounded value. - */ -ol.geom.flat.simplify.snap = function(value, tolerance) { - return tolerance * Math.round(value / tolerance); -}; - - -/** - * Simplifies a line string using an algorithm designed by Tim Schaub. - * Coordinates are snapped to the nearest value in a virtual grid and - * consecutive duplicate coordinates are discarded. This effectively preserves - * topology as the simplification of any subsection of a line string is - * independent of the rest of the line string. This means that, for examples, - * the common edge between two polygons will be simplified to the same line - * string independently in both polygons. This implementation uses a single - * pass over the coordinates and eliminates intermediate collinear points. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} tolerance Tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride, - tolerance, simplifiedFlatCoordinates, simplifiedOffset) { - // do nothing if the line is empty - if (offset == end) { - return simplifiedOffset; - } - // snap the first coordinate (P1) - var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); - var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); - offset += stride; - // add the first coordinate to the output - simplifiedFlatCoordinates[simplifiedOffset++] = x1; - simplifiedFlatCoordinates[simplifiedOffset++] = y1; - // find the next coordinate that does not snap to the same value as the first - // coordinate (P2) - var x2, y2; - do { - x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); - y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); - offset += stride; - if (offset == end) { - // all coordinates snap to the same value, the line collapses to a point - // push the last snapped value anyway to ensure that the output contains - // at least two points - // FIXME should we really return at least two points anyway? - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - return simplifiedOffset; - } - } while (x2 == x1 && y2 == y1); - while (offset < end) { - var x3, y3; - // snap the next coordinate (P3) - x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance); - y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance); - offset += stride; - // skip P3 if it is equal to P2 - if (x3 == x2 && y3 == y2) { - continue; - } - // calculate the delta between P1 and P2 - var dx1 = x2 - x1; - var dy1 = y2 - y1; - // calculate the delta between P3 and P1 - var dx2 = x3 - x1; - var dy2 = y3 - y1; - // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from - // P1 in the same direction then P2 is on the straight line between P1 and - // P3 - if ((dx1 * dy2 == dy1 * dx2) && - ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) && - ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) { - // discard P2 and set P2 = P3 - x2 = x3; - y2 = y3; - continue; - } - // either P1, P2, and P3 are not colinear, or they are colinear but P3 is - // between P3 and P1 or on the opposite half of the line to P2. add P2, - // and continue with P1 = P2 and P2 = P3 - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - x1 = x2; - y1 = y2; - x2 = x3; - y2 = y3; - } - // add the last point (P2) - simplifiedFlatCoordinates[simplifiedOffset++] = x2; - simplifiedFlatCoordinates[simplifiedOffset++] = y2; - return simplifiedOffset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {number} tolerance Tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @param {Array.<number>} simplifiedEnds Simplified ends. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.quantizes = function( - flatCoordinates, offset, ends, stride, - tolerance, - simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - simplifiedOffset = ol.geom.flat.simplify.quantize( - flatCoordinates, offset, end, stride, - tolerance, - simplifiedFlatCoordinates, simplifiedOffset); - simplifiedEnds.push(simplifiedOffset); - offset = end; - } - return simplifiedOffset; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {number} tolerance Tolerance. - * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat - * coordinates. - * @param {number} simplifiedOffset Simplified offset. - * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss. - * @return {number} Simplified offset. - */ -ol.geom.flat.simplify.quantizess = function( - flatCoordinates, offset, endss, stride, - tolerance, - simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) { - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - var simplifiedEnds = []; - simplifiedOffset = ol.geom.flat.simplify.quantizes( - flatCoordinates, offset, ends, stride, - tolerance, - simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds); - simplifiedEndss.push(simplifiedEnds); - offset = ends[ends.length - 1]; - } - return simplifiedOffset; -}; - -goog.provide('ol.geom.LinearRing'); - -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.area'); -goog.require('ol.geom.flat.closest'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.geom.flat.simplify'); - - - -/** - * @classdesc - * Linear ring geometry. Only used as part of polygon; cannot be rendered - * on its own. - * - * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable - */ -ol.geom.LinearRing = function(coordinates, opt_layout) { - - goog.base(this); - - /** - * @private - * @type {number} - */ - this.maxDelta_ = -1; - - /** - * @private - * @type {number} - */ - this.maxDeltaRevision_ = -1; - - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); + } + } + this.blockLength_ = blockLength; + this.totalLength_ += opt_length; }; -goog.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry); - -/** - * Make a complete copy of the geometry. - * @return {!ol.geom.LinearRing} Clone. - * @api stable - */ -ol.geom.LinearRing.prototype.clone = function() { - var linearRing = new ol.geom.LinearRing(null); - linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); - return linearRing; -}; +/** @override */ +goog.crypt.Md5.prototype.digest = function() { + // This must accommodate at least 1 padding byte (0x80), 8 bytes of + // total bitlength, and must end at a 64-byte boundary. + var pad = new Array((this.blockLength_ < 56 ? + this.blockSize : + this.blockSize * 2) - this.blockLength_); -/** - * @inheritDoc - */ -ol.geom.LinearRing.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; + // Add padding: 0x80 0x00* + pad[0] = 0x80; + for (var i = 1; i < pad.length - 8; ++i) { + pad[i] = 0; } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); + // Add the total number of bits, little endian 64-bit integer. + var totalBits = this.totalLength_ * 8; + for (var i = pad.length - 8; i < pad.length; ++i) { + pad[i] = totalBits & 0xff; + totalBits /= 0x100; // Don't use bit-shifting here! } - return ol.geom.flat.closest.getClosestPoint( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); -}; - - -/** - * @return {number} Area (on projected plane). - * @api stable - */ -ol.geom.LinearRing.prototype.getArea = function() { - return ol.geom.flat.area.linearRing( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); -}; - - -/** - * @return {Array.<ol.Coordinate>} Coordinates. - * @api stable - */ -ol.geom.LinearRing.prototype.getCoordinates = function() { - return ol.geom.flat.inflate.coordinates( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); -}; - + this.update(pad); -/** - * @inheritDoc - */ -ol.geom.LinearRing.prototype.getSimplifiedGeometryInternal = - function(squaredTolerance) { - var simplifiedFlatCoordinates = []; - simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - squaredTolerance, simplifiedFlatCoordinates, 0); - var simplifiedLinearRing = new ol.geom.LinearRing(null); - simplifiedLinearRing.setFlatCoordinates( - ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); - return simplifiedLinearRing; + var digest = new Array(16); + var n = 0; + for (var i = 0; i < 4; ++i) { + for (var j = 0; j < 32; j += 8) { + digest[n++] = (this.chain_[i] >>> j) & 0xff; + } + } + return digest; }; +goog.provide('ol.structs.IHasChecksum'); -/** - * @inheritDoc - * @api stable - */ -ol.geom.LinearRing.prototype.getType = function() { - return ol.geom.GeometryType.LINEAR_RING; -}; /** - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @interface */ -ol.geom.LinearRing.prototype.setCoordinates = - function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); - } else { - this.setLayout(opt_layout, coordinates, 1); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; - } - this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( - this.flatCoordinates, 0, coordinates, this.stride); - this.changed(); - } +ol.structs.IHasChecksum = function() { }; /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. + * @return {string} The checksum. */ -ol.geom.LinearRing.prototype.setFlatCoordinates = - function(layout, flatCoordinates) { - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.changed(); +ol.structs.IHasChecksum.prototype.getChecksum = function() { }; -goog.provide('ol.geom.Point'); +goog.provide('ol.style.Stroke'); -goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.math'); +goog.require('goog.crypt'); +goog.require('goog.crypt.Md5'); +goog.require('ol.color'); +goog.require('ol.structs.IHasChecksum'); /** * @classdesc - * Point geometry. + * Set stroke style for vector features. + * Note that the defaults given are the Canvas defaults, which will be used if + * option is not defined. The `get` functions return whatever was entered in + * the options; they will not return the default. * * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {ol.Coordinate} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable - */ -ol.geom.Point = function(coordinates, opt_layout) { - goog.base(this); - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); -}; -goog.inherits(ol.geom.Point, ol.geom.SimpleGeometry); - - -/** - * Make a complete copy of the geometry. - * @return {!ol.geom.Point} Clone. - * @api stable + * @param {olx.style.StrokeOptions=} opt_options Options. + * @implements {ol.structs.IHasChecksum} + * @api */ -ol.geom.Point.prototype.clone = function() { - var point = new ol.geom.Point(null); - point.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); - return point; -}; +ol.style.Stroke = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * @inheritDoc - */ -ol.geom.Point.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - var flatCoordinates = this.flatCoordinates; - var squaredDistance = ol.math.squaredDistance( - x, y, flatCoordinates[0], flatCoordinates[1]); - if (squaredDistance < minSquaredDistance) { - var stride = this.stride; - var i; - for (i = 0; i < stride; ++i) { - closestPoint[i] = flatCoordinates[i]; - } - closestPoint.length = stride; - return squaredDistance; - } else { - return minSquaredDistance; - } -}; + /** + * @private + * @type {ol.Color|string} + */ + this.color_ = goog.isDef(options.color) ? options.color : null; + /** + * @private + * @type {string|undefined} + */ + this.lineCap_ = options.lineCap; -/** - * @return {ol.Coordinate} Coordinates. - * @api stable - */ -ol.geom.Point.prototype.getCoordinates = function() { - return goog.isNull(this.flatCoordinates) ? [] : this.flatCoordinates.slice(); -}; + /** + * @private + * @type {Array.<number>} + */ + this.lineDash_ = goog.isDef(options.lineDash) ? options.lineDash : null; + /** + * @private + * @type {string|undefined} + */ + this.lineJoin_ = options.lineJoin; -/** - * @inheritDoc - */ -ol.geom.Point.prototype.getExtent = function(opt_extent) { - if (this.extentRevision != this.getRevision()) { - this.extent = ol.extent.createOrUpdateFromCoordinate( - this.flatCoordinates, this.extent); - this.extentRevision = this.getRevision(); - } - goog.asserts.assert(goog.isDef(this.extent)); - return ol.extent.returnOrUpdate(this.extent, opt_extent); -}; + /** + * @private + * @type {number|undefined} + */ + this.miterLimit_ = options.miterLimit; + /** + * @private + * @type {number|undefined} + */ + this.width_ = options.width; -/** - * @inheritDoc - * @api stable - */ -ol.geom.Point.prototype.getType = function() { - return ol.geom.GeometryType.POINT; + /** + * @private + * @type {string|undefined} + */ + this.checksum_ = undefined; }; /** - * @inheritDoc + * Get the stroke color. + * @return {ol.Color|string} Color. * @api */ -ol.geom.Point.prototype.intersectsExtent = function(extent) { - return ol.extent.containsXY(extent, - this.flatCoordinates[0], this.flatCoordinates[1]); -}; - - -/** - * @param {ol.Coordinate} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable - */ -ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); - } else { - this.setLayout(opt_layout, coordinates, 0); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; - } - this.flatCoordinates.length = ol.geom.flat.deflate.coordinate( - this.flatCoordinates, 0, coordinates, this.stride); - this.changed(); - } +ol.style.Stroke.prototype.getColor = function() { + return this.color_; }; /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. + * Get the line cap type for the stroke. + * @return {string|undefined} Line cap. + * @api */ -ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) { - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.changed(); +ol.style.Stroke.prototype.getLineCap = function() { + return this.lineCap_; }; -goog.provide('ol.geom.flat.contains'); - -goog.require('goog.asserts'); -goog.require('ol.extent'); - /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {ol.Extent} extent Extent. - * @return {boolean} Contains extent. + * Get the line dash style for the stroke. + * @return {Array.<number>} Line dash. + * @api */ -ol.geom.flat.contains.linearRingContainsExtent = - function(flatCoordinates, offset, end, stride, extent) { - var outside = ol.extent.forEachCorner(extent, - /** - * @param {ol.Coordinate} coordinate Coordinate. - */ - function(coordinate) { - return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates, - offset, end, stride, coordinate[0], coordinate[1]); - }); - return !outside; +ol.style.Stroke.prototype.getLineDash = function() { + return this.lineDash_; }; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). + * Get the line join type for the stroke. + * @return {string|undefined} Line join. + * @api */ -ol.geom.flat.contains.linearRingContainsXY = - function(flatCoordinates, offset, end, stride, x, y) { - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - var contains = false; - var x1 = flatCoordinates[end - stride]; - var y1 = flatCoordinates[end - stride + 1]; - for (; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - var intersect = ((y1 > y) != (y2 > y)) && - (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1); - if (intersect) { - contains = !contains; - } - x1 = x2; - y1 = y2; - } - return contains; +ol.style.Stroke.prototype.getLineJoin = function() { + return this.lineJoin_; }; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). + * Get the miter limit for the stroke. + * @return {number|undefined} Miter limit. + * @api */ -ol.geom.flat.contains.linearRingsContainsXY = - function(flatCoordinates, offset, ends, stride, x, y) { - goog.asserts.assert(ends.length > 0); - if (ends.length === 0) { - return false; - } - if (!ol.geom.flat.contains.linearRingContainsXY( - flatCoordinates, offset, ends[0], stride, x, y)) { - return false; - } - var i, ii; - for (i = 1, ii = ends.length; i < ii; ++i) { - if (ol.geom.flat.contains.linearRingContainsXY( - flatCoordinates, ends[i - 1], ends[i], stride, x, y)) { - return false; - } - } - return true; +ol.style.Stroke.prototype.getMiterLimit = function() { + return this.miterLimit_; }; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {number} x X. - * @param {number} y Y. - * @return {boolean} Contains (x, y). + * Get the stroke width. + * @return {number|undefined} Width. + * @api */ -ol.geom.flat.contains.linearRingssContainsXY = - function(flatCoordinates, offset, endss, stride, x, y) { - goog.asserts.assert(endss.length > 0); - if (endss.length === 0) { - return false; - } - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - if (ol.geom.flat.contains.linearRingsContainsXY( - flatCoordinates, offset, ends, stride, x, y)) { - return true; - } - offset = ends[ends.length - 1]; - } - return false; +ol.style.Stroke.prototype.getWidth = function() { + return this.width_; }; -goog.provide('ol.geom.flat.interiorpoint'); - -goog.require('goog.asserts'); -goog.require('ol.geom.flat.contains'); - /** - * Calculates a point that is likely to lie in the interior of the linear rings. - * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {Array.<number>} flatCenters Flat centers. - * @param {number} flatCentersOffset Flat center offset. - * @param {Array.<number>=} opt_dest Destination. - * @return {Array.<number>} Destination. + * Set the color. + * + * @param {ol.Color|string} color Color. + * @api */ -ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset, - ends, stride, flatCenters, flatCentersOffset, opt_dest) { - var i, ii, x, x1, x2, y1, y2; - var y = flatCenters[flatCentersOffset + 1]; - /** @type {Array.<number>} */ - var intersections = []; - // Calculate intersections with the horizontal line - var end = ends[0]; - x1 = flatCoordinates[end - stride]; - y1 = flatCoordinates[end - stride + 1]; - for (i = offset; i < end; i += stride) { - x2 = flatCoordinates[i]; - y2 = flatCoordinates[i + 1]; - if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) { - x = (y - y1) / (y2 - y1) * (x2 - x1) + x1; - intersections.push(x); - } - x1 = x2; - y1 = y2; - } - // Find the longest segment of the horizontal line that has its center point - // inside the linear ring. - var pointX = NaN; - var maxSegmentLength = -Infinity; - intersections.sort(); - x1 = intersections[0]; - for (i = 1, ii = intersections.length; i < ii; ++i) { - x2 = intersections[i]; - var segmentLength = Math.abs(x2 - x1); - if (segmentLength > maxSegmentLength) { - x = (x1 + x2) / 2; - if (ol.geom.flat.contains.linearRingsContainsXY( - flatCoordinates, offset, ends, stride, x, y)) { - pointX = x; - maxSegmentLength = segmentLength; - } - } - x1 = x2; - } - if (isNaN(pointX)) { - // There is no horizontal line that has its center point inside the linear - // ring. Use the center of the the linear ring's extent. - pointX = flatCenters[flatCentersOffset]; - } - if (goog.isDef(opt_dest)) { - opt_dest.push(pointX, y); - return opt_dest; - } else { - return [pointX, y]; - } +ol.style.Stroke.prototype.setColor = function(color) { + this.color_ = color; + this.checksum_ = undefined; }; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {Array.<number>} flatCenters Flat centers. - * @return {Array.<number>} Interior points. + * Set the line cap. + * + * @param {string|undefined} lineCap Line cap. + * @api */ -ol.geom.flat.interiorpoint.linearRingss = - function(flatCoordinates, offset, endss, stride, flatCenters) { - goog.asserts.assert(2 * endss.length == flatCenters.length); - var interiorPoints = []; - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates, - offset, ends, stride, flatCenters, 2 * i, interiorPoints); - offset = ends[ends.length - 1]; - } - return interiorPoints; +ol.style.Stroke.prototype.setLineCap = function(lineCap) { + this.lineCap_ = lineCap; + this.checksum_ = undefined; }; -goog.provide('ol.geom.flat.segments'); - /** - * This function calls `callback` for each segment of the flat coordinates - * array. If the callback returns a truthy value the function returns that - * value immediately. Otherwise the function returns `false`. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {function(ol.Coordinate, ol.Coordinate): T} callback Function - * called for each segment. - * @return {T|boolean} Value. - * @template T - */ -ol.geom.flat.segments.forEach = - function(flatCoordinates, offset, end, stride, callback) { - var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - var point2 = []; - var ret; - for (; (offset + stride) < end; offset += stride) { - point2[0] = flatCoordinates[offset + stride]; - point2[1] = flatCoordinates[offset + stride + 1]; - ret = callback(point1, point2); - if (ret) { - return ret; - } - point1[0] = point2[0]; - point1[1] = point2[1]; - } - return false; + * Set the line dash. + * + * @param {Array.<number>} lineDash Line dash. + * @api + */ +ol.style.Stroke.prototype.setLineDash = function(lineDash) { + this.lineDash_ = lineDash; + this.checksum_ = undefined; }; -goog.provide('ol.geom.flat.intersectsextent'); -goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.flat.contains'); -goog.require('ol.geom.flat.segments'); +/** + * Set the line join. + * + * @param {string|undefined} lineJoin Line join. + * @api + */ +ol.style.Stroke.prototype.setLineJoin = function(lineJoin) { + this.lineJoin_ = lineJoin; + this.checksum_ = undefined; +}; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {ol.Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. + * Set the miter limit. + * + * @param {number|undefined} miterLimit Miter limit. + * @api */ -ol.geom.flat.intersectsextent.lineString = - function(flatCoordinates, offset, end, stride, extent) { - var coordinatesExtent = ol.extent.extendFlatCoordinates( - ol.extent.createEmpty(), flatCoordinates, offset, end, stride); - if (!ol.extent.intersects(extent, coordinatesExtent)) { - return false; - } - if (ol.extent.containsExtent(extent, coordinatesExtent)) { - return true; - } - if (coordinatesExtent[0] >= extent[0] && - coordinatesExtent[2] <= extent[2]) { - return true; - } - if (coordinatesExtent[1] >= extent[1] && - coordinatesExtent[3] <= extent[3]) { - return true; - } - return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride, - /** - * @param {ol.Coordinate} point1 Start point. - * @param {ol.Coordinate} point2 End point. - * @return {boolean} `true` if the segment and the extent intersect, - * `false` otherwise. - */ - function(point1, point2) { - return ol.extent.intersectsSegment(extent, point1, point2); - }); +ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) { + this.miterLimit_ = miterLimit; + this.checksum_ = undefined; }; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {ol.Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. + * Set the width. + * + * @param {number|undefined} width Width. + * @api */ -ol.geom.flat.intersectsextent.lineStrings = - function(flatCoordinates, offset, ends, stride, extent) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - if (ol.geom.flat.intersectsextent.lineString( - flatCoordinates, offset, ends[i], stride, extent)) { - return true; - } - offset = ends[i]; - } - return false; +ol.style.Stroke.prototype.setWidth = function(width) { + this.width_ = width; + this.checksum_ = undefined; }; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {ol.Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. + * @inheritDoc */ -ol.geom.flat.intersectsextent.linearRing = - function(flatCoordinates, offset, end, stride, extent) { - if (ol.geom.flat.intersectsextent.lineString( - flatCoordinates, offset, end, stride, extent)) { - return true; - } - if (ol.geom.flat.contains.linearRingContainsXY( - flatCoordinates, offset, end, stride, extent[0], extent[1])) { - return true; - } - if (ol.geom.flat.contains.linearRingContainsXY( - flatCoordinates, offset, end, stride, extent[0], extent[3])) { - return true; - } - if (ol.geom.flat.contains.linearRingContainsXY( - flatCoordinates, offset, end, stride, extent[2], extent[1])) { - return true; - } - if (ol.geom.flat.contains.linearRingContainsXY( - flatCoordinates, offset, end, stride, extent[2], extent[3])) { - return true; +ol.style.Stroke.prototype.getChecksum = function() { + if (!goog.isDef(this.checksum_)) { + var raw = 's' + + (!goog.isNull(this.color_) ? + ol.color.asString(this.color_) : '-') + ',' + + (goog.isDef(this.lineCap_) ? + this.lineCap_.toString() : '-') + ',' + + (!goog.isNull(this.lineDash_) ? + this.lineDash_.toString() : '-') + ',' + + (goog.isDef(this.lineJoin_) ? + this.lineJoin_ : '-') + ',' + + (goog.isDef(this.miterLimit_) ? + this.miterLimit_.toString() : '-') + ',' + + (goog.isDef(this.width_) ? + this.width_.toString() : '-'); + + var md5 = new goog.crypt.Md5(); + md5.update(raw); + this.checksum_ = goog.crypt.byteArrayToString(md5.digest()); } - return false; + + return this.checksum_; }; +goog.provide('ol.render.canvas'); + /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {ol.Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. + * @typedef {{fillStyle: string}} */ -ol.geom.flat.intersectsextent.linearRings = - function(flatCoordinates, offset, ends, stride, extent) { - goog.asserts.assert(ends.length > 0); - if (!ol.geom.flat.intersectsextent.linearRing( - flatCoordinates, offset, ends[0], stride, extent)) { - return false; - } - if (ends.length === 1) { - return true; - } - var i, ii; - for (i = 1, ii = ends.length; i < ii; ++i) { - if (ol.geom.flat.contains.linearRingContainsExtent( - flatCoordinates, ends[i - 1], ends[i], stride, extent)) { - return false; - } - } - return true; -}; +ol.render.canvas.FillState; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @param {ol.Extent} extent Extent. - * @return {boolean} True if the geometry and the extent intersect. + * @typedef {{lineCap: string, + * lineDash: Array.<number>, + * lineJoin: string, + * lineWidth: number, + * miterLimit: number, + * strokeStyle: string}} */ -ol.geom.flat.intersectsextent.linearRingss = - function(flatCoordinates, offset, endss, stride, extent) { - goog.asserts.assert(endss.length > 0); - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - if (ol.geom.flat.intersectsextent.linearRings( - flatCoordinates, offset, ends, stride, extent)) { - return true; - } - offset = ends[ends.length - 1]; - } - return false; -}; +ol.render.canvas.StrokeState; -goog.provide('ol.geom.flat.reverse'); + +/** + * @typedef {{font: string, + * textAlign: string, + * textBaseline: string}} + */ +ol.render.canvas.TextState; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. + * @const + * @type {string} */ -ol.geom.flat.reverse.coordinates = - function(flatCoordinates, offset, end, stride) { - while (offset < end - stride) { - var i; - for (i = 0; i < stride; ++i) { - var tmp = flatCoordinates[offset + i]; - flatCoordinates[offset + i] = flatCoordinates[end - stride + i]; - flatCoordinates[end - stride + i] = tmp; - } - offset += stride; - end -= stride; - } -}; +ol.render.canvas.defaultFont = '10px sans-serif'; -goog.provide('ol.geom.flat.orient'); -goog.require('ol.geom.flat.reverse'); +/** + * @const + * @type {ol.Color} + */ +ol.render.canvas.defaultFillStyle = [0, 0, 0, 1]; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {boolean} Is clockwise. + * @const + * @type {string} */ -ol.geom.flat.orient.linearRingIsClockwise = - function(flatCoordinates, offset, end, stride) { - // http://tinyurl.com/clockwise-method - // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp - var edge = 0; - var x1 = flatCoordinates[end - stride]; - var y1 = flatCoordinates[end - stride + 1]; - for (; offset < end; offset += stride) { - var x2 = flatCoordinates[offset]; - var y2 = flatCoordinates[offset + 1]; - edge += (x2 - x1) * (y2 + y1); - x1 = x2; - y1 = y2; - } - return edge > 0; -}; +ol.render.canvas.defaultLineCap = 'round'; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @return {boolean} `true` if all rings are correctly oriented, `false` - * otherwise. + * @const + * @type {Array.<number>} */ -ol.geom.flat.orient.linearRingsAreOriented = - function(flatCoordinates, offset, ends, stride) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( - flatCoordinates, offset, end, stride); - if (i === 0 ? !isClockwise : isClockwise) { - return false; - } - offset = end; - } - return true; -}; +ol.render.canvas.defaultLineDash = []; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @return {boolean} `true` if all rings are correctly oriented, `false` - * otherwise. + * @const + * @type {string} */ -ol.geom.flat.linearRingssAreOriented = - function(flatCoordinates, offset, endss, stride) { - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - if (!ol.geom.flat.orient.linearRingsAreOriented( - flatCoordinates, offset, endss[i], stride)) { - return false; - } - } - return true; -}; +ol.render.canvas.defaultLineJoin = 'round'; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @return {number} End. + * @const + * @type {number} */ -ol.geom.flat.orient.orientLinearRings = - function(flatCoordinates, offset, ends, stride) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var isClockwise = ol.geom.flat.orient.linearRingIsClockwise( - flatCoordinates, offset, end, stride); - var reverse = i === 0 ? !isClockwise : isClockwise; - if (reverse) { - ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride); - } - offset = end; - } - return offset; -}; +ol.render.canvas.defaultMiterLimit = 10; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @return {number} End. + * @const + * @type {ol.Color} */ -ol.geom.flat.orient.orientLinearRingss = - function(flatCoordinates, offset, endss, stride) { - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - offset = ol.geom.flat.orient.orientLinearRings( - flatCoordinates, offset, endss[i], stride); - } - return offset; -}; +ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1]; -goog.provide('ol.geom.Polygon'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LinearRing'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.area'); -goog.require('ol.geom.flat.closest'); -goog.require('ol.geom.flat.contains'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.geom.flat.interiorpoint'); -goog.require('ol.geom.flat.intersectsextent'); -goog.require('ol.geom.flat.orient'); -goog.require('ol.geom.flat.simplify'); +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultTextAlign = 'center'; +/** + * @const + * @type {string} + */ +ol.render.canvas.defaultTextBaseline = 'middle'; + /** - * @classdesc - * Polygon geometry. - * - * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @const + * @type {number} */ -ol.geom.Polygon = function(coordinates, opt_layout) { +ol.render.canvas.defaultLineWidth = 1; - goog.base(this); +goog.provide('ol.style.Fill'); - /** - * @type {Array.<number>} - * @private - */ - this.ends_ = []; +goog.require('ol.color'); +goog.require('ol.structs.IHasChecksum'); - /** - * @private - * @type {number} - */ - this.flatInteriorPointRevision_ = -1; - /** - * @private - * @type {ol.Coordinate} - */ - this.flatInteriorPoint_ = null; - /** - * @private - * @type {number} - */ - this.maxDelta_ = -1; +/** + * @classdesc + * Set fill style for vector features. + * + * @constructor + * @param {olx.style.FillOptions=} opt_options Options. + * @implements {ol.structs.IHasChecksum} + * @api + */ +ol.style.Fill = function(opt_options) { - /** - * @private - * @type {number} - */ - this.maxDeltaRevision_ = -1; + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {number} + * @type {ol.Color|string} */ - this.orientedRevision_ = -1; + this.color_ = goog.isDef(options.color) ? options.color : null; /** * @private - * @type {Array.<number>} + * @type {string|undefined} */ - this.orientedFlatCoordinates_ = null; - - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); - + this.checksum_ = undefined; }; -goog.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry); /** - * @param {ol.geom.LinearRing} linearRing Linear ring. - * @api stable + * Get the fill color. + * @return {ol.Color|string} Color. + * @api */ -ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) { - goog.asserts.assert(linearRing.getLayout() == this.layout); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = linearRing.getFlatCoordinates().slice(); - } else { - goog.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates()); - } - this.ends_.push(this.flatCoordinates.length); - this.changed(); +ol.style.Fill.prototype.getColor = function() { + return this.color_; }; /** - * Make a complete copy of the geometry. - * @return {!ol.geom.Polygon} Clone. - * @api stable + * Set the color. + * + * @param {ol.Color|string} color Color. + * @api */ -ol.geom.Polygon.prototype.clone = function() { - var polygon = new ol.geom.Polygon(null); - polygon.setFlatCoordinates( - this.layout, this.flatCoordinates.slice(), this.ends_.slice()); - return polygon; +ol.style.Fill.prototype.setColor = function(color) { + this.color_ = color; + this.checksum_ = undefined; }; /** * @inheritDoc */ -ol.geom.Polygon.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( - this.flatCoordinates, 0, this.ends_, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); +ol.style.Fill.prototype.getChecksum = function() { + if (!goog.isDef(this.checksum_)) { + this.checksum_ = 'f' + (!goog.isNull(this.color_) ? + ol.color.asString(this.color_) : '-'); } - return ol.geom.flat.closest.getsClosestPoint( - this.flatCoordinates, 0, this.ends_, this.stride, - this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); + + return this.checksum_; }; +goog.provide('ol.style.Circle'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('ol.color'); +goog.require('ol.has'); +goog.require('ol.render.canvas'); +goog.require('ol.structs.IHasChecksum'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Image'); +goog.require('ol.style.ImageState'); +goog.require('ol.style.Stroke'); + + + +/** + * @classdesc + * Set circle style for vector features. + * + * @constructor + * @param {olx.style.CircleOptions=} opt_options Options. + * @extends {ol.style.Image} + * @implements {ol.structs.IHasChecksum} + * @api + */ +ol.style.Circle = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + /** + * @private + * @type {Array.<string>} + */ + this.checksums_ = null; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.hitDetectionCanvas_ = null; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = goog.isDef(options.fill) ? options.fill : null; -/** - * @inheritDoc - */ -ol.geom.Polygon.prototype.containsXY = function(x, y) { - return ol.geom.flat.contains.linearRingsContainsXY( - this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y); -}; + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; + /** + * @private + * @type {number} + */ + this.radius_ = options.radius; -/** - * @return {number} Area (on projected plane). - * @api stable - */ -ol.geom.Polygon.prototype.getArea = function() { - return ol.geom.flat.area.linearRings( - this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride); -}; + /** + * @private + * @type {Array.<number>} + */ + this.origin_ = [0, 0]; + /** + * @private + * @type {Array.<number>} + */ + this.anchor_ = null; -/** - * @return {Array.<Array.<ol.Coordinate>>} Coordinates. - * @api stable - */ -ol.geom.Polygon.prototype.getCoordinates = function() { - return ol.geom.flat.inflate.coordinatess( - this.flatCoordinates, 0, this.ends_, this.stride); -}; + /** + * @private + * @type {ol.Size} + */ + this.size_ = null; + /** + * @private + * @type {ol.Size} + */ + this.imageSize_ = null; -/** - * @return {Array.<number>} Ends. - */ -ol.geom.Polygon.prototype.getEnds = function() { - return this.ends_; -}; + /** + * @private + * @type {ol.Size} + */ + this.hitDetectionImageSize_ = null; + this.render_(options.atlasManager); -/** - * @return {Array.<number>} Interior point. - */ -ol.geom.Polygon.prototype.getFlatInteriorPoint = function() { - if (this.flatInteriorPointRevision_ != this.getRevision()) { - var flatCenter = ol.extent.getCenter(this.getExtent()); - this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings( - this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, - flatCenter, 0); - this.flatInteriorPointRevision_ = this.getRevision(); - } - return this.flatInteriorPoint_; -}; + /** + * @type {boolean} + */ + var snapToPixel = goog.isDef(options.snapToPixel) ? + options.snapToPixel : true; + goog.base(this, { + opacity: 1, + rotateWithView: false, + rotation: 0, + scale: 1, + snapToPixel: snapToPixel + }); -/** - * @return {ol.geom.Point} Interior point. - * @api stable - */ -ol.geom.Polygon.prototype.getInteriorPoint = function() { - return new ol.geom.Point(this.getFlatInteriorPoint()); }; +goog.inherits(ol.style.Circle, ol.style.Image); /** - * Return the number of rings of the polygon, this includes the exterior - * ring and any interior rings. - * - * @return {number} Number of rings. + * @inheritDoc * @api */ -ol.geom.Polygon.prototype.getLinearRingCount = function() { - return this.ends_.length; +ol.style.Circle.prototype.getAnchor = function() { + return this.anchor_; }; /** - * Return the Nth linear ring of the polygon geometry. Return `null` if the - * given index is out of range. - * The exterior linear ring is available at index `0` and the interior rings - * at index `1` and beyond. - * - * @param {number} index Index. - * @return {ol.geom.LinearRing} Linear ring. - * @api stable + * Get the fill style for the circle. + * @return {ol.style.Fill} Fill style. + * @api */ -ol.geom.Polygon.prototype.getLinearRing = function(index) { - goog.asserts.assert(0 <= index && index < this.ends_.length); - if (index < 0 || this.ends_.length <= index) { - return null; - } - var linearRing = new ol.geom.LinearRing(null); - linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice( - index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); - return linearRing; +ol.style.Circle.prototype.getFill = function() { + return this.fill_; }; /** - * @return {Array.<ol.geom.LinearRing>} Linear rings. - * @api stable + * @inheritDoc */ -ol.geom.Polygon.prototype.getLinearRings = function() { - var layout = this.layout; - var flatCoordinates = this.flatCoordinates; - var ends = this.ends_; - var linearRings = []; - var offset = 0; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var linearRing = new ol.geom.LinearRing(null); - linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); - linearRings.push(linearRing); - offset = end; - } - return linearRings; +ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) { + return this.hitDetectionCanvas_; }; /** - * @return {Array.<number>} Oriented flat coordinates. + * Get the image used to render the circle. + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement} Canvas element. + * @api */ -ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() { - if (this.orientedRevision_ != this.getRevision()) { - var flatCoordinates = this.flatCoordinates; - if (ol.geom.flat.orient.linearRingsAreOriented( - flatCoordinates, 0, this.ends_, this.stride)) { - this.orientedFlatCoordinates_ = flatCoordinates; - } else { - this.orientedFlatCoordinates_ = flatCoordinates.slice(); - this.orientedFlatCoordinates_.length = - ol.geom.flat.orient.orientLinearRings( - this.orientedFlatCoordinates_, 0, this.ends_, this.stride); - } - this.orientedRevision_ = this.getRevision(); - } - return this.orientedFlatCoordinates_; +ol.style.Circle.prototype.getImage = function(pixelRatio) { + return this.canvas_; }; /** * @inheritDoc */ -ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = - function(squaredTolerance) { - var simplifiedFlatCoordinates = []; - var simplifiedEnds = []; - simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes( - this.flatCoordinates, 0, this.ends_, this.stride, - Math.sqrt(squaredTolerance), - simplifiedFlatCoordinates, 0, simplifiedEnds); - var simplifiedPolygon = new ol.geom.Polygon(null); - simplifiedPolygon.setFlatCoordinates( - ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); - return simplifiedPolygon; +ol.style.Circle.prototype.getImageState = function() { + return ol.style.ImageState.LOADED; }; /** * @inheritDoc - * @api stable */ -ol.geom.Polygon.prototype.getType = function() { - return ol.geom.GeometryType.POLYGON; +ol.style.Circle.prototype.getImageSize = function() { + return this.imageSize_; }; /** * @inheritDoc - * @api */ -ol.geom.Polygon.prototype.intersectsExtent = function(extent) { - return ol.geom.flat.intersectsextent.linearRings( - this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent); +ol.style.Circle.prototype.getHitDetectionImageSize = function() { + return this.hitDetectionImageSize_; }; /** - * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @inheritDoc + * @api */ -ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); - } else { - this.setLayout(opt_layout, coordinates, 2); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; - } - var ends = ol.geom.flat.deflate.coordinatess( - this.flatCoordinates, 0, coordinates, this.stride, this.ends_); - this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; - this.changed(); - } +ol.style.Circle.prototype.getOrigin = function() { + return this.origin_; }; /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {Array.<number>} ends Ends. + * Get the circle radius. + * @return {number} Radius. + * @api */ -ol.geom.Polygon.prototype.setFlatCoordinates = - function(layout, flatCoordinates, ends) { - if (goog.isNull(flatCoordinates)) { - goog.asserts.assert(!goog.isNull(ends) && ends.length === 0); - } else if (ends.length === 0) { - goog.asserts.assert(flatCoordinates.length === 0); - } else { - goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]); - } - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.ends_ = ends; - this.changed(); +ol.style.Circle.prototype.getRadius = function() { + return this.radius_; }; /** - * Create an approximation of a circle on the surface of a sphere. - * @param {ol.Sphere} sphere The sphere. - * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees). - * @param {number} radius The great-circle distance from the center to - * the polygon vertices. - * @param {number=} opt_n Optional number of vertices for the resulting - * polygon. Default is `32`. - * @return {ol.geom.Polygon} The "circular" polygon. - * @api stable + * @inheritDoc + * @api */ -ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) { - var n = goog.isDef(opt_n) ? opt_n : 32; - /** @type {Array.<number>} */ - var flatCoordinates = []; - var i; - for (i = 0; i < n; ++i) { - goog.array.extend( - flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n)); - } - flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]); - var polygon = new ol.geom.Polygon(null); - polygon.setFlatCoordinates( - ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); - return polygon; +ol.style.Circle.prototype.getSize = function() { + return this.size_; }; /** - * Create a polygon from an extent. The layout used is `XY`. - * @param {ol.Extent} extent The extent. - * @return {ol.geom.Polygon} The polygon. + * Get the stroke style for the circle. + * @return {ol.style.Stroke} Stroke style. * @api */ -ol.geom.Polygon.fromExtent = function(extent) { - var minX = extent[0]; - var minY = extent[1]; - var maxX = extent[2]; - var maxY = extent[3]; - var flatCoordinates = - [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY]; - var polygon = new ol.geom.Polygon(null); - polygon.setFlatCoordinates( - ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]); - return polygon; +ol.style.Circle.prototype.getStroke = function() { + return this.stroke_; }; -// FIXME remove trailing "Geometry" in method names - -goog.provide('ol.render.IVectorContext'); - - /** - * VectorContext interface. Implemented by - * {@link ol.render.canvas.Immediate} and {@link ol.render.webgl.Immediate}. - * @interface + * @inheritDoc */ -ol.render.IVectorContext = function() { -}; +ol.style.Circle.prototype.listenImageChange = goog.nullFunction; /** - * @param {number} zIndex Z index. - * @param {function(ol.render.IVectorContext)} callback Callback. + * @inheritDoc */ -ol.render.IVectorContext.prototype.drawAsync = function(zIndex, callback) { -}; +ol.style.Circle.prototype.load = goog.nullFunction; /** - * @param {ol.geom.Circle} circleGeometry Circle geometry. - * @param {ol.Feature} feature Feature, + * @inheritDoc */ -ol.render.IVectorContext.prototype.drawCircleGeometry = - function(circleGeometry, feature) { -}; +ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction; /** - * @param {ol.Feature} feature Feature. - * @param {ol.style.Style} style Style. + * @typedef {{strokeStyle: (string|undefined), strokeWidth: number, + * size: number, lineDash: Array.<number>}} */ -ol.render.IVectorContext.prototype.drawFeature = function(feature, style) { -}; +ol.style.Circle.RenderOptions; /** - * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry - * collection. - * @param {ol.Feature} feature Feature. + * @private + * @param {ol.style.AtlasManager|undefined} atlasManager */ -ol.render.IVectorContext.prototype.drawGeometryCollectionGeometry = - function(geometryCollectionGeometry, feature) { -}; - +ol.style.Circle.prototype.render_ = function(atlasManager) { + var imageSize; + var lineDash = null; + var strokeStyle; + var strokeWidth = 0; -/** - * @param {ol.geom.Point} pointGeometry Point geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawPointGeometry = - function(pointGeometry, feature) { -}; + if (!goog.isNull(this.stroke_)) { + strokeStyle = ol.color.asString(this.stroke_.getColor()); + strokeWidth = this.stroke_.getWidth(); + if (!goog.isDef(strokeWidth)) { + strokeWidth = ol.render.canvas.defaultLineWidth; + } + lineDash = this.stroke_.getLineDash(); + if (!ol.has.CANVAS_LINE_DASH) { + lineDash = null; + } + } -/** - * @param {ol.geom.LineString} lineStringGeometry Line string geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawLineStringGeometry = - function(lineStringGeometry, feature) { -}; + var size = 2 * (this.radius_ + strokeWidth) + 1; + /** @type {ol.style.Circle.RenderOptions} */ + var renderOptions = { + strokeStyle: strokeStyle, + strokeWidth: strokeWidth, + size: size, + lineDash: lineDash + }; -/** - * @param {ol.geom.MultiLineString} multiLineStringGeometry - * MultiLineString geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawMultiLineStringGeometry = - function(multiLineStringGeometry, feature) { -}; + if (!goog.isDef(atlasManager)) { + // no atlas manager is used, create a new canvas + this.canvas_ = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + this.canvas_.height = size; + this.canvas_.width = size; + // canvas.width and height are rounded to the closest integer + size = this.canvas_.width; + imageSize = size; -/** - * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawMultiPointGeometry = - function(multiPointGeometry, feature) { -}; + // draw the circle on the canvas + var context = /** @type {CanvasRenderingContext2D} */ + (this.canvas_.getContext('2d')); + this.draw_(renderOptions, context, 0, 0); + this.createHitDetectionCanvas_(renderOptions); + } else { + // an atlas manager is used, add the symbol to an atlas + size = Math.round(size); -/** - * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawMultiPolygonGeometry = - function(multiPolygonGeometry, feature) { -}; + var hasCustomHitDetectionImage = goog.isNull(this.fill_); + var renderHitDetectionCallback; + if (hasCustomHitDetectionImage) { + // render the hit-detection image into a separate atlas image + renderHitDetectionCallback = + goog.bind(this.drawHitDetectionCanvas_, this, renderOptions); + } + var id = this.getChecksum(); + var info = atlasManager.add( + id, size, size, goog.bind(this.draw_, this, renderOptions), + renderHitDetectionCallback); + goog.asserts.assert(info !== null, 'circle radius is too large'); -/** - * @param {ol.geom.Polygon} polygonGeometry Polygon geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawPolygonGeometry = - function(polygonGeometry, feature) { -}; + this.canvas_ = info.image; + this.origin_ = [info.offsetX, info.offsetY]; + imageSize = info.image.width; + if (hasCustomHitDetectionImage) { + this.hitDetectionCanvas_ = info.hitImage; + this.hitDetectionImageSize_ = + [info.hitImage.width, info.hitImage.height]; + } else { + this.hitDetectionCanvas_ = this.canvas_; + this.hitDetectionImageSize_ = [imageSize, imageSize]; + } + } -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.Feature} feature Feature. - */ -ol.render.IVectorContext.prototype.drawText = - function(flatCoordinates, offset, end, stride, geometry, feature) { + this.anchor_ = [size / 2, size / 2]; + this.size_ = [size, size]; + this.imageSize_ = [imageSize, imageSize]; }; /** - * @param {ol.style.Fill} fillStyle Fill style. - * @param {ol.style.Stroke} strokeStyle Stroke style. + * @private + * @param {ol.style.Circle.RenderOptions} renderOptions + * @param {CanvasRenderingContext2D} context + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). */ -ol.render.IVectorContext.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { -}; +ol.style.Circle.prototype.draw_ = function(renderOptions, context, x, y) { + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); + // then move to (x, y) + context.translate(x, y); -/** - * @param {ol.style.Image} imageStyle Image style. - */ -ol.render.IVectorContext.prototype.setImageStyle = function(imageStyle) { + context.beginPath(); + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); + + if (!goog.isNull(this.fill_)) { + context.fillStyle = ol.color.asString(this.fill_.getColor()); + context.fill(); + } + if (!goog.isNull(this.stroke_)) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + if (!goog.isNull(renderOptions.lineDash)) { + context.setLineDash(renderOptions.lineDash); + } + context.stroke(); + } + context.closePath(); }; /** - * @param {ol.style.Text} textStyle Text style. + * @private + * @param {ol.style.Circle.RenderOptions} renderOptions */ -ol.render.IVectorContext.prototype.setTextStyle = function(textStyle) { -}; - -goog.provide('ol.render.Event'); -goog.provide('ol.render.EventType'); +ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) { + this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; + if (!goog.isNull(this.fill_)) { + this.hitDetectionCanvas_ = this.canvas_; + return; + } -goog.require('goog.events.Event'); -goog.require('ol.render.IVectorContext'); + // if no fill style is set, create an extra hit-detection image with a + // default fill style + this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + var canvas = this.hitDetectionCanvas_; + canvas.height = renderOptions.size; + canvas.width = renderOptions.size; -/** - * @enum {string} - */ -ol.render.EventType = { - /** - * @event ol.render.Event#postcompose - * @api - */ - POSTCOMPOSE: 'postcompose', - /** - * @event ol.render.Event#precompose - * @api - */ - PRECOMPOSE: 'precompose', - /** - * @event ol.render.Event#render - * @api - */ - RENDER: 'render' + var context = /** @type {CanvasRenderingContext2D} */ + (canvas.getContext('2d')); + this.drawHitDetectionCanvas_(renderOptions, context, 0, 0); }; - /** - * @constructor - * @extends {goog.events.Event} - * @implements {oli.render.Event} - * @param {ol.render.EventType} type Type. - * @param {Object=} opt_target Target. - * @param {ol.render.IVectorContext=} opt_vectorContext Vector context. - * @param {ol.render.IReplayGroup=} opt_replayGroup Replay group. - * @param {olx.FrameState=} opt_frameState Frame state. - * @param {?CanvasRenderingContext2D=} opt_context Context. - * @param {?ol.webgl.Context=} opt_glContext WebGL Context. + * @private + * @param {ol.style.Circle.RenderOptions} renderOptions + * @param {CanvasRenderingContext2D} context + * @param {number} x The origin for the symbol (x). + * @param {number} y The origin for the symbol (y). */ -ol.render.Event = function( - type, opt_target, opt_vectorContext, opt_replayGroup, opt_frameState, - opt_context, opt_glContext) { +ol.style.Circle.prototype.drawHitDetectionCanvas_ = + function(renderOptions, context, x, y) { + // reset transform + context.setTransform(1, 0, 0, 1, 0, 0); - goog.base(this, type, opt_target); + // then move to (x, y) + context.translate(x, y); - /** - * For canvas, this is an instance of {@link ol.render.canvas.Immediate}. - * @type {ol.render.IVectorContext|undefined} - * @api - */ - this.vectorContext = opt_vectorContext; + context.beginPath(); + context.arc( + renderOptions.size / 2, renderOptions.size / 2, + this.radius_, 0, 2 * Math.PI, true); - /** - * @type {ol.render.IReplayGroup|undefined} - */ - this.replayGroup = opt_replayGroup; + context.fillStyle = ol.render.canvas.defaultFillStyle; + context.fill(); + if (!goog.isNull(this.stroke_)) { + context.strokeStyle = renderOptions.strokeStyle; + context.lineWidth = renderOptions.strokeWidth; + if (!goog.isNull(renderOptions.lineDash)) { + context.setLineDash(renderOptions.lineDash); + } + context.stroke(); + } + context.closePath(); +}; - /** - * @type {olx.FrameState|undefined} - * @api - */ - this.frameState = opt_frameState; - /** - * Canvas context. Only available when a Canvas renderer is used, null - * otherwise. - * @type {CanvasRenderingContext2D|null|undefined} - * @api - */ - this.context = opt_context; +/** + * @inheritDoc + */ +ol.style.Circle.prototype.getChecksum = function() { + var strokeChecksum = !goog.isNull(this.stroke_) ? + this.stroke_.getChecksum() : '-'; + var fillChecksum = !goog.isNull(this.fill_) ? + this.fill_.getChecksum() : '-'; - /** - * WebGL context. Only available when a WebGL renderer is used, null - * otherwise. - * @type {ol.webgl.Context|null|undefined} - * @api - */ - this.glContext = opt_glContext; + var recalculate = goog.isNull(this.checksums_) || + (strokeChecksum != this.checksums_[1] || + fillChecksum != this.checksums_[2] || + this.radius_ != this.checksums_[3]); -}; -goog.inherits(ol.render.Event, goog.events.Event); + if (recalculate) { + var checksum = 'c' + strokeChecksum + fillChecksum + + (goog.isDef(this.radius_) ? this.radius_.toString() : '-'); + this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_]; + } -// FIXME add rotation + return this.checksums_[0]; +}; -goog.provide('ol.render.Box'); +goog.provide('ol.style.GeometryFunction'); +goog.provide('ol.style.Style'); +goog.provide('ol.style.StyleFunction'); +goog.provide('ol.style.defaultGeometryFunction'); -goog.require('goog.Disposable'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('ol.geom.Polygon'); -goog.require('ol.render.EventType'); +goog.require('goog.functions'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.style.Circle'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Image'); +goog.require('ol.style.Stroke'); /** + * @classdesc + * Container for vector feature rendering styles. Any changes made to the style + * or its children through `set*()` methods will not take effect until the + * feature or layer that uses the style is re-rendered. + * * @constructor - * @extends {goog.Disposable} - * @param {ol.style.Style} style Style. + * @param {olx.style.StyleOptions=} opt_options Style options. + * @api */ -ol.render.Box = function(style) { +ol.style.Style = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {ol.Map} + * @type {string|ol.geom.Geometry|ol.style.GeometryFunction} */ - this.map_ = null; + this.geometry_ = null; /** * @private - * @type {goog.events.Key} + * @type {!ol.style.GeometryFunction} */ - this.postComposeListenerKey_ = null; + this.geometryFunction_ = ol.style.defaultGeometryFunction; + + if (goog.isDef(options.geometry)) { + this.setGeometry(options.geometry); + } /** * @private - * @type {ol.Pixel} + * @type {ol.style.Fill} */ - this.startPixel_ = null; + this.fill_ = goog.isDef(options.fill) ? options.fill : null; /** * @private - * @type {ol.Pixel} + * @type {ol.style.Image} */ - this.endPixel_ = null; + this.image_ = goog.isDef(options.image) ? options.image : null; /** * @private - * @type {ol.geom.Polygon} + * @type {ol.style.Stroke} */ - this.geometry_ = null; + this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; /** * @private - * @type {ol.style.Style} + * @type {ol.style.Text} */ - this.style_ = style; + this.text_ = goog.isDef(options.text) ? options.text : null; + + /** + * @private + * @type {number|undefined} + */ + this.zIndex_ = options.zIndex; }; -goog.inherits(ol.render.Box, goog.Disposable); /** - * @private - * @return {ol.geom.Polygon} Geometry. + * Get the geometry to be rendered. + * @return {string|ol.geom.Geometry|ol.style.GeometryFunction} + * Feature property or geometry or function that returns the geometry that will + * be rendered with this style. + * @api */ -ol.render.Box.prototype.createGeometry_ = function() { - goog.asserts.assert(!goog.isNull(this.startPixel_)); - goog.asserts.assert(!goog.isNull(this.endPixel_)); - goog.asserts.assert(!goog.isNull(this.map_)); - var startPixel = this.startPixel_; - var endPixel = this.endPixel_; - var pixels = [ - startPixel, - [startPixel[0], endPixel[1]], - endPixel, - [endPixel[0], startPixel[1]] - ]; - var coordinates = goog.array.map(pixels, - this.map_.getCoordinateFromPixel, this.map_); - // close the polygon - coordinates[4] = coordinates[0].slice(); - return new ol.geom.Polygon([coordinates]); +ol.style.Style.prototype.getGeometry = function() { + return this.geometry_; }; /** - * @inheritDoc + * Get the function used to generate a geometry for rendering. + * @return {!ol.style.GeometryFunction} Function that is called with a feature + * and returns the geometry to render instead of the feature's geometry. + * @api */ -ol.render.Box.prototype.disposeInternal = function() { - this.setMap(null); +ol.style.Style.prototype.getGeometryFunction = function() { + return this.geometryFunction_; }; /** - * @param {ol.render.Event} event Event. - * @private + * Get the fill style. + * @return {ol.style.Fill} Fill style. + * @api */ -ol.render.Box.prototype.handleMapPostCompose_ = function(event) { - var geometry = this.geometry_; - goog.asserts.assert(goog.isDefAndNotNull(geometry)); - var style = this.style_; - goog.asserts.assert(!goog.isNull(style)); - // use drawAsync(Infinity) to draw above everything - event.vectorContext.drawAsync(Infinity, function(render) { - render.setFillStrokeStyle(style.getFill(), style.getStroke()); - render.setTextStyle(style.getText()); - render.drawPolygonGeometry(geometry, null); - }); +ol.style.Style.prototype.getFill = function() { + return this.fill_; }; /** - * @return {ol.geom.Polygon} Geometry. + * Get the image style. + * @return {ol.style.Image} Image style. + * @api */ -ol.render.Box.prototype.getGeometry = function() { - return this.geometry_; +ol.style.Style.prototype.getImage = function() { + return this.image_; }; /** - * @private + * Get the stroke style. + * @return {ol.style.Stroke} Stroke style. + * @api */ -ol.render.Box.prototype.requestMapRenderFrame_ = function() { - if (!goog.isNull(this.map_) && - !goog.isNull(this.startPixel_) && - !goog.isNull(this.endPixel_)) { - this.map_.render(); - } +ol.style.Style.prototype.getStroke = function() { + return this.stroke_; }; /** - * @param {ol.Map} map Map. + * Get the text style. + * @return {ol.style.Text} Text style. + * @api */ -ol.render.Box.prototype.setMap = function(map) { - if (!goog.isNull(this.postComposeListenerKey_)) { - goog.events.unlistenByKey(this.postComposeListenerKey_); - this.postComposeListenerKey_ = null; - this.map_.render(); - this.map_ = null; - } - this.map_ = map; - if (!goog.isNull(this.map_)) { - this.postComposeListenerKey_ = goog.events.listen( - map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false, - this); - this.requestMapRenderFrame_(); - } +ol.style.Style.prototype.getText = function() { + return this.text_; }; /** - * @param {ol.Pixel} startPixel Start pixel. - * @param {ol.Pixel} endPixel End pixel. + * Get the z-index for the style. + * @return {number|undefined} ZIndex. + * @api */ -ol.render.Box.prototype.setPixels = function(startPixel, endPixel) { - this.startPixel_ = startPixel; - this.endPixel_ = endPixel; - this.geometry_ = this.createGeometry_(); - this.requestMapRenderFrame_(); +ol.style.Style.prototype.getZIndex = function() { + return this.zIndex_; }; -// FIXME draw drag box -goog.provide('ol.DragBoxEvent'); -goog.provide('ol.interaction.DragBox'); - -goog.require('goog.events.Event'); -goog.require('ol'); -goog.require('ol.events.ConditionType'); -goog.require('ol.events.condition'); -goog.require('ol.interaction.Pointer'); -goog.require('ol.render.Box'); - /** - * @const - * @type {number} + * Set a geometry that is rendered instead of the feature's geometry. + * + * @param {string|ol.geom.Geometry|ol.style.GeometryFunction} geometry + * Feature property or geometry or function returning a geometry to render + * for this style. + * @api */ -ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED = - ol.DRAG_BOX_HYSTERESIS_PIXELS * - ol.DRAG_BOX_HYSTERESIS_PIXELS; +ol.style.Style.prototype.setGeometry = function(geometry) { + if (goog.isFunction(geometry)) { + this.geometryFunction_ = geometry; + } else if (goog.isString(geometry)) { + this.geometryFunction_ = function(feature) { + var result = feature.get(geometry); + if (goog.isDefAndNotNull(result)) { + goog.asserts.assertInstanceof(result, ol.geom.Geometry, + 'feature geometry must be an ol.geom.Geometry instance'); + } + return result; + }; + } else if (goog.isNull(geometry)) { + this.geometryFunction_ = ol.style.defaultGeometryFunction; + } else if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.Geometry, + 'geometry must be an ol.geom.Geometry instance'); + this.geometryFunction_ = function() { + return geometry; + }; + } + this.geometry_ = geometry; +}; /** - * @enum {string} + * Set the z-index. + * + * @param {number|undefined} zIndex ZIndex. + * @api */ -ol.DragBoxEventType = { - /** - * Triggered upon drag box start. - * @event ol.DragBoxEvent#boxstart - * @api stable - */ - BOXSTART: 'boxstart', - /** - * Triggered upon drag box end. - * @event ol.DragBoxEvent#boxend - * @api stable - */ - BOXEND: 'boxend' +ol.style.Style.prototype.setZIndex = function(zIndex) { + this.zIndex_ = zIndex; }; - /** - * @classdesc - * Events emitted by {@link ol.interaction.DragBox} instances are instances of - * this type. + * A function that takes an {@link ol.Feature} and a `{number}` representing + * the view's resolution. The function should return an array of + * {@link ol.style.Style}. This way e.g. a vector layer can be styled. * - * @param {string} type The event type. - * @param {ol.Coordinate} coordinate The event coordinate. - * @extends {goog.events.Event} - * @constructor - * @implements {oli.DragBoxEvent} + * @typedef {function(ol.Feature, number): Array.<ol.style.Style>} + * @api */ -ol.DragBoxEvent = function(type, coordinate) { - goog.base(this, type); +ol.style.StyleFunction; + +/** + * Convert the provided object into a style function. Functions passed through + * unchanged. Arrays of ol.style.Style or single style objects wrapped in a + * new style function. + * @param {ol.style.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj + * A style function, a single style, or an array of styles. + * @return {ol.style.StyleFunction} A style function. + */ +ol.style.createStyleFunction = function(obj) { /** - * The coordinate of the drag event. - * @const - * @type {ol.Coordinate} - * @api stable + * @type {ol.style.StyleFunction} */ - this.coordinate = coordinate; + var styleFunction; + if (goog.isFunction(obj)) { + styleFunction = /** @type {ol.style.StyleFunction} */ (obj); + } else { + /** + * @type {Array.<ol.style.Style>} + */ + var styles; + if (goog.isArray(obj)) { + styles = obj; + } else { + goog.asserts.assertInstanceof(obj, ol.style.Style, + 'obj geometry must be an ol.style.Style instance'); + styles = [obj]; + } + styleFunction = goog.functions.constant(styles); + } + return styleFunction; }; -goog.inherits(ol.DragBoxEvent, goog.events.Event); - /** - * @classdesc - * Allows the user to draw a vector box by clicking and dragging on the map, - * normally combined with an {@link ol.events.condition} that limits - * it to when the shift or other key is held down. This is used, for example, - * for zooming to a specific area of the map - * (see {@link ol.interaction.DragZoom} and - * {@link ol.interaction.DragRotateAndZoom}). - * - * This interaction is only supported for mouse devices. - * - * @constructor - * @extends {ol.interaction.Pointer} - * @fires ol.DragBoxEvent - * @param {olx.interaction.DragBoxOptions=} opt_options Options. - * @api stable + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. */ -ol.interaction.DragBox = function(opt_options) { - - goog.base(this, { - handleDownEvent: ol.interaction.DragBox.handleDownEvent_, - handleDragEvent: ol.interaction.DragBox.handleDragEvent_, - handleUpEvent: ol.interaction.DragBox.handleUpEvent_ +ol.style.defaultStyleFunction = function(feature, resolution) { + var fill = new ol.style.Fill({ + color: 'rgba(255,255,255,0.4)' }); + var stroke = new ol.style.Stroke({ + color: '#3399CC', + width: 1.25 + }); + var styles = [ + new ol.style.Style({ + image: new ol.style.Circle({ + fill: fill, + stroke: stroke, + radius: 5 + }), + fill: fill, + stroke: stroke + }) + ]; - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.style.Style} - */ - var style = goog.isDef(options.style) ? options.style : null; - - /** - * @type {ol.render.Box} - * @private - */ - this.box_ = new ol.render.Box(style); - - /** - * @type {ol.Pixel} - * @private - */ - this.startPixel_ = null; + // Now that we've run it the first time, replace the function with + // a constant version. We don't use an immediately-invoked function + // and a closure so we don't get an error at script evaluation time in + // browsers that do not support Canvas. (ol.style.Circle does + // canvas.getContext('2d') at construction time, which will cause an.error + // in such browsers.) /** - * @private - * @type {ol.events.ConditionType} + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. */ - this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.events.condition.always; - -}; -goog.inherits(ol.interaction.DragBox, ol.interaction.Pointer); - - -/** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.DragBox} - * @private - */ -ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return; - } + ol.style.defaultStyleFunction = function(feature, resolution) { + return styles; + }; - this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel); + return styles; }; /** - * Returns geometry of last drawn box. - * @return {ol.geom.Polygon} Geometry. - * @api stable + * Default styles for editing features. + * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles */ -ol.interaction.DragBox.prototype.getGeometry = function() { - return this.box_.getGeometry(); -}; - +ol.style.createDefaultEditingStyles = function() { + /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */ + var styles = {}; + var white = [255, 255, 255, 1]; + var blue = [0, 153, 255, 1]; + var width = 3; + styles[ol.geom.GeometryType.POLYGON] = [ + new ol.style.Style({ + fill: new ol.style.Fill({ + color: [255, 255, 255, 0.5] + }) + }) + ]; + styles[ol.geom.GeometryType.MULTI_POLYGON] = + styles[ol.geom.GeometryType.POLYGON]; -/** - * To be overriden by child classes. - * FIXME: use constructor option instead of relying on overridding. - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @protected - */ -ol.interaction.DragBox.prototype.onBoxEnd = goog.nullFunction; + styles[ol.geom.GeometryType.LINE_STRING] = [ + new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: white, + width: width + 2 + }) + }), + new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: blue, + width: width + }) + }) + ]; + styles[ol.geom.GeometryType.MULTI_LINE_STRING] = + styles[ol.geom.GeometryType.LINE_STRING]; + styles[ol.geom.GeometryType.CIRCLE] = + styles[ol.geom.GeometryType.POLYGON].concat( + styles[ol.geom.GeometryType.LINE_STRING] + ); -/** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.DragBox} - * @private - */ -ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return true; - } - this.box_.setMap(null); + styles[ol.geom.GeometryType.POINT] = [ + new ol.style.Style({ + image: new ol.style.Circle({ + radius: width * 2, + fill: new ol.style.Fill({ + color: blue + }), + stroke: new ol.style.Stroke({ + color: white, + width: width / 2 + }) + }), + zIndex: Infinity + }) + ]; + styles[ol.geom.GeometryType.MULTI_POINT] = + styles[ol.geom.GeometryType.POINT]; - var deltaX = mapBrowserEvent.pixel[0] - this.startPixel_[0]; - var deltaY = mapBrowserEvent.pixel[1] - this.startPixel_[1]; + styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] = + styles[ol.geom.GeometryType.POLYGON].concat( + styles[ol.geom.GeometryType.POINT] + ); - if (deltaX * deltaX + deltaY * deltaY >= - ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED) { - this.onBoxEnd(mapBrowserEvent); - this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXEND, - mapBrowserEvent.coordinate)); - } - return false; + return styles; }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.DragBox} - * @private + * A function that takes an {@link ol.Feature} as argument and returns an + * {@link ol.geom.Geometry} that will be rendered and styled for the feature. + * + * @typedef {function(ol.Feature): (ol.geom.Geometry|undefined)} + * @api */ -ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return false; - } - - var browserEvent = mapBrowserEvent.browserEvent; - if (browserEvent.isMouseActionButton() && this.condition_(mapBrowserEvent)) { - this.startPixel_ = mapBrowserEvent.pixel; - this.box_.setMap(mapBrowserEvent.map); - this.box_.setPixels(this.startPixel_, this.startPixel_); - this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXSTART, - mapBrowserEvent.coordinate)); - return true; - } else { - return false; - } -}; +ol.style.GeometryFunction; -// Copyright 2008 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Namespace with crypto related helper functions. + * Function that is called with a feature and returns its default geometry. + * @param {ol.Feature} feature Feature to get the geometry for. + * @return {ol.geom.Geometry|undefined} Geometry to render. */ +ol.style.defaultGeometryFunction = function(feature) { + goog.asserts.assert(!goog.isNull(feature), + 'feature must not be null'); + return feature.getGeometry(); +}; -goog.provide('goog.crypt'); +goog.provide('ol.interaction.DragZoom'); -goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.interaction.DragBox'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); + /** - * Turns a string into an array of bytes; a "byte" being a JS number in the - * range 0-255. - * @param {string} str String value to arrify. - * @return {!Array<number>} Array of numbers corresponding to the - * UCS character codes of each character in str. + * @classdesc + * Allows the user to zoom the map by clicking and dragging on the map, + * normally combined with an {@link ol.events.condition} that limits + * it to when a key, shift by default, is held down. + * + * @constructor + * @extends {ol.interaction.DragBox} + * @param {olx.interaction.DragZoomOptions=} opt_options Options. + * @api stable */ -goog.crypt.stringToByteArray = function(str) { - var output = [], p = 0; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - while (c > 0xff) { - output[p++] = c & 0xff; - c >>= 8; - } - output[p++] = c; - } - return output; -}; +ol.interaction.DragZoom = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; + var condition = goog.isDef(options.condition) ? + options.condition : ol.events.condition.shiftKeyOnly; -/** - * Turns an array of numbers into the string given by the concatenation of the - * characters to which the numbers correspond. - * @param {Array<number>} bytes Array of numbers representing characters. - * @return {string} Stringification of the array. - */ -goog.crypt.byteArrayToString = function(bytes) { - var CHUNK_SIZE = 8192; + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 200; - // Special-case the simple case for speed's sake. - if (bytes.length < CHUNK_SIZE) { - return String.fromCharCode.apply(null, bytes); - } + /** + * @private + * @type {ol.style.Style} + */ + var style = goog.isDef(options.style) ? + options.style : new ol.style.Style({ + stroke: new ol.style.Stroke({ + color: [0, 0, 255, 1] + }) + }); - // The remaining logic splits conversion by chunks since - // Function#apply() has a maximum parameter count. - // See discussion: http://goo.gl/LrWmZ9 + goog.base(this, { + condition: condition, + style: style + }); - var str = ''; - for (var i = 0; i < bytes.length; i += CHUNK_SIZE) { - var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE); - str += String.fromCharCode.apply(null, chunk); - } - return str; }; +goog.inherits(ol.interaction.DragZoom, ol.interaction.DragBox); /** - * Turns an array of numbers into the hex string given by the concatenation of - * the hex values to which the numbers correspond. - * @param {Uint8Array|Array<number>} array Array of numbers representing - * characters. - * @return {string} Hex string. + * @inheritDoc */ -goog.crypt.byteArrayToHex = function(array) { - return goog.array.map(array, function(numByte) { - var hexByte = numByte.toString(16); - return hexByte.length > 1 ? hexByte : '0' + hexByte; - }).join(''); +ol.interaction.DragZoom.prototype.onBoxEnd = function() { + var map = this.getMap(); + var view = map.getView(); + goog.asserts.assert(!goog.isNull(view), 'view should not be null'); + var extent = this.getGeometry().getExtent(); + var center = ol.extent.getCenter(extent); + var size = map.getSize(); + goog.asserts.assert(goog.isDef(size), 'size should be defined'); + ol.interaction.Interaction.zoom(map, view, + view.getResolutionForExtent(extent, size), + center, this.duration_); }; +goog.provide('ol.interaction.KeyboardPan'); + +goog.require('goog.asserts'); +goog.require('goog.events.KeyCodes'); +goog.require('goog.events.KeyHandler.EventType'); +goog.require('goog.functions'); +goog.require('ol.coordinate'); +goog.require('ol.events.ConditionType'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); -/** - * Converts a hex string into an integer array. - * @param {string} hexString Hex string of 16-bit integers (two characters - * per integer). - * @return {!Array<number>} Array of {0,255} integers for the given string. - */ -goog.crypt.hexToByteArray = function(hexString) { - goog.asserts.assert(hexString.length % 2 == 0, - 'Key string length must be multiple of 2'); - var arr = []; - for (var i = 0; i < hexString.length; i += 2) { - arr.push(parseInt(hexString.substring(i, i + 2), 16)); - } - return arr; -}; /** - * Converts a JS string to a UTF-8 "byte" array. - * @param {string} str 16-bit unicode string. - * @return {!Array<number>} UTF-8 byte array. + * @classdesc + * Allows the user to pan the map using keyboard arrows. + * Note that, although this interaction is by default included in maps, + * the keys can only be used when browser focus is on the element to which + * the keyboard events are attached. By default, this is the map div, + * though you can change this with the `keyboardEventTarget` in + * {@link ol.Map}. `document` never loses focus but, for any other element, + * focus will have to be on, and returned to, this element if the keys are to + * function. + * See also {@link ol.interaction.KeyboardZoom}. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.KeyboardPanOptions=} opt_options Options. + * @api stable */ -goog.crypt.stringToUtf8ByteArray = function(str) { - // TODO(user): Use native implementations if/when available - str = str.replace(/\r\n/g, '\n'); - var out = [], p = 0; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - if (c < 128) { - out[p++] = c; - } else if (c < 2048) { - out[p++] = (c >> 6) | 192; - out[p++] = (c & 63) | 128; - } else { - out[p++] = (c >> 12) | 224; - out[p++] = ((c >> 6) & 63) | 128; - out[p++] = (c & 63) | 128; - } - } - return out; +ol.interaction.KeyboardPan = function(opt_options) { + + goog.base(this, { + handleEvent: ol.interaction.KeyboardPan.handleEvent + }); + + var options = goog.isDef(opt_options) ? opt_options : {}; + + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? options.condition : + goog.functions.and(ol.events.condition.noModifierKeys, + ol.events.condition.targetNotEditable); + + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 100; + + /** + * @private + * @type {number} + */ + this.pixelDelta_ = goog.isDef(options.pixelDelta) ? options.pixelDelta : 128; + }; +goog.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction); /** - * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode. - * @param {Uint8Array|Array<number>} bytes UTF-8 byte array. - * @return {string} 16-bit Unicode string. + * Handles the {@link ol.MapBrowserEvent map browser event} if it was a + * `KeyEvent`, and decides the direction to pan to (if an arrow key was + * pressed). + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.KeyboardPan} + * @api */ -goog.crypt.utf8ByteArrayToString = function(bytes) { - // TODO(user): Use native implementations if/when available - var out = [], pos = 0, c = 0; - while (pos < bytes.length) { - var c1 = bytes[pos++]; - if (c1 < 128) { - out[c++] = String.fromCharCode(c1); - } else if (c1 > 191 && c1 < 224) { - var c2 = bytes[pos++]; - out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63); - } else { - var c2 = bytes[pos++]; - var c3 = bytes[pos++]; - out[c++] = String.fromCharCode( - (c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63); +ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { + var keyEvent = /** @type {goog.events.KeyEvent} */ + (mapBrowserEvent.browserEvent); + var keyCode = keyEvent.keyCode; + if (this.condition_(mapBrowserEvent) && + (keyCode == goog.events.KeyCodes.DOWN || + keyCode == goog.events.KeyCodes.LEFT || + keyCode == goog.events.KeyCodes.RIGHT || + keyCode == goog.events.KeyCodes.UP)) { + var map = mapBrowserEvent.map; + var view = map.getView(); + goog.asserts.assert(!goog.isNull(view), 'view should not be null'); + var viewState = view.getState(); + var mapUnitsDelta = viewState.resolution * this.pixelDelta_; + var deltaX = 0, deltaY = 0; + if (keyCode == goog.events.KeyCodes.DOWN) { + deltaY = -mapUnitsDelta; + } else if (keyCode == goog.events.KeyCodes.LEFT) { + deltaX = -mapUnitsDelta; + } else if (keyCode == goog.events.KeyCodes.RIGHT) { + deltaX = mapUnitsDelta; + } else { + deltaY = mapUnitsDelta; + } + var delta = [deltaX, deltaY]; + ol.coordinate.rotate(delta, viewState.rotation); + ol.interaction.Interaction.pan(map, view, delta, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; } } - return out.join(''); + return !stopEvent; }; +goog.provide('ol.interaction.KeyboardZoom'); -/** - * XOR two byte arrays. - * @param {!ArrayBufferView|!Array<number>} bytes1 Byte array 1. - * @param {!ArrayBufferView|!Array<number>} bytes2 Byte array 2. - * @return {!Array<number>} Resulting XOR of the two byte arrays. - */ -goog.crypt.xorByteArray = function(bytes1, bytes2) { - goog.asserts.assert( - bytes1.length == bytes2.length, - 'XOR array lengths must match'); +goog.require('goog.asserts'); +goog.require('goog.events.KeyHandler.EventType'); +goog.require('ol.events.ConditionType'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); - var result = []; - for (var i = 0; i < bytes1.length; i++) { - result.push(bytes1[i] ^ bytes2[i]); - } - return result; -}; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Abstract cryptographic hash interface. - * - * See goog.crypt.Sha1 and goog.crypt.Md5 for sample implementations. + * @classdesc + * Allows the user to zoom the map using keyboard + and -. + * Note that, although this interaction is by default included in maps, + * the keys can only be used when browser focus is on the element to which + * the keyboard events are attached. By default, this is the map div, + * though you can change this with the `keyboardEventTarget` in + * {@link ol.Map}. `document` never loses focus but, for any other element, + * focus will have to be on, and returned to, this element if the keys are to + * function. + * See also {@link ol.interaction.KeyboardPan}. * + * @constructor + * @param {olx.interaction.KeyboardZoomOptions=} opt_options Options. + * @extends {ol.interaction.Interaction} + * @api stable */ +ol.interaction.KeyboardZoom = function(opt_options) { -goog.provide('goog.crypt.Hash'); + goog.base(this, { + handleEvent: ol.interaction.KeyboardZoom.handleEvent + }); + var options = goog.isDef(opt_options) ? opt_options : {}; + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? options.condition : + ol.events.condition.targetNotEditable; -/** - * Create a cryptographic hash instance. - * - * @constructor - * @struct - */ -goog.crypt.Hash = function() { /** - * The block size for the hasher. + * @private * @type {number} */ - this.blockSize = -1; -}; + this.delta_ = goog.isDef(options.delta) ? options.delta : 1; + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 100; -/** - * Resets the internal accumulator. - */ -goog.crypt.Hash.prototype.reset = goog.abstractMethod; +}; +goog.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction); /** - * Adds a byte array (array with values in [0-255] range) or a string (might - * only contain 8-bit, i.e., Latin1 characters) to the internal accumulator. - * - * Many hash functions operate on blocks of data and implement optimizations - * when a full chunk of data is readily available. Hence it is often preferable - * to provide large chunks of data (a kilobyte or more) than to repeatedly - * call the update method with few tens of bytes. If this is not possible, or - * not feasible, it might be good to provide data in multiplies of hash block - * size (often 64 bytes). Please see the implementation and performance tests - * of your favourite hash. - * - * @param {Array<number>|Uint8Array|string} bytes Data used for the update. - * @param {number=} opt_length Number of bytes to use. + * Handles the {@link ol.MapBrowserEvent map browser event} if it was a + * `KeyEvent`, and decides whether to zoom in or out (depending on whether the + * key pressed was '+' or '-'). + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.KeyboardZoom} + * @api */ -goog.crypt.Hash.prototype.update = goog.abstractMethod; +ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { + var keyEvent = /** @type {goog.events.KeyEvent} */ + (mapBrowserEvent.browserEvent); + var charCode = keyEvent.charCode; + if (this.condition_(mapBrowserEvent) && + (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) { + var map = mapBrowserEvent.map; + var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_; + map.render(); + var view = map.getView(); + goog.asserts.assert(!goog.isNull(view), 'view should not be null'); + ol.interaction.Interaction.zoomByDelta( + map, view, delta, undefined, this.duration_); + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + } + return !stopEvent; +}; +goog.provide('ol.interaction.MouseWheelZoom'); + +goog.require('goog.asserts'); +goog.require('goog.events.MouseWheelEvent'); +goog.require('goog.events.MouseWheelHandler.EventType'); +goog.require('goog.math'); +goog.require('ol'); +goog.require('ol.Coordinate'); +goog.require('ol.interaction.Interaction'); -/** - * @return {!Array<number>} The finalized hash computed - * from the internal accumulator. - */ -goog.crypt.Hash.prototype.digest = goog.abstractMethod; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview MD5 cryptographic hash. - * Implementation of http://tools.ietf.org/html/rfc1321 with common - * optimizations and tweaks (see http://en.wikipedia.org/wiki/MD5). - * - * Usage: - * var md5 = new goog.crypt.Md5(); - * md5.update(bytes); - * var hash = md5.digest(); - * - * Performance: - * Chrome 23 ~680 Mbit/s - * Chrome 13 (in a VM) ~250 Mbit/s - * Firefox 6.0 (in a VM) ~100 Mbit/s - * IE9 (in a VM) ~27 Mbit/s - * Firefox 3.6 ~15 Mbit/s - * IE8 (in a VM) ~13 Mbit/s + * @classdesc + * Allows the user to zoom the map by scrolling the mouse wheel. * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options. + * @api stable */ +ol.interaction.MouseWheelZoom = function(opt_options) { -goog.provide('goog.crypt.Md5'); - -goog.require('goog.crypt.Hash'); - - + goog.base(this, { + handleEvent: ol.interaction.MouseWheelZoom.handleEvent + }); -/** - * MD5 cryptographic hash constructor. - * @constructor - * @extends {goog.crypt.Hash} - * @final - * @struct - */ -goog.crypt.Md5 = function() { - goog.crypt.Md5.base(this, 'constructor'); + var options = goog.isDef(opt_options) ? opt_options : {}; - this.blockSize = 512 / 8; + /** + * @private + * @type {number} + */ + this.delta_ = 0; /** - * Holds the current values of accumulated A-D variables (MD buffer). - * @type {!Array<number>} * @private + * @type {number} */ - this.chain_ = new Array(4); + this.duration_ = goog.isDef(options.duration) ? options.duration : 250; /** - * A buffer holding the data until the whole block can be processed. - * @type {!Array<number>} * @private + * @type {?ol.Coordinate} */ - this.block_ = new Array(this.blockSize); + this.lastAnchor_ = null; /** - * The length of yet-unprocessed data as collected in the block. - * @type {number} * @private + * @type {number|undefined} */ - this.blockLength_ = 0; + this.startTime_ = undefined; /** - * The total length of the message so far. - * @type {number} * @private + * @type {number|undefined} */ - this.totalLength_ = 0; + this.timeoutId_ = undefined; - this.reset(); }; -goog.inherits(goog.crypt.Md5, goog.crypt.Hash); +goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction); /** - * Integer rotation constants used by the abbreviated implementation. - * They are hardcoded in the unrolled implementation, so it is left - * here commented out. - * @type {Array<number>} - * @private - * -goog.crypt.Md5.S_ = [ - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 -]; + * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a + * mousewheel-event) and eventually zooms the map. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.MouseWheelZoom} + * @api */ +ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) { + var stopEvent = false; + if (mapBrowserEvent.type == + goog.events.MouseWheelHandler.EventType.MOUSEWHEEL) { + var map = mapBrowserEvent.map; + var mouseWheelEvent = mapBrowserEvent.browserEvent; + goog.asserts.assertInstanceof(mouseWheelEvent, goog.events.MouseWheelEvent, + 'mouseWheelEvent should be of type MouseWheelEvent'); + + this.lastAnchor_ = mapBrowserEvent.coordinate; + this.delta_ += mouseWheelEvent.deltaY; + + if (!goog.isDef(this.startTime_)) { + this.startTime_ = goog.now(); + } + + var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION; + var timeLeft = Math.max(duration - (goog.now() - this.startTime_), 0); + + goog.global.clearTimeout(this.timeoutId_); + this.timeoutId_ = goog.global.setTimeout( + goog.bind(this.doZoom_, this, map), timeLeft); + + mapBrowserEvent.preventDefault(); + stopEvent = true; + } + return !stopEvent; +}; + /** - * Sine function constants used by the abbreviated implementation. - * They are hardcoded in the unrolled implementation, so it is left - * here commented out. - * @type {Array<number>} * @private - * -goog.crypt.Md5.T_ = [ - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 -]; + * @param {ol.Map} map Map. */ +ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) { + var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA; + var delta = goog.math.clamp(this.delta_, -maxDelta, maxDelta); + var view = map.getView(); + goog.asserts.assert(!goog.isNull(view), 'view should not be null'); -/** @override */ -goog.crypt.Md5.prototype.reset = function() { - this.chain_[0] = 0x67452301; - this.chain_[1] = 0xefcdab89; - this.chain_[2] = 0x98badcfe; - this.chain_[3] = 0x10325476; + map.render(); + ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_, + this.duration_); - this.blockLength_ = 0; - this.totalLength_ = 0; + this.delta_ = 0; + this.lastAnchor_ = null; + this.startTime_ = undefined; + this.timeoutId_ = undefined; }; +goog.provide('ol.interaction.PinchRotate'); + +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.style'); +goog.require('ol.Coordinate'); +goog.require('ol.ViewHint'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + /** - * Internal compress helper function. It takes a block of data (64 bytes) - * and updates the accumulator. - * @param {Array<number>|Uint8Array|string} buf The block to compress. - * @param {number=} opt_offset Offset of the block in the buffer. - * @private + * @classdesc + * Allows the user to rotate the map by twisting with two fingers + * on a touch screen. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.PinchRotateOptions=} opt_options Options. + * @api stable */ -goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) { - if (!opt_offset) { - opt_offset = 0; - } +ol.interaction.PinchRotate = function(opt_options) { - // We allocate the array every time, but it's cheap in practice. - var X = new Array(16); + goog.base(this, { + handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_, + handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_, + handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_ + }); - // Get 16 little endian words. It is not worth unrolling this for Chrome 11. - if (goog.isString(buf)) { - for (var i = 0; i < 16; ++i) { - X[i] = (buf.charCodeAt(opt_offset++)) | - (buf.charCodeAt(opt_offset++) << 8) | - (buf.charCodeAt(opt_offset++) << 16) | - (buf.charCodeAt(opt_offset++) << 24); - } - } else { - for (var i = 0; i < 16; ++i) { - X[i] = (buf[opt_offset++]) | - (buf[opt_offset++] << 8) | - (buf[opt_offset++] << 16) | - (buf[opt_offset++] << 24); - } - } + var options = goog.isDef(opt_options) ? opt_options : {}; - var A = this.chain_[0]; - var B = this.chain_[1]; - var C = this.chain_[2]; - var D = this.chain_[3]; - var sum = 0; + /** + * @private + * @type {ol.Coordinate} + */ + this.anchor_ = null; - /* - * This is an abbreviated implementation, it is left here commented out for - * reference purposes. See below for an unrolled version in use. - * - var f, n, tmp; - for (var i = 0; i < 64; ++i) { + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; - if (i < 16) { - f = (D ^ (B & (C ^ D))); - n = i; - } else if (i < 32) { - f = (C ^ (D & (B ^ C))); - n = (5 * i + 1) % 16; - } else if (i < 48) { - f = (B ^ C ^ D); - n = (3 * i + 5) % 16; - } else { - f = (C ^ (B | (~D))); - n = (7 * i) % 16; - } + /** + * @private + * @type {boolean} + */ + this.rotating_ = false; - tmp = D; - D = C; - C = B; - sum = (A + f + goog.crypt.Md5.T_[i] + X[n]) & 0xffffffff; - B += ((sum << goog.crypt.Md5.S_[i]) & 0xffffffff) | - (sum >>> (32 - goog.crypt.Md5.S_[i])); - A = tmp; - } + /** + * @private + * @type {number} */ + this.rotationDelta_ = 0.0; - /* - * This is an unrolled MD5 implementation, which gives ~30% speedup compared - * to the abbreviated implementation above, as measured on Chrome 11. It is - * important to keep 32-bit croppings to minimum and inline the integer - * rotation. + /** + * @private + * @type {number} */ - sum = (A + (D ^ (B & (C ^ D))) + X[0] + 0xd76aa478) & 0xffffffff; - A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); - sum = (D + (C ^ (A & (B ^ C))) + X[1] + 0xe8c7b756) & 0xffffffff; - D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); - sum = (C + (B ^ (D & (A ^ B))) + X[2] + 0x242070db) & 0xffffffff; - C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); - sum = (B + (A ^ (C & (D ^ A))) + X[3] + 0xc1bdceee) & 0xffffffff; - B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); - sum = (A + (D ^ (B & (C ^ D))) + X[4] + 0xf57c0faf) & 0xffffffff; - A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); - sum = (D + (C ^ (A & (B ^ C))) + X[5] + 0x4787c62a) & 0xffffffff; - D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); - sum = (C + (B ^ (D & (A ^ B))) + X[6] + 0xa8304613) & 0xffffffff; - C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); - sum = (B + (A ^ (C & (D ^ A))) + X[7] + 0xfd469501) & 0xffffffff; - B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); - sum = (A + (D ^ (B & (C ^ D))) + X[8] + 0x698098d8) & 0xffffffff; - A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); - sum = (D + (C ^ (A & (B ^ C))) + X[9] + 0x8b44f7af) & 0xffffffff; - D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); - sum = (C + (B ^ (D & (A ^ B))) + X[10] + 0xffff5bb1) & 0xffffffff; - C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); - sum = (B + (A ^ (C & (D ^ A))) + X[11] + 0x895cd7be) & 0xffffffff; - B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); - sum = (A + (D ^ (B & (C ^ D))) + X[12] + 0x6b901122) & 0xffffffff; - A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25)); - sum = (D + (C ^ (A & (B ^ C))) + X[13] + 0xfd987193) & 0xffffffff; - D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20)); - sum = (C + (B ^ (D & (A ^ B))) + X[14] + 0xa679438e) & 0xffffffff; - C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15)); - sum = (B + (A ^ (C & (D ^ A))) + X[15] + 0x49b40821) & 0xffffffff; - B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10)); - sum = (A + (C ^ (D & (B ^ C))) + X[1] + 0xf61e2562) & 0xffffffff; - A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); - sum = (D + (B ^ (C & (A ^ B))) + X[6] + 0xc040b340) & 0xffffffff; - D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); - sum = (C + (A ^ (B & (D ^ A))) + X[11] + 0x265e5a51) & 0xffffffff; - C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); - sum = (B + (D ^ (A & (C ^ D))) + X[0] + 0xe9b6c7aa) & 0xffffffff; - B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); - sum = (A + (C ^ (D & (B ^ C))) + X[5] + 0xd62f105d) & 0xffffffff; - A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); - sum = (D + (B ^ (C & (A ^ B))) + X[10] + 0x02441453) & 0xffffffff; - D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); - sum = (C + (A ^ (B & (D ^ A))) + X[15] + 0xd8a1e681) & 0xffffffff; - C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); - sum = (B + (D ^ (A & (C ^ D))) + X[4] + 0xe7d3fbc8) & 0xffffffff; - B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); - sum = (A + (C ^ (D & (B ^ C))) + X[9] + 0x21e1cde6) & 0xffffffff; - A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); - sum = (D + (B ^ (C & (A ^ B))) + X[14] + 0xc33707d6) & 0xffffffff; - D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); - sum = (C + (A ^ (B & (D ^ A))) + X[3] + 0xf4d50d87) & 0xffffffff; - C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); - sum = (B + (D ^ (A & (C ^ D))) + X[8] + 0x455a14ed) & 0xffffffff; - B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); - sum = (A + (C ^ (D & (B ^ C))) + X[13] + 0xa9e3e905) & 0xffffffff; - A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27)); - sum = (D + (B ^ (C & (A ^ B))) + X[2] + 0xfcefa3f8) & 0xffffffff; - D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23)); - sum = (C + (A ^ (B & (D ^ A))) + X[7] + 0x676f02d9) & 0xffffffff; - C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18)); - sum = (B + (D ^ (A & (C ^ D))) + X[12] + 0x8d2a4c8a) & 0xffffffff; - B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12)); - sum = (A + (B ^ C ^ D) + X[5] + 0xfffa3942) & 0xffffffff; - A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); - sum = (D + (A ^ B ^ C) + X[8] + 0x8771f681) & 0xffffffff; - D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); - sum = (C + (D ^ A ^ B) + X[11] + 0x6d9d6122) & 0xffffffff; - C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); - sum = (B + (C ^ D ^ A) + X[14] + 0xfde5380c) & 0xffffffff; - B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); - sum = (A + (B ^ C ^ D) + X[1] + 0xa4beea44) & 0xffffffff; - A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); - sum = (D + (A ^ B ^ C) + X[4] + 0x4bdecfa9) & 0xffffffff; - D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); - sum = (C + (D ^ A ^ B) + X[7] + 0xf6bb4b60) & 0xffffffff; - C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); - sum = (B + (C ^ D ^ A) + X[10] + 0xbebfbc70) & 0xffffffff; - B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); - sum = (A + (B ^ C ^ D) + X[13] + 0x289b7ec6) & 0xffffffff; - A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); - sum = (D + (A ^ B ^ C) + X[0] + 0xeaa127fa) & 0xffffffff; - D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); - sum = (C + (D ^ A ^ B) + X[3] + 0xd4ef3085) & 0xffffffff; - C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); - sum = (B + (C ^ D ^ A) + X[6] + 0x04881d05) & 0xffffffff; - B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); - sum = (A + (B ^ C ^ D) + X[9] + 0xd9d4d039) & 0xffffffff; - A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28)); - sum = (D + (A ^ B ^ C) + X[12] + 0xe6db99e5) & 0xffffffff; - D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21)); - sum = (C + (D ^ A ^ B) + X[15] + 0x1fa27cf8) & 0xffffffff; - C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16)); - sum = (B + (C ^ D ^ A) + X[2] + 0xc4ac5665) & 0xffffffff; - B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9)); - sum = (A + (C ^ (B | (~D))) + X[0] + 0xf4292244) & 0xffffffff; - A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); - sum = (D + (B ^ (A | (~C))) + X[7] + 0x432aff97) & 0xffffffff; - D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); - sum = (C + (A ^ (D | (~B))) + X[14] + 0xab9423a7) & 0xffffffff; - C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); - sum = (B + (D ^ (C | (~A))) + X[5] + 0xfc93a039) & 0xffffffff; - B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); - sum = (A + (C ^ (B | (~D))) + X[12] + 0x655b59c3) & 0xffffffff; - A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); - sum = (D + (B ^ (A | (~C))) + X[3] + 0x8f0ccc92) & 0xffffffff; - D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); - sum = (C + (A ^ (D | (~B))) + X[10] + 0xffeff47d) & 0xffffffff; - C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); - sum = (B + (D ^ (C | (~A))) + X[1] + 0x85845dd1) & 0xffffffff; - B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); - sum = (A + (C ^ (B | (~D))) + X[8] + 0x6fa87e4f) & 0xffffffff; - A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); - sum = (D + (B ^ (A | (~C))) + X[15] + 0xfe2ce6e0) & 0xffffffff; - D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); - sum = (C + (A ^ (D | (~B))) + X[6] + 0xa3014314) & 0xffffffff; - C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); - sum = (B + (D ^ (C | (~A))) + X[13] + 0x4e0811a1) & 0xffffffff; - B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); - sum = (A + (C ^ (B | (~D))) + X[4] + 0xf7537e82) & 0xffffffff; - A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26)); - sum = (D + (B ^ (A | (~C))) + X[11] + 0xbd3af235) & 0xffffffff; - D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22)); - sum = (C + (A ^ (D | (~B))) + X[2] + 0x2ad7d2bb) & 0xffffffff; - C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17)); - sum = (B + (D ^ (C | (~A))) + X[9] + 0xeb86d391) & 0xffffffff; - B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11)); + this.threshold_ = goog.isDef(options.threshold) ? options.threshold : 0.3; + + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 250; - this.chain_[0] = (this.chain_[0] + A) & 0xffffffff; - this.chain_[1] = (this.chain_[1] + B) & 0xffffffff; - this.chain_[2] = (this.chain_[2] + C) & 0xffffffff; - this.chain_[3] = (this.chain_[3] + D) & 0xffffffff; }; +goog.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer); -/** @override */ -goog.crypt.Md5.prototype.update = function(bytes, opt_length) { - if (!goog.isDef(opt_length)) { - opt_length = bytes.length; - } - var lengthMinusBlock = opt_length - this.blockSize; +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) { + goog.asserts.assert(this.targetPointers.length >= 2, + 'length of this.targetPointers should be greater than or equal to 2'); + var rotationDelta = 0.0; - // Copy some object properties to local variables in order to save on access - // time from inside the loop (~10% speedup was observed on Chrome 11). - var block = this.block_; - var blockLength = this.blockLength_; - var i = 0; + var touch0 = this.targetPointers[0]; + var touch1 = this.targetPointers[1]; - // The outer while loop should execute at most twice. - while (i < opt_length) { - // When we have no data in the block to top up, we can directly process the - // input buffer (assuming it contains sufficient data). This gives ~30% - // speedup on Chrome 14 and ~70% speedup on Firefox 6.0, but requires that - // the data is provided in large chunks (or in multiples of 64 bytes). - if (blockLength == 0) { - while (i <= lengthMinusBlock) { - this.compress_(bytes, i); - i += this.blockSize; - } - } + // angle between touches + var angle = Math.atan2( + touch1.clientY - touch0.clientY, + touch1.clientX - touch0.clientX); - if (goog.isString(bytes)) { - while (i < opt_length) { - block[blockLength++] = bytes.charCodeAt(i++); - if (blockLength == this.blockSize) { - this.compress_(block); - blockLength = 0; - // Jump to the outer loop so we use the full-block optimization. - break; - } - } - } else { - while (i < opt_length) { - block[blockLength++] = bytes[i++]; - if (blockLength == this.blockSize) { - this.compress_(block); - blockLength = 0; - // Jump to the outer loop so we use the full-block optimization. - break; - } - } + if (goog.isDef(this.lastAngle_)) { + var delta = angle - this.lastAngle_; + this.rotationDelta_ += delta; + if (!this.rotating_ && + Math.abs(this.rotationDelta_) > this.threshold_) { + this.rotating_ = true; } + rotationDelta = delta; } + this.lastAngle_ = angle; - this.blockLength_ = blockLength; - this.totalLength_ += opt_length; -}; - + var map = mapBrowserEvent.map; -/** @override */ -goog.crypt.Md5.prototype.digest = function() { - // This must accommodate at least 1 padding byte (0x80), 8 bytes of - // total bitlength, and must end at a 64-byte boundary. - var pad = new Array((this.blockLength_ < 56 ? - this.blockSize : - this.blockSize * 2) - this.blockLength_); + // rotate anchor point. + // FIXME: should be the intersection point between the lines: + // touch0,touch1 and previousTouch0,previousTouch1 + var viewportPosition = goog.style.getClientPosition(map.getViewport()); + var centroid = + ol.interaction.Pointer.centroid(this.targetPointers); + centroid[0] -= viewportPosition.x; + centroid[1] -= viewportPosition.y; + this.anchor_ = map.getCoordinateFromPixel(centroid); - // Add padding: 0x80 0x00* - pad[0] = 0x80; - for (var i = 1; i < pad.length - 8; ++i) { - pad[i] = 0; - } - // Add the total number of bits, little endian 64-bit integer. - var totalBits = this.totalLength_ * 8; - for (var i = pad.length - 8; i < pad.length; ++i) { - pad[i] = totalBits & 0xff; - totalBits /= 0x100; // Don't use bit-shifting here! + // rotate + if (this.rotating_) { + var view = map.getView(); + var rotation = view.getRotation(); + map.render(); + ol.interaction.Interaction.rotateWithoutConstraints(map, view, + rotation + rotationDelta, this.anchor_); } - this.update(pad); +}; - var digest = new Array(16); - var n = 0; - for (var i = 0; i < 4; ++i) { - for (var j = 0; j < 32; j += 8) { - digest[n++] = (this.chain_[i] >>> j) & 0xff; + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.PinchRotate} + * @private + */ +ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.ViewHint.INTERACTING, -1); + if (this.rotating_) { + var rotation = view.getRotation(); + ol.interaction.Interaction.rotate( + map, view, rotation, this.anchor_, this.duration_); } + return false; + } else { + return true; } - return digest; }; -goog.provide('ol.structs.IHasChecksum'); - - /** - * @interface + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.PinchRotate} + * @private */ -ol.structs.IHasChecksum = function() { +ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + var map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastAngle_ = undefined; + this.rotating_ = false; + this.rotationDelta_ = 0.0; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ol.ViewHint.INTERACTING, 1); + } + map.render(); + return true; + } else { + return false; + } }; /** - * @return {string} The checksum. + * @inheritDoc */ -ol.structs.IHasChecksum.prototype.getChecksum = function() { -}; +ol.interaction.PinchRotate.prototype.shouldStopEvent = goog.functions.FALSE; -goog.provide('ol.style.Stroke'); +goog.provide('ol.interaction.PinchZoom'); -goog.require('goog.crypt'); -goog.require('goog.crypt.Md5'); -goog.require('ol.color'); -goog.require('ol.structs.IHasChecksum'); +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.style'); +goog.require('ol.Coordinate'); +goog.require('ol.ViewHint'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); /** * @classdesc - * Set stroke style for vector features. - * Note that the defaults given are the Canvas defaults, which will be used if - * option is not defined. The `get` functions return whatever was entered in - * the options; they will not return the default. + * Allows the user to zoom the map by pinching with two fingers + * on a touch screen. * * @constructor - * @param {olx.style.StrokeOptions=} opt_options Options. - * @implements {ol.structs.IHasChecksum} - * @api + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.PinchZoomOptions=} opt_options Options. + * @api stable */ -ol.style.Stroke = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; +ol.interaction.PinchZoom = function(opt_options) { - /** - * @private - * @type {ol.Color|string} - */ - this.color_ = goog.isDef(options.color) ? options.color : null; + goog.base(this, { + handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_, + handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_, + handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_ + }); - /** - * @private - * @type {string|undefined} - */ - this.lineCap_ = options.lineCap; + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {Array.<number>} + * @type {ol.Coordinate} */ - this.lineDash_ = goog.isDef(options.lineDash) ? options.lineDash : null; + this.anchor_ = null; /** * @private - * @type {string|undefined} + * @type {number} */ - this.lineJoin_ = options.lineJoin; + this.duration_ = goog.isDef(options.duration) ? options.duration : 400; /** * @private * @type {number|undefined} */ - this.miterLimit_ = options.miterLimit; + this.lastDistance_ = undefined; /** * @private - * @type {number|undefined} + * @type {number} */ - this.width_ = options.width; + this.lastScaleDelta_ = 1; - /** - * @private - * @type {string|undefined} - */ - this.checksum_ = undefined; }; +goog.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer); /** - * @return {ol.Color|string} Color. - * @api + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.PinchZoom} + * @private */ -ol.style.Stroke.prototype.getColor = function() { - return this.color_; +ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) { + goog.asserts.assert(this.targetPointers.length >= 2, + 'length of this.targetPointers should be 2 or more'); + var scaleDelta = 1.0; + + var touch0 = this.targetPointers[0]; + var touch1 = this.targetPointers[1]; + var dx = touch0.clientX - touch1.clientX; + var dy = touch0.clientY - touch1.clientY; + + // distance between touches + var distance = Math.sqrt(dx * dx + dy * dy); + + if (goog.isDef(this.lastDistance_)) { + scaleDelta = this.lastDistance_ / distance; + } + this.lastDistance_ = distance; + if (scaleDelta != 1.0) { + this.lastScaleDelta_ = scaleDelta; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + var resolution = view.getResolution(); + + // scale anchor point. + var viewportPosition = goog.style.getClientPosition(map.getViewport()); + var centroid = + ol.interaction.Pointer.centroid(this.targetPointers); + centroid[0] -= viewportPosition.x; + centroid[1] -= viewportPosition.y; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // scale, bypass the resolution constraint + map.render(); + ol.interaction.Interaction.zoomWithoutConstraints( + map, view, resolution * scaleDelta, this.anchor_); + }; /** - * @return {string|undefined} Line cap. - * @api + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.PinchZoom} + * @private */ -ol.style.Stroke.prototype.getLineCap = function() { - return this.lineCap_; +ol.interaction.PinchZoom.handleUpEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.ViewHint.INTERACTING, -1); + var resolution = view.getResolution(); + // Zoom to final resolution, with an animation, and provide a + // direction not to zoom out/in if user was pinching in/out. + // Direction is > 0 if pinching out, and < 0 if pinching in. + var direction = this.lastScaleDelta_ - 1; + ol.interaction.Interaction.zoom(map, view, resolution, + this.anchor_, this.duration_, direction); + return false; + } else { + return true; + } }; /** - * @return {Array.<number>} Line dash. - * @api + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.PinchZoom} + * @private */ -ol.style.Stroke.prototype.getLineDash = function() { - return this.lineDash_; +ol.interaction.PinchZoom.handleDownEvent_ = function(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + var map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastDistance_ = undefined; + this.lastScaleDelta_ = 1; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ol.ViewHint.INTERACTING, 1); + } + map.render(); + return true; + } else { + return false; + } }; /** - * @return {string|undefined} Line join. - * @api + * @inheritDoc */ -ol.style.Stroke.prototype.getLineJoin = function() { - return this.lineJoin_; -}; +ol.interaction.PinchZoom.prototype.shouldStopEvent = goog.functions.FALSE; + +goog.provide('ol.interaction'); + +goog.require('ol.Collection'); +goog.require('ol.Kinetic'); +goog.require('ol.interaction.DoubleClickZoom'); +goog.require('ol.interaction.DragPan'); +goog.require('ol.interaction.DragRotate'); +goog.require('ol.interaction.DragZoom'); +goog.require('ol.interaction.KeyboardPan'); +goog.require('ol.interaction.KeyboardZoom'); +goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.PinchRotate'); +goog.require('ol.interaction.PinchZoom'); /** - * @return {number|undefined} Miter limit. - * @api + * Set of interactions included in maps by default. Specific interactions can be + * excluded by setting the appropriate option to false in the constructor + * options, but the order of the interactions is fixed. If you want to specify + * a different order for interactions, you will need to create your own + * {@link ol.interaction.Interaction} instances and insert them into a + * {@link ol.Collection} in the order you want before creating your + * {@link ol.Map} instance. The default set of interactions, in sequence, is: + * * {@link ol.interaction.DragRotate} + * * {@link ol.interaction.DoubleClickZoom} + * * {@link ol.interaction.DragPan} + * * {@link ol.interaction.PinchRotate} + * * {@link ol.interaction.PinchZoom} + * * {@link ol.interaction.KeyboardPan} + * * {@link ol.interaction.KeyboardZoom} + * * {@link ol.interaction.MouseWheelZoom} + * * {@link ol.interaction.DragZoom} + * + * Note that DragZoom renders a box as a vector polygon, so this interaction + * should be excluded if you want a build with no vector support. + * + * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options. + * @return {ol.Collection.<ol.interaction.Interaction>} A collection of + * interactions to be used with the ol.Map constructor's interactions option. + * @api stable */ -ol.style.Stroke.prototype.getMiterLimit = function() { - return this.miterLimit_; +ol.interaction.defaults = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + var interactions = new ol.Collection(); + + var kinetic = new ol.Kinetic(-0.005, 0.05, 100); + + var altShiftDragRotate = goog.isDef(options.altShiftDragRotate) ? + options.altShiftDragRotate : true; + if (altShiftDragRotate) { + interactions.push(new ol.interaction.DragRotate()); + } + + var doubleClickZoom = goog.isDef(options.doubleClickZoom) ? + options.doubleClickZoom : true; + if (doubleClickZoom) { + interactions.push(new ol.interaction.DoubleClickZoom({ + delta: options.zoomDelta, + duration: options.zoomDuration + })); + } + + var dragPan = goog.isDef(options.dragPan) ? + options.dragPan : true; + if (dragPan) { + interactions.push(new ol.interaction.DragPan({ + kinetic: kinetic + })); + } + + var pinchRotate = goog.isDef(options.pinchRotate) ? + options.pinchRotate : true; + if (pinchRotate) { + interactions.push(new ol.interaction.PinchRotate()); + } + + var pinchZoom = goog.isDef(options.pinchZoom) ? + options.pinchZoom : true; + if (pinchZoom) { + interactions.push(new ol.interaction.PinchZoom({ + duration: options.zoomDuration + })); + } + + var keyboard = goog.isDef(options.keyboard) ? + options.keyboard : true; + if (keyboard) { + interactions.push(new ol.interaction.KeyboardPan()); + interactions.push(new ol.interaction.KeyboardZoom({ + delta: options.zoomDelta, + duration: options.zoomDuration + })); + } + + var mouseWheelZoom = goog.isDef(options.mouseWheelZoom) ? + options.mouseWheelZoom : true; + if (mouseWheelZoom) { + interactions.push(new ol.interaction.MouseWheelZoom({ + duration: options.zoomDuration + })); + } + + var shiftDragZoom = goog.isDef(options.shiftDragZoom) ? + options.shiftDragZoom : true; + if (shiftDragZoom) { + interactions.push(new ol.interaction.DragZoom()); + } + + return interactions; + }; +goog.provide('ol.layer.Group'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.math'); +goog.require('goog.object'); +goog.require('ol.Collection'); +goog.require('ol.CollectionEvent'); +goog.require('ol.CollectionEventType'); +goog.require('ol.Object'); +goog.require('ol.ObjectEventType'); +goog.require('ol.extent'); +goog.require('ol.layer.Base'); +goog.require('ol.source.State'); + /** - * @return {number|undefined} Width. - * @api + * @enum {string} */ -ol.style.Stroke.prototype.getWidth = function() { - return this.width_; +ol.layer.GroupProperty = { + LAYERS: 'layers' }; + /** - * Set the color. + * @classdesc + * A {@link ol.Collection} of layers that are handled together. * - * @param {ol.Color|string} color Color. - * @api + * A generic `change` event is triggered when the group/Collection changes. + * + * @constructor + * @extends {ol.layer.Base} + * @param {olx.layer.GroupOptions=} opt_options Layer options. + * @api stable */ -ol.style.Stroke.prototype.setColor = function(color) { - this.color_ = color; - this.checksum_ = undefined; +ol.layer.Group = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + var baseOptions = /** @type {olx.layer.GroupOptions} */ + (goog.object.clone(options)); + delete baseOptions.layers; + + var layers = options.layers; + + goog.base(this, baseOptions); + + /** + * @private + * @type {Array.<goog.events.Key>} + */ + this.layersListenerKeys_ = []; + + /** + * @private + * @type {Object.<string, Array.<goog.events.Key>>} + */ + this.listenerKeys_ = {}; + + goog.events.listen(this, + ol.Object.getChangeEventType(ol.layer.GroupProperty.LAYERS), + this.handleLayersChanged_, false, this); + + if (goog.isDefAndNotNull(layers)) { + if (goog.isArray(layers)) { + layers = new ol.Collection(layers.slice()); + } else { + goog.asserts.assertInstanceof(layers, ol.Collection, + 'layers should be an ol.Collection'); + layers = layers; + } + } else { + layers = new ol.Collection(); + } + + this.setLayers(layers); + }; +goog.inherits(ol.layer.Group, ol.layer.Base); /** - * Set the line cap. - * - * @param {string|undefined} lineCap Line cap. - * @api + * @private */ -ol.style.Stroke.prototype.setLineCap = function(lineCap) { - this.lineCap_ = lineCap; - this.checksum_ = undefined; +ol.layer.Group.prototype.handleLayerChange_ = function() { + if (this.getVisible()) { + this.changed(); + } }; /** - * Set the line dash. - * - * @param {Array.<number>} lineDash Line dash. - * @api + * @param {goog.events.Event} event Event. + * @private */ -ol.style.Stroke.prototype.setLineDash = function(lineDash) { - this.lineDash_ = lineDash; - this.checksum_ = undefined; +ol.layer.Group.prototype.handleLayersChanged_ = function(event) { + goog.array.forEach(this.layersListenerKeys_, goog.events.unlistenByKey); + this.layersListenerKeys_.length = 0; + + var layers = this.getLayers(); + this.layersListenerKeys_.push( + goog.events.listen(layers, ol.CollectionEventType.ADD, + this.handleLayersAdd_, false, this), + goog.events.listen(layers, ol.CollectionEventType.REMOVE, + this.handleLayersRemove_, false, this)); + + goog.object.forEach(this.listenerKeys_, function(keys) { + goog.array.forEach(keys, goog.events.unlistenByKey); + }); + goog.object.clear(this.listenerKeys_); + + var layersArray = layers.getArray(); + var i, ii, layer; + for (i = 0, ii = layersArray.length; i < ii; i++) { + layer = layersArray[i]; + this.listenerKeys_[goog.getUid(layer).toString()] = [ + goog.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE, + this.handleLayerChange_, false, this), + goog.events.listen(layer, goog.events.EventType.CHANGE, + this.handleLayerChange_, false, this) + ]; + } + + this.changed(); }; /** - * Set the line join. - * - * @param {string|undefined} lineJoin Line join. - * @api + * @param {ol.CollectionEvent} collectionEvent Collection event. + * @private */ -ol.style.Stroke.prototype.setLineJoin = function(lineJoin) { - this.lineJoin_ = lineJoin; - this.checksum_ = undefined; +ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) { + var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); + var key = goog.getUid(layer).toString(); + goog.asserts.assert(!(key in this.listenerKeys_), + 'listeners already registered'); + this.listenerKeys_[key] = [ + goog.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE, + this.handleLayerChange_, false, this), + goog.events.listen(layer, goog.events.EventType.CHANGE, + this.handleLayerChange_, false, this) + ]; + this.changed(); }; /** - * Set the miter limit. - * - * @param {number|undefined} miterLimit Miter limit. - * @api + * @param {ol.CollectionEvent} collectionEvent Collection event. + * @private */ -ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) { - this.miterLimit_ = miterLimit; - this.checksum_ = undefined; +ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) { + var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); + var key = goog.getUid(layer).toString(); + goog.asserts.assert(key in this.listenerKeys_, 'no listeners to unregister'); + goog.array.forEach(this.listenerKeys_[key], goog.events.unlistenByKey); + delete this.listenerKeys_[key]; + this.changed(); }; /** - * Set the width. - * - * @param {number|undefined} width Width. - * @api + * Returns the {@link ol.Collection collection} of {@link ol.layer.Layer layers} + * in this group. + * @return {!ol.Collection.<ol.layer.Base>} Collection of + * {@link ol.layer.Base layers} that are part of this group. + * @observable + * @api stable */ -ol.style.Stroke.prototype.setWidth = function(width) { - this.width_ = width; - this.checksum_ = undefined; +ol.layer.Group.prototype.getLayers = function() { + return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get( + ol.layer.GroupProperty.LAYERS)); }; /** - * @inheritDoc + * Set the {@link ol.Collection collection} of {@link ol.layer.Layer layers} + * in this group. + * @param {!ol.Collection.<ol.layer.Base>} layers Collection of + * {@link ol.layer.Base layers} that are part of this group. + * @observable + * @api stable */ -ol.style.Stroke.prototype.getChecksum = function() { - if (!goog.isDef(this.checksum_)) { - var raw = 's' + - (!goog.isNull(this.color_) ? - ol.color.asString(this.color_) : '-') + ',' + - (goog.isDef(this.lineCap_) ? - this.lineCap_.toString() : '-') + ',' + - (!goog.isNull(this.lineDash_) ? - this.lineDash_.toString() : '-') + ',' + - (goog.isDef(this.lineJoin_) ? - this.lineJoin_ : '-') + ',' + - (goog.isDef(this.miterLimit_) ? - this.miterLimit_.toString() : '-') + ',' + - (goog.isDef(this.width_) ? - this.width_.toString() : '-'); - - var md5 = new goog.crypt.Md5(); - md5.update(raw); - this.checksum_ = goog.crypt.byteArrayToString(md5.digest()); - } - - return this.checksum_; +ol.layer.Group.prototype.setLayers = function(layers) { + this.set(ol.layer.GroupProperty.LAYERS, layers); }; -goog.provide('ol.render.canvas'); - /** - * @typedef {{fillStyle: string}} + * @inheritDoc */ -ol.render.canvas.FillState; +ol.layer.Group.prototype.getLayersArray = function(opt_array) { + var array = goog.isDef(opt_array) ? opt_array : []; + this.getLayers().forEach(function(layer) { + layer.getLayersArray(array); + }); + return array; +}; /** - * @typedef {{lineCap: string, - * lineDash: Array.<number>, - * lineJoin: string, - * lineWidth: number, - * miterLimit: number, - * strokeStyle: string}} + * @inheritDoc */ -ol.render.canvas.StrokeState; +ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) { + var states = goog.isDef(opt_states) ? opt_states : []; + + var pos = states.length; + + this.getLayers().forEach(function(layer) { + layer.getLayerStatesArray(states); + }); + + var ownLayerState = this.getLayerState(); + var i, ii, layerState; + for (i = pos, ii = states.length; i < ii; i++) { + layerState = states[i]; + layerState.brightness = goog.math.clamp( + layerState.brightness + ownLayerState.brightness, -1, 1); + layerState.contrast *= ownLayerState.contrast; + layerState.hue += ownLayerState.hue; + layerState.opacity *= ownLayerState.opacity; + layerState.saturation *= ownLayerState.saturation; + layerState.visible = layerState.visible && ownLayerState.visible; + layerState.maxResolution = Math.min( + layerState.maxResolution, ownLayerState.maxResolution); + layerState.minResolution = Math.max( + layerState.minResolution, ownLayerState.minResolution); + if (goog.isDef(ownLayerState.extent)) { + if (goog.isDef(layerState.extent)) { + layerState.extent = ol.extent.getIntersection( + layerState.extent, ownLayerState.extent); + } else { + layerState.extent = ownLayerState.extent; + } + } + } + + return states; +}; /** - * @typedef {{font: string, - * textAlign: string, - * textBaseline: string}} + * @inheritDoc */ -ol.render.canvas.TextState; +ol.layer.Group.prototype.getSourceState = function() { + return ol.source.State.READY; +}; + +goog.provide('ol.proj.EPSG3857'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.math'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); + /** - * @const - * @type {string} + * @classdesc + * Projection object for web/spherical Mercator (EPSG:3857). + * + * @constructor + * @extends {ol.proj.Projection} + * @param {string} code Code. + * @private */ -ol.render.canvas.defaultFont = '10px sans-serif'; +ol.proj.EPSG3857_ = function(code) { + goog.base(this, { + code: code, + units: ol.proj.Units.METERS, + extent: ol.proj.EPSG3857.EXTENT, + global: true, + worldExtent: ol.proj.EPSG3857.WORLD_EXTENT + }); +}; +goog.inherits(ol.proj.EPSG3857_, ol.proj.Projection); /** - * @const - * @type {ol.Color} + * @inheritDoc */ -ol.render.canvas.defaultFillStyle = [0, 0, 0, 1]; +ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) { + return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS); +}; /** * @const - * @type {string} + * @type {number} */ -ol.render.canvas.defaultLineCap = 'round'; +ol.proj.EPSG3857.RADIUS = 6378137; /** * @const - * @type {Array.<number>} + * @type {number} */ -ol.render.canvas.defaultLineDash = []; +ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS; /** * @const - * @type {string} + * @type {ol.Extent} */ -ol.render.canvas.defaultLineJoin = 'round'; +ol.proj.EPSG3857.EXTENT = [ + -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE, + ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE +]; /** * @const - * @type {number} + * @type {ol.Extent} */ -ol.render.canvas.defaultMiterLimit = 10; +ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85]; /** - * @const - * @type {ol.Color} + * Lists several projection codes with the same meaning as EPSG:3857. + * + * @type {Array.<string>} */ -ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1]; +ol.proj.EPSG3857.CODES = [ + 'EPSG:3857', + 'EPSG:102100', + 'EPSG:102113', + 'EPSG:900913', + 'urn:ogc:def:crs:EPSG:6.18:3:3857', + 'urn:ogc:def:crs:EPSG::3857', + 'http://www.opengis.net/gml/srs/epsg.xml#3857' +]; /** + * Projections equal to EPSG:3857. + * * @const - * @type {string} + * @type {Array.<ol.proj.Projection>} */ -ol.render.canvas.defaultTextAlign = 'center'; +ol.proj.EPSG3857.PROJECTIONS = goog.array.map( + ol.proj.EPSG3857.CODES, + function(code) { + return new ol.proj.EPSG3857_(code); + }); /** - * @const - * @type {string} + * Transformation from EPSG:4326 to EPSG:3857. + * + * @param {Array.<number>} input Input array of coordinate values. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension (default is `2`). + * @return {Array.<number>} Output array of coordinate values. */ -ol.render.canvas.defaultTextBaseline = 'middle'; +ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) { + var length = input.length, + dimension = opt_dimension > 1 ? opt_dimension : 2, + output = opt_output; + if (!goog.isDef(output)) { + if (dimension > 2) { + // preserve values beyond second dimension + output = input.slice(); + } else { + output = new Array(length); + } + } + goog.asserts.assert(output.length % dimension === 0, + 'modulus of output.length with dimension should be 0'); + for (var i = 0; i < length; i += dimension) { + output[i] = ol.proj.EPSG3857.RADIUS * Math.PI * input[i] / 180; + output[i + 1] = ol.proj.EPSG3857.RADIUS * + Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360)); + } + return output; +}; /** - * @const - * @type {number} + * Transformation from EPSG:3857 to EPSG:4326. + * + * @param {Array.<number>} input Input array of coordinate values. + * @param {Array.<number>=} opt_output Output array of coordinate values. + * @param {number=} opt_dimension Dimension (default is `2`). + * @return {Array.<number>} Output array of coordinate values. */ -ol.render.canvas.defaultLineWidth = 1; +ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) { + var length = input.length, + dimension = opt_dimension > 1 ? opt_dimension : 2, + output = opt_output; + if (!goog.isDef(output)) { + if (dimension > 2) { + // preserve values beyond second dimension + output = input.slice(); + } else { + output = new Array(length); + } + } + goog.asserts.assert(output.length % dimension === 0, + 'modulus of output.length with dimension should be 0'); + for (var i = 0; i < length; i += dimension) { + output[i] = 180 * input[i] / (ol.proj.EPSG3857.RADIUS * Math.PI); + output[i + 1] = 360 * Math.atan( + Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90; + } + return output; +}; -goog.provide('ol.style.Fill'); +goog.provide('ol.proj.EPSG4326'); -goog.require('ol.color'); -goog.require('ol.structs.IHasChecksum'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); /** * @classdesc - * Set fill style for vector features. + * Projection object for WGS84 geographic coordinates (EPSG:4326). + * + * Note that OpenLayers does not strictly comply with the EPSG definition. + * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x). + * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates. * * @constructor - * @param {olx.style.FillOptions=} opt_options Options. - * @implements {ol.structs.IHasChecksum} - * @api + * @extends {ol.proj.Projection} + * @param {string} code Code. + * @param {string=} opt_axisOrientation Axis orientation. + * @private */ -ol.style.Fill = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.Color|string} - */ - this.color_ = goog.isDef(options.color) ? options.color : null; - - /** - * @private - * @type {string|undefined} - */ - this.checksum_ = undefined; +ol.proj.EPSG4326_ = function(code, opt_axisOrientation) { + goog.base(this, { + code: code, + units: ol.proj.Units.DEGREES, + extent: ol.proj.EPSG4326.EXTENT, + axisOrientation: opt_axisOrientation, + global: true, + worldExtent: ol.proj.EPSG4326.EXTENT + }); }; +goog.inherits(ol.proj.EPSG4326_, ol.proj.Projection); /** - * @return {ol.Color|string} Color. - * @api + * @inheritDoc */ -ol.style.Fill.prototype.getColor = function() { - return this.color_; +ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) { + return resolution; }; /** - * Set the color. + * Extent of the EPSG:4326 projection which is the whole world. * - * @param {ol.Color|string} color Color. - * @api + * @const + * @type {ol.Extent} */ -ol.style.Fill.prototype.setColor = function(color) { - this.color_ = color; - this.checksum_ = undefined; -}; +ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90]; /** - * @inheritDoc + * Projections equal to EPSG:4326. + * + * @const + * @type {Array.<ol.proj.Projection>} */ -ol.style.Fill.prototype.getChecksum = function() { - if (!goog.isDef(this.checksum_)) { - this.checksum_ = 'f' + (!goog.isNull(this.color_) ? - ol.color.asString(this.color_) : '-'); - } +ol.proj.EPSG4326.PROJECTIONS = [ + new ol.proj.EPSG4326_('CRS:84'), + new ol.proj.EPSG4326_('EPSG:4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG::4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:1.3:CRS84'), + new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:2:84'), + new ol.proj.EPSG4326_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'), + new ol.proj.EPSG4326_('urn:x-ogc:def:crs:EPSG:4326', 'neu') +]; - return this.checksum_; +goog.provide('ol.proj.common'); + +goog.require('ol.proj'); +goog.require('ol.proj.EPSG3857'); +goog.require('ol.proj.EPSG4326'); + + +/** + * FIXME empty description for jsdoc + * @api + */ +ol.proj.common.add = function() { + // Add transformations that don't alter coordinates to convert within set of + // projections with equal meaning. + ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS); + ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS); + // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like + // coordinates and back. + ol.proj.addEquivalentTransforms( + ol.proj.EPSG4326.PROJECTIONS, + ol.proj.EPSG3857.PROJECTIONS, + ol.proj.EPSG3857.fromEPSG4326, + ol.proj.EPSG3857.toEPSG4326); }; -goog.provide('ol.style.Circle'); +goog.provide('ol.layer.Image'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('ol.color'); -goog.require('ol.has'); -goog.require('ol.render.canvas'); -goog.require('ol.structs.IHasChecksum'); -goog.require('ol.style.Fill'); -goog.require('ol.style.Image'); -goog.require('ol.style.ImageState'); -goog.require('ol.style.Stroke'); +goog.require('ol.layer.Layer'); /** * @classdesc - * Set circle style for vector features. + * Server-rendered images that are available for arbitrary extents and + * resolutions. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. * * @constructor - * @param {olx.style.CircleOptions=} opt_options Options. - * @extends {ol.style.Image} - * @implements {ol.structs.IHasChecksum} - * @api + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.ImageOptions=} opt_options Layer options. + * @api stable */ -ol.style.Circle = function(opt_options) { - +ol.layer.Image = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; + goog.base(this, /** @type {olx.layer.LayerOptions} */ (options)); +}; +goog.inherits(ol.layer.Image, ol.layer.Layer); - /** - * @private - * @type {Array.<string>} - */ - this.checksums_ = null; - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = null; - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.hitDetectionCanvas_ = null; - - /** - * @private - * @type {ol.style.Fill} - */ - this.fill_ = goog.isDef(options.fill) ? options.fill : null; - - /** - * @private - * @type {ol.style.Stroke} - */ - this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; - /** - * @private - * @type {number} - */ - this.radius_ = options.radius; +/** + * Return the associated {@link ol.source.Image source} of the image layer. + * @function + * @return {ol.source.Image} Source. + * @api stable + */ +ol.layer.Image.prototype.getSource; - /** - * @private - * @type {Array.<number>} - */ - this.origin_ = [0, 0]; +goog.provide('ol.layer.Tile'); - /** - * @private - * @type {Array.<number>} - */ - this.hitDetectionOrigin_ = [0, 0]; +goog.require('goog.object'); +goog.require('ol.layer.Layer'); - /** - * @private - * @type {Array.<number>} - */ - this.anchor_ = null; - /** - * @private - * @type {ol.Size} - */ - this.size_ = null; +/** + * @enum {string} + */ +ol.layer.TileProperty = { + PRELOAD: 'preload', + USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' +}; - /** - * @private - * @type {ol.Size} - */ - this.imageSize_ = null; - /** - * @private - * @type {ol.Size} - */ - this.hitDetectionImageSize_ = null; - this.render_(options.atlasManager); +/** + * @classdesc + * For layer sources that provide pre-rendered, tiled images in grids that are + * organized by zoom levels for specific resolutions. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.TileOptions=} opt_options Tile layer options. + * @api stable + */ +ol.layer.Tile = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; - /** - * @type {boolean} - */ - var snapToPixel = goog.isDef(options.snapToPixel) ? - options.snapToPixel : true; + var baseOptions = goog.object.clone(options); - goog.base(this, { - opacity: 1, - rotateWithView: false, - rotation: 0, - scale: 1, - snapToPixel: snapToPixel - }); + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + this.setPreload(goog.isDef(options.preload) ? options.preload : 0); + this.setUseInterimTilesOnError(goog.isDef(options.useInterimTilesOnError) ? + options.useInterimTilesOnError : true); }; -goog.inherits(ol.style.Circle, ol.style.Image); +goog.inherits(ol.layer.Tile, ol.layer.Layer); /** - * @inheritDoc + * Return the level as number to which we will preload tiles up to. + * @return {number} The level to preload tiles up to. + * @observable * @api */ -ol.style.Circle.prototype.getAnchor = function() { - return this.anchor_; +ol.layer.Tile.prototype.getPreload = function() { + return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD)); }; /** - * @return {ol.style.Fill} Fill style. - * @api + * Return the associated {@link ol.source.Tile tilesource} of the layer. + * @function + * @return {ol.source.Tile} Source. + * @api stable */ -ol.style.Circle.prototype.getFill = function() { - return this.fill_; -}; +ol.layer.Tile.prototype.getSource; /** - * @inheritDoc + * Set the level as number to which we will preload tiles up to. + * @param {number} preload The level to preload tiles up to. + * @observable + * @api */ -ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) { - return this.hitDetectionCanvas_; +ol.layer.Tile.prototype.setPreload = function(preload) { + this.set(ol.layer.TileProperty.PRELOAD, preload); }; /** - * @param {number} pixelRatio Pixel ratio. - * @return {HTMLCanvasElement} Canvas element. + * Whether we use interim tiles on error. + * @return {boolean} Use interim tiles on error. + * @observable * @api */ -ol.style.Circle.prototype.getImage = function(pixelRatio) { - return this.canvas_; +ol.layer.Tile.prototype.getUseInterimTilesOnError = function() { + return /** @type {boolean} */ ( + this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR)); }; /** - * @inheritDoc + * Set whether we use interim tiles on error. + * @param {boolean} useInterimTilesOnError Use interim tiles on error. + * @observable + * @api */ -ol.style.Circle.prototype.getImageState = function() { - return ol.style.ImageState.LOADED; +ol.layer.Tile.prototype.setUseInterimTilesOnError = + function(useInterimTilesOnError) { + this.set( + ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); }; +goog.provide('ol.layer.Vector'); -/** - * @inheritDoc - */ -ol.style.Circle.prototype.getImageSize = function() { - return this.imageSize_; -}; +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.layer.Layer'); +goog.require('ol.style.Style'); /** - * @inheritDoc + * @enum {string} */ -ol.style.Circle.prototype.getHitDetectionImageSize = function() { - return this.hitDetectionImageSize_; +ol.layer.VectorProperty = { + RENDER_ORDER: 'renderOrder' }; + /** - * @inheritDoc - * @api + * @classdesc + * Vector data that is rendered client-side. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Layer} + * @fires ol.render.Event + * @param {olx.layer.VectorOptions=} opt_options Options. + * @api stable */ -ol.style.Circle.prototype.getOrigin = function() { - return this.origin_; +ol.layer.Vector = function(opt_options) { + + var options = goog.isDef(opt_options) ? + opt_options : /** @type {olx.layer.VectorOptions} */ ({}); + + goog.asserts.assert( + !goog.isDef(options.renderOrder) || goog.isNull(options.renderOrder) || + goog.isFunction(options.renderOrder), + 'renderOrder must be a comparator function'); + + var baseOptions = goog.object.clone(options); + + delete baseOptions.style; + delete baseOptions.renderBuffer; + delete baseOptions.updateWhileAnimating; + delete baseOptions.updateWhileInteracting; + goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); + + /** + * @type {number} + * @private + */ + this.renderBuffer_ = goog.isDef(options.renderBuffer) ? + options.renderBuffer : 100; + + /** + * User provided style. + * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} + * @private + */ + this.style_ = null; + + /** + * Style function for use within the library. + * @type {ol.style.StyleFunction|undefined} + * @private + */ + this.styleFunction_ = undefined; + + this.setStyle(options.style); + + /** + * @type {boolean} + * @private + */ + this.updateWhileAnimating_ = goog.isDef(options.updateWhileAnimating) ? + options.updateWhileAnimating : false; + + /** + * @type {boolean} + * @private + */ + this.updateWhileInteracting_ = goog.isDef(options.updateWhileInteracting) ? + options.updateWhileInteracting : false; + }; +goog.inherits(ol.layer.Vector, ol.layer.Layer); /** - * @inheritDoc + * @return {number|undefined} Render buffer. */ -ol.style.Circle.prototype.getHitDetectionOrigin = function() { - return this.hitDetectionOrigin_; +ol.layer.Vector.prototype.getRenderBuffer = function() { + return this.renderBuffer_; }; /** - * @return {number} Radius. - * @api + * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render + * order. */ -ol.style.Circle.prototype.getRadius = function() { - return this.radius_; +ol.layer.Vector.prototype.getRenderOrder = function() { + return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ ( + this.get(ol.layer.VectorProperty.RENDER_ORDER)); }; /** - * @inheritDoc - * @api + * Return the associated {@link ol.source.Vector vectorsource} of the layer. + * @function + * @return {ol.source.Vector} Source. + * @api stable */ -ol.style.Circle.prototype.getSize = function() { - return this.size_; -}; +ol.layer.Vector.prototype.getSource; /** - * @return {ol.style.Stroke} Stroke style. - * @api + * Get the style for features. This returns whatever was passed to the `style` + * option at construction or to the `setStyle` method. + * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} + * Layer style. + * @api stable */ -ol.style.Circle.prototype.getStroke = function() { - return this.stroke_; +ol.layer.Vector.prototype.getStyle = function() { + return this.style_; }; /** - * @inheritDoc + * Get the style function. + * @return {ol.style.StyleFunction|undefined} Layer style function. + * @api stable */ -ol.style.Circle.prototype.listenImageChange = goog.nullFunction; +ol.layer.Vector.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; /** - * @inheritDoc + * @return {boolean} Whether the rendered layer should be updated while + * animating. */ -ol.style.Circle.prototype.load = goog.nullFunction; +ol.layer.Vector.prototype.getUpdateWhileAnimating = function() { + return this.updateWhileAnimating_; +}; /** - * @inheritDoc + * @return {boolean} Whether the rendered layer should be updated while + * interacting. */ -ol.style.Circle.prototype.unlistenImageChange = goog.nullFunction; +ol.layer.Vector.prototype.getUpdateWhileInteracting = function() { + return this.updateWhileInteracting_; +}; /** - * @typedef {{strokeStyle: (string|undefined), strokeWidth: number, - * size: number, lineDash: Array.<number>}} + * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder + * Render order. */ -ol.style.Circle.RenderOptions; +ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) { + goog.asserts.assert( + !goog.isDef(renderOrder) || goog.isNull(renderOrder) || + goog.isFunction(renderOrder), + 'renderOrder must be a comparator function'); + this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder); +}; /** - * @private - * @param {ol.style.AtlasManager|undefined} atlasManager + * Set the style for features. This can be a single style object, an array + * of styles, or a function that takes a feature and resolution and returns + * an array of styles. If it is `undefined` the default style is used. If + * it is `null` the layer has no style (a `null` style), so only features + * that have their own styles will be rendered in the layer. See + * {@link ol.style} for information on the default style. + * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined} + * style Layer style. + * @api stable */ -ol.style.Circle.prototype.render_ = function(atlasManager) { - var imageSize; - var lineDash = null; - var strokeStyle; - var strokeWidth = 0; - - if (!goog.isNull(this.stroke_)) { - strokeStyle = ol.color.asString(this.stroke_.getColor()); - strokeWidth = this.stroke_.getWidth(); - if (!goog.isDef(strokeWidth)) { - strokeWidth = ol.render.canvas.defaultLineWidth; - } - lineDash = this.stroke_.getLineDash(); - if (!ol.has.CANVAS_LINE_DASH) { - lineDash = null; - } - } - - - var size = 2 * (this.radius_ + strokeWidth) + 1; - - /** @type {ol.style.Circle.RenderOptions} */ - var renderOptions = { - strokeStyle: strokeStyle, - strokeWidth: strokeWidth, - size: size, - lineDash: lineDash - }; - - if (!goog.isDef(atlasManager)) { - // no atlas manager is used, create a new canvas - this.canvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement(goog.dom.TagName.CANVAS)); - this.canvas_.height = size; - this.canvas_.width = size; - - // canvas.width and height are rounded to the closest integer - size = this.canvas_.width; - imageSize = size; - - // draw the circle on the canvas - var context = /** @type {CanvasRenderingContext2D} */ - (this.canvas_.getContext('2d')); - this.draw_(renderOptions, context, 0, 0); - - this.createHitDetectionCanvas_(renderOptions); - } else { - // an atlas manager is used, add the symbol to an atlas - size = Math.round(size); - - var hasCustomHitDetectionImage = goog.isNull(this.fill_); - var renderHitDetectionCallback; - if (hasCustomHitDetectionImage) { - // render the hit-detection image into a separate atlas image - renderHitDetectionCallback = - goog.bind(this.drawHitDetectionCanvas_, this, renderOptions); - } +ol.layer.Vector.prototype.setStyle = function(style) { + this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction; + this.styleFunction_ = goog.isNull(style) ? + undefined : ol.style.createStyleFunction(this.style_); + this.changed(); +}; - var id = this.getChecksum(); - var info = atlasManager.add( - id, size, size, goog.bind(this.draw_, this, renderOptions), - renderHitDetectionCallback); - goog.asserts.assert(info !== null, 'circle radius is too large'); +// FIXME test, especially polygons with holes and multipolygons +// FIXME need to handle large thick features (where pixel size matters) +// FIXME add offset and end to ol.geom.flat.transform.transform2D? - this.canvas_ = info.image; - this.origin_ = [info.offsetX, info.offsetY]; - imageSize = info.image.width; +goog.provide('ol.render.canvas.Immediate'); - if (hasCustomHitDetectionImage) { - this.hitDetectionCanvas_ = info.hitImage; - this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY]; - this.hitDetectionImageSize_ = - [info.hitImage.width, info.hitImage.height]; - } else { - this.hitDetectionCanvas_ = this.canvas_; - this.hitDetectionOrigin_ = this.origin_; - this.hitDetectionImageSize_ = [imageSize, imageSize]; - } - } +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('goog.vec.Mat4'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.has'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas'); +goog.require('ol.vec.Mat4'); - this.anchor_ = [size / 2, size / 2]; - this.size_ = [size, size]; - this.imageSize_ = [imageSize, imageSize]; -}; /** - * @private - * @param {ol.style.Circle.RenderOptions} renderOptions - * @param {CanvasRenderingContext2D} context - * @param {number} x The origin for the symbol (x). - * @param {number} y The origin for the symbol (y). + * @classdesc + * A concrete subclass of {@link ol.render.VectorContext} that implements + * direct rendering of features and geometries to an HTML5 Canvas context. + * Instances of this class are created internally by the library and + * provided to application code as vectorContext member of the + * {@link ol.render.Event} object associated with postcompose, precompose and + * render events emitted by layers and maps. + * + * @constructor + * @extends {ol.render.VectorContext} + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Extent} extent Extent. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {number} viewRotation View rotation. + * @struct */ -ol.style.Circle.prototype.draw_ = function(renderOptions, context, x, y) { - // reset transform - context.setTransform(1, 0, 0, 1, 0, 0); - - // then move to (x, y) - context.translate(x, y); +ol.render.canvas.Immediate = + function(context, pixelRatio, extent, transform, viewRotation) { - context.beginPath(); - context.arc( - renderOptions.size / 2, renderOptions.size / 2, - this.radius_, 0, 2 * Math.PI, true); + /** + * @private + * @type {Object.<string, + * Array.<function(ol.render.canvas.Immediate)>>} + */ + this.callbacksByZIndex_ = {}; - if (!goog.isNull(this.fill_)) { - context.fillStyle = ol.color.asString(this.fill_.getColor()); - context.fill(); - } - if (!goog.isNull(this.stroke_)) { - context.strokeStyle = renderOptions.strokeStyle; - context.lineWidth = renderOptions.strokeWidth; - if (!goog.isNull(renderOptions.lineDash)) { - context.setLineDash(renderOptions.lineDash); - } - context.stroke(); - } - context.closePath(); -}; + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = context; + /** + * @private + * @type {number} + */ + this.pixelRatio_ = pixelRatio; -/** - * @private - * @param {ol.style.Circle.RenderOptions} renderOptions - */ -ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) { - this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; - if (!goog.isNull(this.fill_)) { - this.hitDetectionCanvas_ = this.canvas_; - return; - } + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; - // if no fill style is set, create an extra hit-detection image with a - // default fill style - this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement(goog.dom.TagName.CANVAS)); - var canvas = this.hitDetectionCanvas_; + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.transform_ = transform; - canvas.height = renderOptions.size; - canvas.width = renderOptions.size; + /** + * @private + * @type {number} + */ + this.viewRotation_ = viewRotation; - var context = /** @type {CanvasRenderingContext2D} */ - (canvas.getContext('2d')); - this.drawHitDetectionCanvas_(renderOptions, context, 0, 0); -}; + /** + * @private + * @type {?ol.render.canvas.FillState} + */ + this.contextFillState_ = null; + /** + * @private + * @type {?ol.render.canvas.StrokeState} + */ + this.contextStrokeState_ = null; -/** - * @private - * @param {ol.style.Circle.RenderOptions} renderOptions - * @param {CanvasRenderingContext2D} context - * @param {number} x The origin for the symbol (x). - * @param {number} y The origin for the symbol (y). - */ -ol.style.Circle.prototype.drawHitDetectionCanvas_ = - function(renderOptions, context, x, y) { - // reset transform - context.setTransform(1, 0, 0, 1, 0, 0); + /** + * @private + * @type {?ol.render.canvas.TextState} + */ + this.contextTextState_ = null; - // then move to (x, y) - context.translate(x, y); + /** + * @private + * @type {?ol.render.canvas.FillState} + */ + this.fillState_ = null; - context.beginPath(); - context.arc( - renderOptions.size / 2, renderOptions.size / 2, - this.radius_, 0, 2 * Math.PI, true); + /** + * @private + * @type {?ol.render.canvas.StrokeState} + */ + this.strokeState_ = null; - context.fillStyle = ol.render.canvas.defaultFillStyle; - context.fill(); - if (!goog.isNull(this.stroke_)) { - context.strokeStyle = renderOptions.strokeStyle; - context.lineWidth = renderOptions.strokeWidth; - if (!goog.isNull(renderOptions.lineDash)) { - context.setLineDash(renderOptions.lineDash); - } - context.stroke(); - } - context.closePath(); -}; + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.image_ = null; + /** + * @private + * @type {number} + */ + this.imageAnchorX_ = 0; -/** - * @inheritDoc - */ -ol.style.Circle.prototype.getChecksum = function() { - var strokeChecksum = !goog.isNull(this.stroke_) ? - this.stroke_.getChecksum() : '-'; - var fillChecksum = !goog.isNull(this.fill_) ? - this.fill_.getChecksum() : '-'; + /** + * @private + * @type {number} + */ + this.imageAnchorY_ = 0; - var recalculate = goog.isNull(this.checksums_) || - (strokeChecksum != this.checksums_[1] || - fillChecksum != this.checksums_[2] || - this.radius_ != this.checksums_[3]); + /** + * @private + * @type {number} + */ + this.imageHeight_ = 0; - if (recalculate) { - var checksum = 'c' + strokeChecksum + fillChecksum + - (goog.isDef(this.radius_) ? this.radius_.toString() : '-'); - this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_]; - } + /** + * @private + * @type {number} + */ + this.imageOpacity_ = 0; - return this.checksums_[0]; -}; + /** + * @private + * @type {number} + */ + this.imageOriginX_ = 0; -goog.provide('ol.style.Style'); -goog.provide('ol.style.defaultGeometryFunction'); + /** + * @private + * @type {number} + */ + this.imageOriginY_ = 0; -goog.require('goog.asserts'); -goog.require('goog.functions'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.style.Circle'); -goog.require('ol.style.Fill'); -goog.require('ol.style.Image'); -goog.require('ol.style.Stroke'); + /** + * @private + * @type {boolean} + */ + this.imageRotateWithView_ = false; + /** + * @private + * @type {number} + */ + this.imageRotation_ = 0; + /** + * @private + * @type {number} + */ + this.imageScale_ = 0; -/** - * @classdesc - * Container for vector feature rendering styles. Any changes made to the style - * or its children through `set*()` methods will not take effect until the - * feature, layer or FeatureOverlay that uses the style is re-rendered. - * - * @constructor - * @param {olx.style.StyleOptions=} opt_options Style options. - * @api - */ -ol.style.Style = function(opt_options) { + /** + * @private + * @type {boolean} + */ + this.imageSnapToPixel_ = false; - var options = goog.isDef(opt_options) ? opt_options : {}; + /** + * @private + * @type {number} + */ + this.imageWidth_ = 0; /** * @private - * @type {string|ol.geom.Geometry|ol.style.GeometryFunction} + * @type {string} */ - this.geometry_ = null; + this.text_ = ''; /** * @private - * @type {!ol.style.GeometryFunction} + * @type {number} */ - this.geometryFunction_ = ol.style.defaultGeometryFunction; + this.textOffsetX_ = 0; - if (goog.isDef(options.geometry)) { - this.setGeometry(options.geometry); - } + /** + * @private + * @type {number} + */ + this.textOffsetY_ = 0; /** * @private - * @type {ol.style.Fill} + * @type {number} */ - this.fill_ = goog.isDef(options.fill) ? options.fill : null; + this.textRotation_ = 0; /** * @private - * @type {ol.style.Image} + * @type {number} */ - this.image_ = goog.isDef(options.image) ? options.image : null; + this.textScale_ = 0; /** * @private - * @type {ol.style.Stroke} + * @type {?ol.render.canvas.FillState} */ - this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; + this.textFillState_ = null; /** * @private - * @type {ol.style.Text} + * @type {?ol.render.canvas.StrokeState} */ - this.text_ = goog.isDef(options.text) ? options.text : null; + this.textStrokeState_ = null; /** * @private - * @type {number|undefined} + * @type {?ol.render.canvas.TextState} */ - this.zIndex_ = options.zIndex; + this.textState_ = null; -}; + /** + * @private + * @type {Array.<number>} + */ + this.pixelCoordinates_ = []; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.tmpLocalTransform_ = goog.vec.Mat4.createNumber(); -/** - * @return {string|ol.geom.Geometry|ol.style.GeometryFunction} - * Feature property or geometry or function that returns the geometry that will - * be rendered with this style. - * @api - */ -ol.style.Style.prototype.getGeometry = function() { - return this.geometry_; }; /** - * @return {!ol.style.GeometryFunction} Function that is called with a feature - * and returns the geometry to render instead of the feature's geometry. - * @api + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private */ -ol.style.Style.prototype.getGeometryFunction = function() { - return this.geometryFunction_; +ol.render.canvas.Immediate.prototype.drawImages_ = + function(flatCoordinates, offset, end, stride) { + if (goog.isNull(this.image_)) { + return; + } + goog.asserts.assert(offset === 0, 'offset should be 0'); + goog.asserts.assert(end == flatCoordinates.length, + 'end should be equal to the length of flatCoordinates'); + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, 2, this.transform_, + this.pixelCoordinates_); + var context = this.context_; + var localTransform = this.tmpLocalTransform_; + var alpha = context.globalAlpha; + if (this.imageOpacity_ != 1) { + context.globalAlpha = alpha * this.imageOpacity_; + } + var rotation = this.imageRotation_; + if (this.imageRotateWithView_) { + rotation += this.viewRotation_; + } + var i, ii; + for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) { + var x = pixelCoordinates[i] - this.imageAnchorX_; + var y = pixelCoordinates[i + 1] - this.imageAnchorY_; + if (this.imageSnapToPixel_) { + x = (x + 0.5) | 0; + y = (y + 0.5) | 0; + } + if (rotation !== 0 || this.imageScale_ != 1) { + var centerX = x + this.imageAnchorX_; + var centerY = y + this.imageAnchorY_; + ol.vec.Mat4.makeTransform2D(localTransform, + centerX, centerY, this.imageScale_, this.imageScale_, + rotation, -centerX, -centerY); + context.setTransform( + goog.vec.Mat4.getElement(localTransform, 0, 0), + goog.vec.Mat4.getElement(localTransform, 1, 0), + goog.vec.Mat4.getElement(localTransform, 0, 1), + goog.vec.Mat4.getElement(localTransform, 1, 1), + goog.vec.Mat4.getElement(localTransform, 0, 3), + goog.vec.Mat4.getElement(localTransform, 1, 3)); + } + context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_, + this.imageWidth_, this.imageHeight_, x, y, + this.imageWidth_, this.imageHeight_); + } + if (rotation !== 0 || this.imageScale_ != 1) { + context.setTransform(1, 0, 0, 1, 0, 0); + } + if (this.imageOpacity_ != 1) { + context.globalAlpha = alpha; + } }; /** - * @return {ol.style.Fill} Fill style. - * @api + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private */ -ol.style.Style.prototype.getFill = function() { - return this.fill_; +ol.render.canvas.Immediate.prototype.drawText_ = + function(flatCoordinates, offset, end, stride) { + if (goog.isNull(this.textState_) || this.text_ === '') { + return; + } + if (!goog.isNull(this.textFillState_)) { + this.setContextFillState_(this.textFillState_); + } + if (!goog.isNull(this.textStrokeState_)) { + this.setContextStrokeState_(this.textStrokeState_); + } + this.setContextTextState_(this.textState_); + goog.asserts.assert(offset === 0, 'offset should be 0'); + goog.asserts.assert(end == flatCoordinates.length, + 'end should be equal to the length of flatCoordinates'); + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, stride, this.transform_, + this.pixelCoordinates_); + var context = this.context_; + for (; offset < end; offset += stride) { + var x = pixelCoordinates[offset] + this.textOffsetX_; + var y = pixelCoordinates[offset + 1] + this.textOffsetY_; + if (this.textRotation_ !== 0 || this.textScale_ != 1) { + var localTransform = ol.vec.Mat4.makeTransform2D(this.tmpLocalTransform_, + x, y, this.textScale_, this.textScale_, this.textRotation_, -x, -y); + context.setTransform( + goog.vec.Mat4.getElement(localTransform, 0, 0), + goog.vec.Mat4.getElement(localTransform, 1, 0), + goog.vec.Mat4.getElement(localTransform, 0, 1), + goog.vec.Mat4.getElement(localTransform, 1, 1), + goog.vec.Mat4.getElement(localTransform, 0, 3), + goog.vec.Mat4.getElement(localTransform, 1, 3)); + } + if (!goog.isNull(this.textStrokeState_)) { + context.strokeText(this.text_, x, y); + } + if (!goog.isNull(this.textFillState_)) { + context.fillText(this.text_, x, y); + } + } + if (this.textRotation_ !== 0 || this.textScale_ != 1) { + context.setTransform(1, 0, 0, 1, 0, 0); + } }; /** - * @return {ol.style.Image} Image style. - * @api + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {boolean} close Close. + * @private + * @return {number} end End. */ -ol.style.Style.prototype.getImage = function() { - return this.image_; +ol.render.canvas.Immediate.prototype.moveToLineTo_ = + function(flatCoordinates, offset, end, stride, close) { + var context = this.context_; + var pixelCoordinates = ol.geom.flat.transform.transform2D( + flatCoordinates, offset, end, stride, this.transform_, + this.pixelCoordinates_); + context.moveTo(pixelCoordinates[0], pixelCoordinates[1]); + var i; + for (i = 2; i < pixelCoordinates.length; i += 2) { + context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]); + } + if (close) { + context.lineTo(pixelCoordinates[0], pixelCoordinates[1]); + } + return end; }; /** - * @return {ol.style.Stroke} Stroke style. - * @api + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @private + * @return {number} End. */ -ol.style.Style.prototype.getStroke = function() { - return this.stroke_; +ol.render.canvas.Immediate.prototype.drawRings_ = + function(flatCoordinates, offset, ends, stride) { + var context = this.context_; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.moveToLineTo_( + flatCoordinates, offset, ends[i], stride, true); + context.closePath(); // FIXME is this needed here? + } + return offset; }; /** - * @return {ol.style.Text} Text style. + * Register a function to be called for rendering at a given zIndex. The + * function will be called asynchronously. The callback will receive a + * reference to {@link ol.render.canvas.Immediate} context for drawing. + * + * @param {number} zIndex Z index. + * @param {function(ol.render.canvas.Immediate)} callback Callback. * @api */ -ol.style.Style.prototype.getText = function() { - return this.text_; +ol.render.canvas.Immediate.prototype.drawAsync = function(zIndex, callback) { + var zIndexKey = zIndex.toString(); + var callbacks = this.callbacksByZIndex_[zIndexKey]; + if (goog.isDef(callbacks)) { + callbacks.push(callback); + } else { + this.callbacksByZIndex_[zIndexKey] = [callback]; + } }; /** - * @return {number|undefined} ZIndex. + * Render a circle geometry into the canvas. Rendering is immediate and uses + * the current fill and stroke styles. + * + * @param {ol.geom.Circle} circleGeometry Circle geometry. + * @param {ol.Feature} feature Feature, * @api */ -ol.style.Style.prototype.getZIndex = function() { - return this.zIndex_; +ol.render.canvas.Immediate.prototype.drawCircleGeometry = + function(circleGeometry, feature) { + if (!ol.extent.intersects(this.extent_, circleGeometry.getExtent())) { + return; + } + if (!goog.isNull(this.fillState_) || !goog.isNull(this.strokeState_)) { + if (!goog.isNull(this.fillState_)) { + this.setContextFillState_(this.fillState_); + } + if (!goog.isNull(this.strokeState_)) { + this.setContextStrokeState_(this.strokeState_); + } + var pixelCoordinates = ol.geom.transformSimpleGeometry2D( + circleGeometry, this.transform_, this.pixelCoordinates_); + var dx = pixelCoordinates[2] - pixelCoordinates[0]; + var dy = pixelCoordinates[3] - pixelCoordinates[1]; + var radius = Math.sqrt(dx * dx + dy * dy); + var context = this.context_; + context.beginPath(); + context.arc( + pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI); + if (!goog.isNull(this.fillState_)) { + context.fill(); + } + if (!goog.isNull(this.strokeState_)) { + context.stroke(); + } + } + if (this.text_ !== '') { + this.drawText_(circleGeometry.getCenter(), 0, 2, 2); + } }; /** - * Set a geometry that is rendered instead of the feature's geometry. + * Render a feature into the canvas. In order to respect the zIndex of the + * style this method draws asynchronously and thus *after* calls to + * drawXxxxGeometry have been finished, effectively drawing the feature + * *on top* of everything else. You probably should be using an + * {@link ol.layer.Vector} instead of calling this method directly. * - * @param {string|ol.geom.Geometry|ol.style.GeometryFunction} geometry - * Feature property or geometry or function returning a geometry to render - * for this style. + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. * @api */ -ol.style.Style.prototype.setGeometry = function(geometry) { - if (goog.isFunction(geometry)) { - this.geometryFunction_ = geometry; - } else if (goog.isString(geometry)) { - this.geometryFunction_ = function(feature) { - var result = feature.get(geometry); - if (goog.isDefAndNotNull(result)) { - goog.asserts.assertInstanceof(result, ol.geom.Geometry); - } - return result; - }; - } else if (goog.isNull(geometry)) { - this.geometryFunction_ = ol.style.defaultGeometryFunction; - } else if (goog.isDef(geometry)) { - goog.asserts.assertInstanceof(geometry, ol.geom.Geometry); - this.geometryFunction_ = function() { - return geometry; - }; +ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!goog.isDefAndNotNull(geometry) || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; } - this.geometry_ = geometry; + var zIndex = style.getZIndex(); + if (!goog.isDef(zIndex)) { + zIndex = 0; + } + this.drawAsync(zIndex, function(render) { + render.setFillStrokeStyle(style.getFill(), style.getStroke()); + render.setImageStyle(style.getImage()); + render.setTextStyle(style.getText()); + var renderGeometry = + ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()]; + goog.asserts.assert(goog.isDef(renderGeometry), + 'renderGeometry should be defined'); + renderGeometry.call(render, geometry, null); + }); }; /** - * Set the zIndex. + * Render a GeometryCollection to the canvas. Rendering is immediate and + * uses the current styles appropriate for each geometry in the collection. * - * @param {number|undefined} zIndex ZIndex. - * @api + * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry + * collection. + * @param {ol.Feature} feature Feature. */ -ol.style.Style.prototype.setZIndex = function(zIndex) { - this.zIndex_ = zIndex; +ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry = + function(geometryCollectionGeometry, feature) { + var geometries = geometryCollectionGeometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometry = geometries[i]; + var geometryRenderer = + ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()]; + goog.asserts.assert(goog.isDef(geometryRenderer), + 'geometryRenderer should be defined'); + geometryRenderer.call(this, geometry, feature); + } }; /** - * A function that takes an {@link ol.Feature} and a `{number}` representing - * the view's resolution. The function should return an array of - * {@link ol.style.Style}. This way e.g. a vector layer can be styled. + * Render a Point geometry into the canvas. Rendering is immediate and uses + * the current style. * - * @typedef {function(ol.Feature, number): Array.<ol.style.Style>} + * @param {ol.geom.Point} pointGeometry Point geometry. + * @param {ol.Feature} feature Feature. * @api */ -ol.style.StyleFunction; - - -/** - * Convert the provided object into a style function. Functions passed through - * unchanged. Arrays of ol.style.Style or single style objects wrapped in a - * new style function. - * @param {ol.style.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj - * A style function, a single style, or an array of styles. - * @return {ol.style.StyleFunction} A style function. - */ -ol.style.createStyleFunction = function(obj) { - /** - * @type {ol.style.StyleFunction} - */ - var styleFunction; - - if (goog.isFunction(obj)) { - styleFunction = /** @type {ol.style.StyleFunction} */ (obj); - } else { - /** - * @type {Array.<ol.style.Style>} - */ - var styles; - if (goog.isArray(obj)) { - styles = obj; - } else { - goog.asserts.assertInstanceof(obj, ol.style.Style); - styles = [obj]; - } - styleFunction = goog.functions.constant(styles); +ol.render.canvas.Immediate.prototype.drawPointGeometry = + function(pointGeometry, feature) { + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + if (!goog.isNull(this.image_)) { + this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); + } + if (this.text_ !== '') { + this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); } - return styleFunction; }; /** + * Render a MultiPoint geometry into the canvas. Rendering is immediate and + * uses the current style. + * + * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry. * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @return {Array.<ol.style.Style>} Style. - */ -ol.style.defaultStyleFunction = function(feature, resolution) { - var fill = new ol.style.Fill({ - color: 'rgba(255,255,255,0.4)' - }); - var stroke = new ol.style.Stroke({ - color: '#3399CC', - width: 1.25 - }); - var styles = [ - new ol.style.Style({ - image: new ol.style.Circle({ - fill: fill, - stroke: stroke, - radius: 5 - }), - fill: fill, - stroke: stroke - }) - ]; - - // Now that we've run it the first time, replace the function with - // a constant version. We don't use an immediately-invoked function - // and a closure so we don't get an error at script evaluation time in - // browsers that do not support Canvas. (ol.style.Circle does - // canvas.getContext('2d') at construction time, which will cause an.error - // in such browsers.) - - /** - * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @return {Array.<ol.style.Style>} Style. - */ - ol.style.defaultStyleFunction = function(feature, resolution) { - return styles; - }; - - return styles; -}; - - -/** - * Default styles for editing features. - * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles + * @api */ -ol.style.createDefaultEditingStyles = function() { - /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */ - var styles = {}; - var white = [255, 255, 255, 1]; - var blue = [0, 153, 255, 1]; - var width = 3; - styles[ol.geom.GeometryType.POLYGON] = [ - new ol.style.Style({ - fill: new ol.style.Fill({ - color: [255, 255, 255, 0.5] - }) - }) - ]; - styles[ol.geom.GeometryType.MULTI_POLYGON] = - styles[ol.geom.GeometryType.POLYGON]; - - styles[ol.geom.GeometryType.LINE_STRING] = [ - new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: white, - width: width + 2 - }) - }), - new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: blue, - width: width - }) - }) - ]; - styles[ol.geom.GeometryType.MULTI_LINE_STRING] = - styles[ol.geom.GeometryType.LINE_STRING]; - - styles[ol.geom.GeometryType.POINT] = [ - new ol.style.Style({ - image: new ol.style.Circle({ - radius: width * 2, - fill: new ol.style.Fill({ - color: blue - }), - stroke: new ol.style.Stroke({ - color: white, - width: width / 2 - }) - }), - zIndex: Infinity - }) - ]; - styles[ol.geom.GeometryType.MULTI_POINT] = - styles[ol.geom.GeometryType.POINT]; - - styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] = - styles[ol.geom.GeometryType.POLYGON].concat( - styles[ol.geom.GeometryType.POINT] - ); - - return styles; +ol.render.canvas.Immediate.prototype.drawMultiPointGeometry = + function(multiPointGeometry, feature) { + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + if (!goog.isNull(this.image_)) { + this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); + } + if (this.text_ !== '') { + this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); + } }; /** - * A function that takes an {@link ol.Feature} as argument and returns an - * {@link ol.geom.Geometry} that will be rendered and styled for the feature. + * Render a LineString into the canvas. Rendering is immediate and uses + * the current style. * - * @typedef {function(ol.Feature): (ol.geom.Geometry|undefined)} + * @param {ol.geom.LineString} lineStringGeometry Line string geometry. + * @param {ol.Feature} feature Feature. * @api */ -ol.style.GeometryFunction; - - -/** - * Function that is called with a feature and returns its default geometry. - * @param {ol.Feature} feature Feature to get the geometry for. - * @return {ol.geom.Geometry|undefined} Geometry to render. - */ -ol.style.defaultGeometryFunction = function(feature) { - goog.asserts.assert(!goog.isNull(feature)); - return feature.getGeometry(); +ol.render.canvas.Immediate.prototype.drawLineStringGeometry = + function(lineStringGeometry, feature) { + if (!ol.extent.intersects(this.extent_, lineStringGeometry.getExtent())) { + return; + } + if (!goog.isNull(this.strokeState_)) { + this.setContextStrokeState_(this.strokeState_); + var context = this.context_; + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + context.beginPath(); + this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length, + lineStringGeometry.getStride(), false); + context.stroke(); + } + if (this.text_ !== '') { + var flatMidpoint = lineStringGeometry.getFlatMidpoint(); + this.drawText_(flatMidpoint, 0, 2, 2); + } }; -goog.provide('ol.interaction.DragZoom'); - -goog.require('goog.asserts'); -goog.require('ol'); -goog.require('ol.events.condition'); -goog.require('ol.extent'); -goog.require('ol.interaction.DragBox'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.style.Stroke'); -goog.require('ol.style.Style'); - - /** - * @classdesc - * Allows the user to zoom the map by clicking and dragging on the map, - * normally combined with an {@link ol.events.condition} that limits - * it to when a key, shift by default, is held down. + * Render a MultiLineString geometry into the canvas. Rendering is immediate + * and uses the current style. * - * @constructor - * @extends {ol.interaction.DragBox} - * @param {olx.interaction.DragZoomOptions=} opt_options Options. - * @api stable - */ -ol.interaction.DragZoom = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - - var condition = goog.isDef(options.condition) ? - options.condition : ol.events.condition.shiftKeyOnly; - - /** - * @private - * @type {ol.style.Style} - */ - var style = goog.isDef(options.style) ? - options.style : new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: [0, 0, 255, 1] - }) - }); - - goog.base(this, { - condition: condition, - style: style - }); - -}; -goog.inherits(ol.interaction.DragZoom, ol.interaction.DragBox); - - -/** - * @inheritDoc + * @param {ol.geom.MultiLineString} multiLineStringGeometry + * MultiLineString geometry. + * @param {ol.Feature} feature Feature. + * @api */ -ol.interaction.DragZoom.prototype.onBoxEnd = function() { - var map = this.getMap(); - var view = map.getView(); - goog.asserts.assert(!goog.isNull(view)); - var extent = this.getGeometry().getExtent(); - var center = ol.extent.getCenter(extent); - var size = map.getSize(); - goog.asserts.assert(goog.isDef(size)); - ol.interaction.Interaction.zoom(map, view, - view.getResolutionForExtent(extent, size), - center, ol.DRAGZOOM_ANIMATION_DURATION); -}; - -goog.provide('ol.interaction.KeyboardPan'); - -goog.require('goog.asserts'); -goog.require('goog.events.KeyCodes'); -goog.require('goog.events.KeyHandler.EventType'); -goog.require('goog.functions'); -goog.require('ol'); -goog.require('ol.coordinate'); -goog.require('ol.events.ConditionType'); -goog.require('ol.events.condition'); -goog.require('ol.interaction.Interaction'); - +ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry = + function(multiLineStringGeometry, feature) { + var geometryExtent = multiLineStringGeometry.getExtent(); + if (!ol.extent.intersects(this.extent_, geometryExtent)) { + return; + } + if (!goog.isNull(this.strokeState_)) { + this.setContextStrokeState_(this.strokeState_); + var context = this.context_; + var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); + var offset = 0; + var ends = multiLineStringGeometry.getEnds(); + var stride = multiLineStringGeometry.getStride(); + context.beginPath(); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.moveToLineTo_( + flatCoordinates, offset, ends[i], stride, false); + } + context.stroke(); + } + if (this.text_ !== '') { + var flatMidpoints = multiLineStringGeometry.getFlatMidpoints(); + this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2); + } +}; /** - * @classdesc - * Allows the user to pan the map using keyboard arrows. - * Note that, although this interaction is by default included in maps, - * the keys can only be used when browser focus is on the element to which - * the keyboard events are attached. By default, this is the map div, - * though you can change this with the `keyboardEventTarget` in - * {@link ol.Map}. `document` never loses focus but, for any other element, - * focus will have to be on, and returned to, this element if the keys are to - * function. - * See also {@link ol.interaction.KeyboardZoom}. + * Render a Polygon geometry into the canvas. Rendering is immediate and uses + * the current style. * - * @constructor - * @extends {ol.interaction.Interaction} - * @param {olx.interaction.KeyboardPanOptions=} opt_options Options. - * @api stable + * @param {ol.geom.Polygon} polygonGeometry Polygon geometry. + * @param {ol.Feature} feature Feature. + * @api */ -ol.interaction.KeyboardPan = function(opt_options) { - - goog.base(this, { - handleEvent: ol.interaction.KeyboardPan.handleEvent - }); - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.events.ConditionType} - */ - this.condition_ = goog.isDef(options.condition) ? options.condition : - goog.functions.and(ol.events.condition.noModifierKeys, - ol.events.condition.targetNotEditable); - - /** - * @private - * @type {number} - */ - this.pixelDelta_ = goog.isDef(options.pixelDelta) ? options.pixelDelta : 128; - +ol.render.canvas.Immediate.prototype.drawPolygonGeometry = + function(polygonGeometry, feature) { + if (!ol.extent.intersects(this.extent_, polygonGeometry.getExtent())) { + return; + } + if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) { + if (!goog.isNull(this.fillState_)) { + this.setContextFillState_(this.fillState_); + } + if (!goog.isNull(this.strokeState_)) { + this.setContextStrokeState_(this.strokeState_); + } + var context = this.context_; + context.beginPath(); + this.drawRings_(polygonGeometry.getOrientedFlatCoordinates(), + 0, polygonGeometry.getEnds(), polygonGeometry.getStride()); + if (!goog.isNull(this.fillState_)) { + context.fill(); + } + if (!goog.isNull(this.strokeState_)) { + context.stroke(); + } + } + if (this.text_ !== '') { + var flatInteriorPoint = polygonGeometry.getFlatInteriorPoint(); + this.drawText_(flatInteriorPoint, 0, 2, 2); + } }; -goog.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction); /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.KeyboardPan} + * Render MultiPolygon geometry into the canvas. Rendering is immediate and + * uses the current style. + * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. + * @param {ol.Feature} feature Feature. * @api */ -ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) { - var stopEvent = false; - if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { - var keyEvent = /** @type {goog.events.KeyEvent} */ - (mapBrowserEvent.browserEvent); - var keyCode = keyEvent.keyCode; - if (this.condition_(mapBrowserEvent) && - (keyCode == goog.events.KeyCodes.DOWN || - keyCode == goog.events.KeyCodes.LEFT || - keyCode == goog.events.KeyCodes.RIGHT || - keyCode == goog.events.KeyCodes.UP)) { - var map = mapBrowserEvent.map; - var view = map.getView(); - goog.asserts.assert(!goog.isNull(view)); - var viewState = view.getState(); - var mapUnitsDelta = viewState.resolution * this.pixelDelta_; - var deltaX = 0, deltaY = 0; - if (keyCode == goog.events.KeyCodes.DOWN) { - deltaY = -mapUnitsDelta; - } else if (keyCode == goog.events.KeyCodes.LEFT) { - deltaX = -mapUnitsDelta; - } else if (keyCode == goog.events.KeyCodes.RIGHT) { - deltaX = mapUnitsDelta; - } else { - deltaY = mapUnitsDelta; +ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry = + function(multiPolygonGeometry, feature) { + if (!ol.extent.intersects(this.extent_, multiPolygonGeometry.getExtent())) { + return; + } + if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) { + if (!goog.isNull(this.fillState_)) { + this.setContextFillState_(this.fillState_); + } + if (!goog.isNull(this.strokeState_)) { + this.setContextStrokeState_(this.strokeState_); + } + var context = this.context_; + var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); + var offset = 0; + var endss = multiPolygonGeometry.getEndss(); + var stride = multiPolygonGeometry.getStride(); + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + context.beginPath(); + offset = this.drawRings_(flatCoordinates, offset, ends, stride); + if (!goog.isNull(this.fillState_)) { + context.fill(); + } + if (!goog.isNull(this.strokeState_)) { + context.stroke(); } - var delta = [deltaX, deltaY]; - ol.coordinate.rotate(delta, viewState.rotation); - ol.interaction.Interaction.pan( - map, view, delta, ol.KEYBOARD_PAN_DURATION); - mapBrowserEvent.preventDefault(); - stopEvent = true; } } - return !stopEvent; + if (this.text_ !== '') { + var flatInteriorPoints = multiPolygonGeometry.getFlatInteriorPoints(); + this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2); + } }; -goog.provide('ol.interaction.KeyboardZoom'); - -goog.require('goog.asserts'); -goog.require('goog.events.KeyHandler.EventType'); -goog.require('ol.events.ConditionType'); -goog.require('ol.events.condition'); -goog.require('ol.interaction.Interaction'); - - /** - * @classdesc - * Allows the user to zoom the map using keyboard + and -. - * Note that, although this interaction is by default included in maps, - * the keys can only be used when browser focus is on the element to which - * the keyboard events are attached. By default, this is the map div, - * though you can change this with the `keyboardEventTarget` in - * {@link ol.Map}. `document` never loses focus but, for any other element, - * focus will have to be on, and returned to, this element if the keys are to - * function. - * See also {@link ol.interaction.KeyboardPan}. - * - * @constructor - * @param {olx.interaction.KeyboardZoomOptions=} opt_options Options. - * @extends {ol.interaction.Interaction} - * @api stable + * @inheritDoc */ -ol.interaction.KeyboardZoom = function(opt_options) { - - goog.base(this, { - handleEvent: ol.interaction.KeyboardZoom.handleEvent - }); - - var options = goog.isDef(opt_options) ? opt_options : {}; +ol.render.canvas.Immediate.prototype.drawText = goog.abstractMethod; - /** - * @private - * @type {ol.events.ConditionType} - */ - this.condition_ = goog.isDef(options.condition) ? options.condition : - ol.events.condition.targetNotEditable; - /** - * @private - * @type {number} - */ - this.delta_ = goog.isDef(options.delta) ? options.delta : 1; +/** + * FIXME: empty description for jsdoc + */ +ol.render.canvas.Immediate.prototype.flush = function() { + /** @type {Array.<number>} */ + var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number); + goog.array.sort(zs); + var i, ii, callbacks, j, jj; + for (i = 0, ii = zs.length; i < ii; ++i) { + callbacks = this.callbacksByZIndex_[zs[i].toString()]; + for (j = 0, jj = callbacks.length; j < jj; ++j) { + callbacks[j](this); + } + } +}; - /** - * @private - * @type {number} - */ - this.duration_ = goog.isDef(options.duration) ? options.duration : 100; +/** + * @param {ol.render.canvas.FillState} fillState Fill state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextFillState_ = + function(fillState) { + var context = this.context_; + var contextFillState = this.contextFillState_; + if (goog.isNull(contextFillState)) { + context.fillStyle = fillState.fillStyle; + this.contextFillState_ = { + fillStyle: fillState.fillStyle + }; + } else { + if (contextFillState.fillStyle != fillState.fillStyle) { + contextFillState.fillStyle = context.fillStyle = fillState.fillStyle; + } + } }; -goog.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction); /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.KeyboardZoom} - * @api + * @param {ol.render.canvas.StrokeState} strokeState Stroke state. + * @private */ -ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) { - var stopEvent = false; - if (mapBrowserEvent.type == goog.events.KeyHandler.EventType.KEY) { - var keyEvent = /** @type {goog.events.KeyEvent} */ - (mapBrowserEvent.browserEvent); - var charCode = keyEvent.charCode; - if (this.condition_(mapBrowserEvent) && - (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) { - var map = mapBrowserEvent.map; - var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_; - map.render(); - var view = map.getView(); - goog.asserts.assert(!goog.isNull(view)); - ol.interaction.Interaction.zoomByDelta( - map, view, delta, undefined, this.duration_); - mapBrowserEvent.preventDefault(); - stopEvent = true; +ol.render.canvas.Immediate.prototype.setContextStrokeState_ = + function(strokeState) { + var context = this.context_; + var contextStrokeState = this.contextStrokeState_; + if (goog.isNull(contextStrokeState)) { + context.lineCap = strokeState.lineCap; + if (ol.has.CANVAS_LINE_DASH) { + context.setLineDash(strokeState.lineDash); + } + context.lineJoin = strokeState.lineJoin; + context.lineWidth = strokeState.lineWidth; + context.miterLimit = strokeState.miterLimit; + context.strokeStyle = strokeState.strokeStyle; + this.contextStrokeState_ = { + lineCap: strokeState.lineCap, + lineDash: strokeState.lineDash, + lineJoin: strokeState.lineJoin, + lineWidth: strokeState.lineWidth, + miterLimit: strokeState.miterLimit, + strokeStyle: strokeState.strokeStyle + }; + } else { + if (contextStrokeState.lineCap != strokeState.lineCap) { + contextStrokeState.lineCap = context.lineCap = strokeState.lineCap; + } + if (ol.has.CANVAS_LINE_DASH) { + if (!goog.array.equals( + contextStrokeState.lineDash, strokeState.lineDash)) { + context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash); + } + } + if (contextStrokeState.lineJoin != strokeState.lineJoin) { + contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin; + } + if (contextStrokeState.lineWidth != strokeState.lineWidth) { + contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth; + } + if (contextStrokeState.miterLimit != strokeState.miterLimit) { + contextStrokeState.miterLimit = context.miterLimit = + strokeState.miterLimit; + } + if (contextStrokeState.strokeStyle != strokeState.strokeStyle) { + contextStrokeState.strokeStyle = context.strokeStyle = + strokeState.strokeStyle; } } - return !stopEvent; }; -goog.provide('ol.interaction.MouseWheelZoom'); - -goog.require('goog.asserts'); -goog.require('goog.events.MouseWheelEvent'); -goog.require('goog.events.MouseWheelHandler.EventType'); -goog.require('goog.math'); -goog.require('ol'); -goog.require('ol.Coordinate'); -goog.require('ol.interaction.Interaction'); +/** + * @param {ol.render.canvas.TextState} textState Text state. + * @private + */ +ol.render.canvas.Immediate.prototype.setContextTextState_ = + function(textState) { + var context = this.context_; + var contextTextState = this.contextTextState_; + if (goog.isNull(contextTextState)) { + context.font = textState.font; + context.textAlign = textState.textAlign; + context.textBaseline = textState.textBaseline; + this.contextTextState_ = { + font: textState.font, + textAlign: textState.textAlign, + textBaseline: textState.textBaseline + }; + } else { + if (contextTextState.font != textState.font) { + contextTextState.font = context.font = textState.font; + } + if (contextTextState.textAlign != textState.textAlign) { + contextTextState.textAlign = context.textAlign = textState.textAlign; + } + if (contextTextState.textBaseline != textState.textBaseline) { + contextTextState.textBaseline = context.textBaseline = + textState.textBaseline; + } + } +}; /** - * @classdesc - * Allows the user to zoom the map by scrolling the mouse wheel. + * Set the fill and stroke style for subsequent draw operations. To clear + * either fill or stroke styles, pass null for the appropriate parameter. * - * @constructor - * @extends {ol.interaction.Interaction} - * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options. - * @api stable + * @param {ol.style.Fill} fillStyle Fill style. + * @param {ol.style.Stroke} strokeStyle Stroke style. + * @api */ -ol.interaction.MouseWheelZoom = function(opt_options) { - - goog.base(this, { - handleEvent: ol.interaction.MouseWheelZoom.handleEvent - }); - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {number} - */ - this.delta_ = 0; - - /** - * @private - * @type {number} - */ - this.duration_ = goog.isDef(options.duration) ? options.duration : 250; - - /** - * @private - * @type {?ol.Coordinate} - */ - this.lastAnchor_ = null; - - /** - * @private - * @type {number|undefined} - */ - this.startTime_ = undefined; - - /** - * @private - * @type {number|undefined} - */ - this.timeoutId_ = undefined; - +ol.render.canvas.Immediate.prototype.setFillStrokeStyle = + function(fillStyle, strokeStyle) { + if (goog.isNull(fillStyle)) { + this.fillState_ = null; + } else { + var fillStyleColor = fillStyle.getColor(); + this.fillState_ = { + fillStyle: ol.color.asString(!goog.isNull(fillStyleColor) ? + fillStyleColor : ol.render.canvas.defaultFillStyle) + }; + } + if (goog.isNull(strokeStyle)) { + this.strokeState_ = null; + } else { + var strokeStyleColor = strokeStyle.getColor(); + var strokeStyleLineCap = strokeStyle.getLineCap(); + var strokeStyleLineDash = strokeStyle.getLineDash(); + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + var strokeStyleWidth = strokeStyle.getWidth(); + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.strokeState_ = { + lineCap: goog.isDef(strokeStyleLineCap) ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap, + lineDash: goog.isDefAndNotNull(strokeStyleLineDash) ? + strokeStyleLineDash : ol.render.canvas.defaultLineDash, + lineJoin: goog.isDef(strokeStyleLineJoin) ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin, + lineWidth: this.pixelRatio_ * (goog.isDef(strokeStyleWidth) ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth), + miterLimit: goog.isDef(strokeStyleMiterLimit) ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, + strokeStyle: ol.color.asString(!goog.isNull(strokeStyleColor) ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle) + }; + } }; -goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction); /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.MouseWheelZoom} + * Set the image style for subsequent draw operations. Pass null to remove + * the image style. + * + * @param {ol.style.Image} imageStyle Image style. * @api */ -ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) { - var stopEvent = false; - if (mapBrowserEvent.type == - goog.events.MouseWheelHandler.EventType.MOUSEWHEEL) { - var map = mapBrowserEvent.map; - var mouseWheelEvent = mapBrowserEvent.browserEvent; - goog.asserts.assertInstanceof(mouseWheelEvent, goog.events.MouseWheelEvent); +ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) { + if (goog.isNull(imageStyle)) { + this.image_ = null; + } else { + var imageAnchor = imageStyle.getAnchor(); + // FIXME pixel ratio + var imageImage = imageStyle.getImage(1); + var imageOrigin = imageStyle.getOrigin(); + var imageSize = imageStyle.getSize(); + goog.asserts.assert(!goog.isNull(imageAnchor), + 'imageAnchor should not be null'); + goog.asserts.assert(!goog.isNull(imageImage), + 'imageImage should not be null'); + goog.asserts.assert(!goog.isNull(imageOrigin), + 'imageOrigin should not be null'); + goog.asserts.assert(!goog.isNull(imageSize), + 'imageSize should not be null'); + this.imageAnchorX_ = imageAnchor[0]; + this.imageAnchorY_ = imageAnchor[1]; + this.imageHeight_ = imageSize[1]; + this.image_ = imageImage; + this.imageOpacity_ = imageStyle.getOpacity(); + this.imageOriginX_ = imageOrigin[0]; + this.imageOriginY_ = imageOrigin[1]; + this.imageRotateWithView_ = imageStyle.getRotateWithView(); + this.imageRotation_ = imageStyle.getRotation(); + this.imageScale_ = imageStyle.getScale(); + this.imageSnapToPixel_ = imageStyle.getSnapToPixel(); + this.imageWidth_ = imageSize[0]; + } +}; - this.lastAnchor_ = mapBrowserEvent.coordinate; - this.delta_ += mouseWheelEvent.deltaY; - if (!goog.isDef(this.startTime_)) { - this.startTime_ = goog.now(); +/** + * Set the text style for subsequent draw operations. Pass null to + * remove the text style. + * + * @param {ol.style.Text} textStyle Text style. + * @api + */ +ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) { + if (goog.isNull(textStyle)) { + this.text_ = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (goog.isNull(textFillStyle)) { + this.textFillState_ = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + this.textFillState_ = { + fillStyle: ol.color.asString(!goog.isNull(textFillStyleColor) ? + textFillStyleColor : ol.render.canvas.defaultFillStyle) + }; } - - var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION; - var timeLeft = Math.max(duration - (goog.now() - this.startTime_), 0); - - goog.global.clearTimeout(this.timeoutId_); - this.timeoutId_ = goog.global.setTimeout( - goog.bind(this.doZoom_, this, map), timeLeft); - - mapBrowserEvent.preventDefault(); - stopEvent = true; + var textStrokeStyle = textStyle.getStroke(); + if (goog.isNull(textStrokeStyle)) { + this.textStrokeState_ = null; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); + var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); + var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); + var textStrokeStyleWidth = textStrokeStyle.getWidth(); + var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); + this.textStrokeState_ = { + lineCap: goog.isDef(textStrokeStyleLineCap) ? + textStrokeStyleLineCap : ol.render.canvas.defaultLineCap, + lineDash: goog.isDefAndNotNull(textStrokeStyleLineDash) ? + textStrokeStyleLineDash : ol.render.canvas.defaultLineDash, + lineJoin: goog.isDef(textStrokeStyleLineJoin) ? + textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin, + lineWidth: goog.isDef(textStrokeStyleWidth) ? + textStrokeStyleWidth : ol.render.canvas.defaultLineWidth, + miterLimit: goog.isDef(textStrokeStyleMiterLimit) ? + textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, + strokeStyle: ol.color.asString(!goog.isNull(textStrokeStyleColor) ? + textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle) + }; + } + var textFont = textStyle.getFont(); + var textOffsetX = textStyle.getOffsetX(); + var textOffsetY = textStyle.getOffsetY(); + var textRotation = textStyle.getRotation(); + var textScale = textStyle.getScale(); + var textText = textStyle.getText(); + var textTextAlign = textStyle.getTextAlign(); + var textTextBaseline = textStyle.getTextBaseline(); + this.textState_ = { + font: goog.isDef(textFont) ? + textFont : ol.render.canvas.defaultFont, + textAlign: goog.isDef(textTextAlign) ? + textTextAlign : ol.render.canvas.defaultTextAlign, + textBaseline: goog.isDef(textTextBaseline) ? + textTextBaseline : ol.render.canvas.defaultTextBaseline + }; + this.text_ = goog.isDef(textText) ? textText : ''; + this.textOffsetX_ = + goog.isDef(textOffsetX) ? (this.pixelRatio_ * textOffsetX) : 0; + this.textOffsetY_ = + goog.isDef(textOffsetY) ? (this.pixelRatio_ * textOffsetY) : 0; + this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0; + this.textScale_ = this.pixelRatio_ * (goog.isDef(textScale) ? + textScale : 1); } - return !stopEvent; }; /** + * @const * @private - * @param {ol.Map} map Map. + * @type {Object.<ol.geom.GeometryType, + * function(this: ol.render.canvas.Immediate, ol.geom.Geometry, + * Object)>} */ -ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) { - var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA; - var delta = goog.math.clamp(this.delta_, -maxDelta, maxDelta); - - var view = map.getView(); - goog.asserts.assert(!goog.isNull(view)); - - map.render(); - ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_, - this.duration_); - - this.delta_ = 0; - this.lastAnchor_ = null; - this.startTime_ = undefined; - this.timeoutId_ = undefined; +ol.render.canvas.Immediate.GEOMETRY_RENDERERS_ = { + 'Point': ol.render.canvas.Immediate.prototype.drawPointGeometry, + 'LineString': ol.render.canvas.Immediate.prototype.drawLineStringGeometry, + 'Polygon': ol.render.canvas.Immediate.prototype.drawPolygonGeometry, + 'MultiPoint': ol.render.canvas.Immediate.prototype.drawMultiPointGeometry, + 'MultiLineString': + ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry, + 'MultiPolygon': ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry, + 'GeometryCollection': + ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry, + 'Circle': ol.render.canvas.Immediate.prototype.drawCircleGeometry }; -goog.provide('ol.interaction.PinchRotate'); +goog.provide('ol.renderer.canvas.Layer'); +goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.style'); -goog.require('ol'); -goog.require('ol.Coordinate'); -goog.require('ol.ViewHint'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.interaction.Pointer'); +goog.require('goog.vec.Mat4'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Layer'); +goog.require('ol.render.Event'); +goog.require('ol.render.EventType'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Layer'); +goog.require('ol.vec.Mat4'); /** - * @classdesc - * Allows the user to rotate the map by twisting with two fingers - * on a touch screen. - * * @constructor - * @extends {ol.interaction.Pointer} - * @param {olx.interaction.PinchRotateOptions=} opt_options Options. - * @api stable + * @extends {ol.renderer.Layer} + * @param {ol.layer.Layer} layer Layer. */ -ol.interaction.PinchRotate = function(opt_options) { - - goog.base(this, { - handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_, - handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_, - handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_ - }); - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.Coordinate} - */ - this.anchor_ = null; - - /** - * @private - * @type {number|undefined} - */ - this.lastAngle_ = undefined; - - /** - * @private - * @type {boolean} - */ - this.rotating_ = false; +ol.renderer.canvas.Layer = function(layer) { - /** - * @private - * @type {number} - */ - this.rotationDelta_ = 0.0; + goog.base(this, layer); /** * @private - * @type {number} + * @type {!goog.vec.Mat4.Number} */ - this.threshold_ = goog.isDef(options.threshold) ? options.threshold : 0.3; + this.transform_ = goog.vec.Mat4.createNumber(); }; -goog.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer); +goog.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer); /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.PinchRotate} - * @private + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @param {CanvasRenderingContext2D} context Context. */ -ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) { - goog.asserts.assert(this.targetPointers.length >= 2); - var rotationDelta = 0.0; +ol.renderer.canvas.Layer.prototype.composeFrame = + function(frameState, layerState, context) { - var touch0 = this.targetPointers[0]; - var touch1 = this.targetPointers[1]; + this.dispatchPreComposeEvent(context, frameState); - // angle between touches - var angle = Math.atan2( - touch1.clientY - touch0.clientY, - touch1.clientX - touch0.clientX); + var image = this.getImage(); + if (!goog.isNull(image)) { - if (goog.isDef(this.lastAngle_)) { - var delta = angle - this.lastAngle_; - this.rotationDelta_ += delta; - if (!this.rotating_ && - Math.abs(this.rotationDelta_) > this.threshold_) { - this.rotating_ = true; + // clipped rendering if layer extent is set + var extent = layerState.extent; + var clipped = goog.isDef(extent); + if (clipped) { + goog.asserts.assert(goog.isDef(extent), + 'layerState extent is defined'); + var pixelRatio = frameState.pixelRatio; + var topLeft = ol.extent.getTopLeft(extent); + var topRight = ol.extent.getTopRight(extent); + var bottomRight = ol.extent.getBottomRight(extent); + var bottomLeft = ol.extent.getBottomLeft(extent); + + ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, + topLeft, topLeft); + ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, + topRight, topRight); + ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, + bottomRight, bottomRight); + ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, + bottomLeft, bottomLeft); + + context.save(); + context.beginPath(); + context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio); + context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio); + context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio); + context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio); + context.clip(); } - rotationDelta = delta; - } - this.lastAngle_ = angle; - var map = mapBrowserEvent.map; + var imageTransform = this.getImageTransform(); + // for performance reasons, context.save / context.restore is not used + // to save and restore the transformation matrix and the opacity. + // see http://jsperf.com/context-save-restore-versus-variable + var alpha = context.globalAlpha; + context.globalAlpha = layerState.opacity; - // rotate anchor point. - // FIXME: should be the intersection point between the lines: - // touch0,touch1 and previousTouch0,previousTouch1 - var viewportPosition = goog.style.getClientPosition(map.getViewport()); - var centroid = - ol.interaction.Pointer.centroid(this.targetPointers); - centroid[0] -= viewportPosition.x; - centroid[1] -= viewportPosition.y; - this.anchor_ = map.getCoordinateFromPixel(centroid); + // for performance reasons, context.setTransform is only used + // when the view is rotated. see http://jsperf.com/canvas-transform + if (frameState.viewState.rotation === 0) { + var dx = goog.vec.Mat4.getElement(imageTransform, 0, 3); + var dy = goog.vec.Mat4.getElement(imageTransform, 1, 3); + var dw = image.width * goog.vec.Mat4.getElement(imageTransform, 0, 0); + var dh = image.height * goog.vec.Mat4.getElement(imageTransform, 1, 1); + context.drawImage(image, 0, 0, +image.width, +image.height, + Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); + } else { + context.setTransform( + goog.vec.Mat4.getElement(imageTransform, 0, 0), + goog.vec.Mat4.getElement(imageTransform, 1, 0), + goog.vec.Mat4.getElement(imageTransform, 0, 1), + goog.vec.Mat4.getElement(imageTransform, 1, 1), + goog.vec.Mat4.getElement(imageTransform, 0, 3), + goog.vec.Mat4.getElement(imageTransform, 1, 3)); + context.drawImage(image, 0, 0); + context.setTransform(1, 0, 0, 1, 0, 0); + } + context.globalAlpha = alpha; - // rotate - if (this.rotating_) { - var view = map.getView(); - var viewState = view.getState(); - map.render(); - ol.interaction.Interaction.rotateWithoutConstraints(map, view, - viewState.rotation + rotationDelta, this.anchor_); + if (clipped) { + context.restore(); + } } + + this.dispatchPostComposeEvent(context, frameState); + }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.PinchRotate} + * @param {ol.render.EventType} type Event type. + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {goog.vec.Mat4.Number=} opt_transform Transform. * @private */ -ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) { - if (this.targetPointers.length < 2) { - var map = mapBrowserEvent.map; - var view = map.getView(); - view.setHint(ol.ViewHint.INTERACTING, -1); - if (this.rotating_) { - var viewState = view.getState(); - ol.interaction.Interaction.rotate( - map, view, viewState.rotation, this.anchor_, - ol.ROTATE_ANIMATION_DURATION); - } - return false; - } else { - return true; +ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = + function(type, context, frameState, opt_transform) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var transform = goog.isDef(opt_transform) ? + opt_transform : this.getTransform(frameState, 0); + var render = new ol.render.canvas.Immediate( + context, frameState.pixelRatio, frameState.extent, transform, + frameState.viewState.rotation); + var composeEvent = new ol.render.Event(type, layer, render, frameState, + context, null); + layer.dispatchEvent(composeEvent); + render.flush(); } }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.PinchRotate} - * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {goog.vec.Mat4.Number=} opt_transform Transform. + * @protected */ -ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) { - if (this.targetPointers.length >= 2) { - var map = mapBrowserEvent.map; - this.anchor_ = null; - this.lastAngle_ = undefined; - this.rotating_ = false; - this.rotationDelta_ = 0.0; - if (!this.handlingDownUpSequence) { - map.getView().setHint(ol.ViewHint.INTERACTING, 1); - } - map.render(); - return true; - } else { - return false; - } +ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent = + function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context, + frameState, opt_transform); }; /** - * @inheritDoc + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {goog.vec.Mat4.Number=} opt_transform Transform. + * @protected */ -ol.interaction.PinchRotate.prototype.shouldStopEvent = goog.functions.FALSE; - -goog.provide('ol.interaction.PinchZoom'); - -goog.require('goog.asserts'); -goog.require('goog.style'); -goog.require('ol.Coordinate'); -goog.require('ol.ViewHint'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.interaction.Pointer'); - +ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent = + function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context, + frameState, opt_transform); +}; /** - * @classdesc - * Allows the user to zoom the map by pinching with two fingers - * on a touch screen. - * - * @constructor - * @extends {ol.interaction.Pointer} - * @param {olx.interaction.PinchZoomOptions=} opt_options Options. - * @api stable + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {goog.vec.Mat4.Number=} opt_transform Transform. + * @protected */ -ol.interaction.PinchZoom = function(opt_options) { - - goog.base(this, { - handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_, - handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_, - handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_ - }); - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.Coordinate} - */ - this.anchor_ = null; - - /** - * @private - * @type {number} - */ - this.duration_ = goog.isDef(options.duration) ? options.duration : 400; - - /** - * @private - * @type {number|undefined} - */ - this.lastDistance_ = undefined; - - /** - * @private - * @type {number} - */ - this.lastScaleDelta_ = 1; - +ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = + function(context, frameState, opt_transform) { + this.dispatchComposeEvent_(ol.render.EventType.RENDER, context, + frameState, opt_transform); }; -goog.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer); /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.PinchZoom} - * @private + * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas. */ -ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) { - goog.asserts.assert(this.targetPointers.length >= 2); - var scaleDelta = 1.0; - - var touch0 = this.targetPointers[0]; - var touch1 = this.targetPointers[1]; - var dx = touch0.clientX - touch1.clientX; - var dy = touch0.clientY - touch1.clientY; - - // distance between touches - var distance = Math.sqrt(dx * dx + dy * dy); - - if (goog.isDef(this.lastDistance_)) { - scaleDelta = this.lastDistance_ / distance; - } - this.lastDistance_ = distance; - if (scaleDelta != 1.0) { - this.lastScaleDelta_ = scaleDelta; - } +ol.renderer.canvas.Layer.prototype.getImage = goog.abstractMethod; - var map = mapBrowserEvent.map; - var view = map.getView(); - var viewState = view.getState(); - // scale anchor point. - var viewportPosition = goog.style.getClientPosition(map.getViewport()); - var centroid = - ol.interaction.Pointer.centroid(this.targetPointers); - centroid[0] -= viewportPosition.x; - centroid[1] -= viewportPosition.y; - this.anchor_ = map.getCoordinateFromPixel(centroid); +/** + * @return {!goog.vec.Mat4.Number} Image transform. + */ +ol.renderer.canvas.Layer.prototype.getImageTransform = goog.abstractMethod; - // scale, bypass the resolution constraint - map.render(); - ol.interaction.Interaction.zoomWithoutConstraints( - map, view, viewState.resolution * scaleDelta, this.anchor_); +/** + * @param {olx.FrameState} frameState Frame state. + * @param {number} offsetX Offset on the x-axis in view coordinates. + * @protected + * @return {!goog.vec.Mat4.Number} Transform. + */ +ol.renderer.canvas.Layer.prototype.getTransform = + function(frameState, offsetX) { + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + return ol.vec.Mat4.makeTransform2D(this.transform_, + pixelRatio * frameState.size[0] / 2, + pixelRatio * frameState.size[1] / 2, + pixelRatio / viewState.resolution, + -pixelRatio / viewState.resolution, + -viewState.rotation, + -viewState.center[0] + offsetX, + -viewState.center[1]); }; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.PinchZoom} - * @private + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @return {boolean} whether composeFrame should be called. */ -ol.interaction.PinchZoom.handleUpEvent_ = - function(mapBrowserEvent) { - if (this.targetPointers.length < 2) { - var map = mapBrowserEvent.map; - var view = map.getView(); - view.setHint(ol.ViewHint.INTERACTING, -1); - var viewState = view.getState(); - // Zoom to final resolution, with an animation, and provide a - // direction not to zoom out/in if user was pinching in/out. - // Direction is > 0 if pinching out, and < 0 if pinching in. - var direction = this.lastScaleDelta_ - 1; - ol.interaction.Interaction.zoom(map, view, viewState.resolution, - this.anchor_, this.duration_, direction); - return false; - } else { - return true; - } -}; +ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod; /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.PinchZoom} - * @private + * @param {ol.Pixel} pixelOnMap Pixel. + * @param {goog.vec.Mat4.Number} imageTransformInv The transformation matrix + * to convert from a map pixel to a canvas pixel. + * @return {ol.Pixel} + * @protected */ -ol.interaction.PinchZoom.handleDownEvent_ = - function(mapBrowserEvent) { - if (this.targetPointers.length >= 2) { - var map = mapBrowserEvent.map; - this.anchor_ = null; - this.lastDistance_ = undefined; - this.lastScaleDelta_ = 1; - if (!this.handlingDownUpSequence) { - map.getView().setHint(ol.ViewHint.INTERACTING, 1); - } - map.render(); - return true; - } else { - return false; - } +ol.renderer.canvas.Layer.prototype.getPixelOnCanvas = + function(pixelOnMap, imageTransformInv) { + var pixelOnCanvas = [0, 0]; + ol.vec.Mat4.multVec2(imageTransformInv, pixelOnMap, pixelOnCanvas); + return pixelOnCanvas; }; /** - * @inheritDoc + * @param {ol.Size} size Size. + * @return {boolean} True when the canvas with the current size does not exceed + * the maximum dimensions. */ -ol.interaction.PinchZoom.prototype.shouldStopEvent = goog.functions.FALSE; - -goog.provide('ol.interaction'); +ol.renderer.canvas.Layer.testCanvasSize = (function() { -goog.require('ol.Collection'); -goog.require('ol.Kinetic'); -goog.require('ol.interaction.DoubleClickZoom'); -goog.require('ol.interaction.DragPan'); -goog.require('ol.interaction.DragRotate'); -goog.require('ol.interaction.DragZoom'); -goog.require('ol.interaction.KeyboardPan'); -goog.require('ol.interaction.KeyboardZoom'); -goog.require('ol.interaction.MouseWheelZoom'); -goog.require('ol.interaction.PinchRotate'); -goog.require('ol.interaction.PinchZoom'); + /** + * @type {CanvasRenderingContext2D} + */ + var context = null; + /** + * @type {ImageData} + */ + var imageData = null; -/** - * Set of interactions included in maps by default. Specific interactions can be - * excluded by setting the appropriate option to false in the constructor - * options, but the order of the interactions is fixed. If you want to specify - * a different order for interactions, you will need to create your own - * {@link ol.interaction.Interaction} instances and insert them into a - * {@link ol.Collection} in the order you want before creating your - * {@link ol.Map} instance. The default set of interactions, in sequence, is: - * * {@link ol.interaction.DragRotate} - * * {@link ol.interaction.DoubleClickZoom} - * * {@link ol.interaction.DragPan} - * * {@link ol.interaction.PinchRotate} - * * {@link ol.interaction.PinchZoom} - * * {@link ol.interaction.KeyboardPan} - * * {@link ol.interaction.KeyboardZoom} - * * {@link ol.interaction.MouseWheelZoom} - * * {@link ol.interaction.DragZoom} - * - * Note that DragZoom renders a box as a vector polygon, so this interaction - * should be excluded if you want a build with no vector support. - * - * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options. - * @return {ol.Collection.<ol.interaction.Interaction>} A collection of - * interactions to be used with the ol.Map constructor's interactions option. - * @api stable - */ -ol.interaction.defaults = function(opt_options) { + return function(size) { + if (goog.isNull(context)) { + context = ol.dom.createCanvasContext2D(1, 1); + imageData = context.createImageData(1, 1); + var data = imageData.data; + data[0] = 42; + data[1] = 84; + data[2] = 126; + data[3] = 255; + } + var canvas = context.canvas; + var good = size[0] <= canvas.width && size[1] <= canvas.height; + if (!good) { + canvas.width = size[0]; + canvas.height = size[1]; + var x = size[0] - 1; + var y = size[1] - 1; + context.putImageData(imageData, x, y); + var result = context.getImageData(x, y, 1, 1); + good = goog.array.equals(imageData.data, result.data); + } + return good; + }; +})(); - var options = goog.isDef(opt_options) ? opt_options : {}; +goog.provide('ol.render.IReplayGroup'); - var interactions = new ol.Collection(); +goog.require('ol.render.VectorContext'); - var kinetic = new ol.Kinetic(-0.005, 0.05, 100); - var altShiftDragRotate = goog.isDef(options.altShiftDragRotate) ? - options.altShiftDragRotate : true; - if (altShiftDragRotate) { - interactions.push(new ol.interaction.DragRotate()); - } +/** + * @enum {string} + */ +ol.render.ReplayType = { + IMAGE: 'Image', + LINE_STRING: 'LineString', + POLYGON: 'Polygon', + TEXT: 'Text' +}; - var doubleClickZoom = goog.isDef(options.doubleClickZoom) ? - options.doubleClickZoom : true; - if (doubleClickZoom) { - interactions.push(new ol.interaction.DoubleClickZoom({ - delta: options.zoomDelta, - duration: options.zoomDuration - })); - } - var dragPan = goog.isDef(options.dragPan) ? - options.dragPan : true; - if (dragPan) { - interactions.push(new ol.interaction.DragPan({ - kinetic: kinetic - })); - } +/** + * @const + * @type {Array.<ol.render.ReplayType>} + */ +ol.render.REPLAY_ORDER = [ + ol.render.ReplayType.POLYGON, + ol.render.ReplayType.LINE_STRING, + ol.render.ReplayType.IMAGE, + ol.render.ReplayType.TEXT +]; - var pinchRotate = goog.isDef(options.pinchRotate) ? - options.pinchRotate : true; - if (pinchRotate) { - interactions.push(new ol.interaction.PinchRotate()); - } - var pinchZoom = goog.isDef(options.pinchZoom) ? - options.pinchZoom : true; - if (pinchZoom) { - interactions.push(new ol.interaction.PinchZoom({ - duration: options.zoomDuration - })); - } - var keyboard = goog.isDef(options.keyboard) ? - options.keyboard : true; - if (keyboard) { - interactions.push(new ol.interaction.KeyboardPan()); - interactions.push(new ol.interaction.KeyboardZoom({ - delta: options.zoomDelta, - duration: options.zoomDuration - })); - } +/** + * @interface + */ +ol.render.IReplayGroup = function() { +}; - var mouseWheelZoom = goog.isDef(options.mouseWheelZoom) ? - options.mouseWheelZoom : true; - if (mouseWheelZoom) { - interactions.push(new ol.interaction.MouseWheelZoom({ - duration: options.zoomDuration - })); - } - var shiftDragZoom = goog.isDef(options.shiftDragZoom) ? - options.shiftDragZoom : true; - if (shiftDragZoom) { - interactions.push(new ol.interaction.DragZoom()); - } +/** + * @param {number|undefined} zIndex Z index. + * @param {ol.render.ReplayType} replayType Replay type. + * @return {ol.render.VectorContext} Replay. + */ +ol.render.IReplayGroup.prototype.getReplay = function(zIndex, replayType) { +}; - return interactions; +/** + * @return {boolean} Is empty. + */ +ol.render.IReplayGroup.prototype.isEmpty = function() { }; -goog.provide('ol.layer.Group'); +// FIXME add option to apply snapToPixel to all coordinates? +// FIXME can eliminate empty set styles and strokes (when all geoms skipped) + +goog.provide('ol.render.canvas.Replay'); +goog.provide('ol.render.canvas.ReplayGroup'); goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.math'); goog.require('goog.object'); -goog.require('ol.Collection'); -goog.require('ol.CollectionEvent'); -goog.require('ol.CollectionEventType'); -goog.require('ol.Object'); -goog.require('ol.ObjectEventType'); +goog.require('goog.vec.Mat4'); +goog.require('ol.array'); +goog.require('ol.color'); +goog.require('ol.dom'); goog.require('ol.extent'); -goog.require('ol.layer.Base'); -goog.require('ol.source.State'); +goog.require('ol.extent.Relationship'); +goog.require('ol.geom.flat.simplify'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.has'); +goog.require('ol.render.IReplayGroup'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.canvas'); +goog.require('ol.vec.Mat4'); /** - * @enum {string} + * @enum {number} */ -ol.layer.GroupProperty = { - LAYERS: 'layers' +ol.render.canvas.Instruction = { + BEGIN_GEOMETRY: 0, + BEGIN_PATH: 1, + CIRCLE: 2, + CLOSE_PATH: 3, + DRAW_IMAGE: 4, + DRAW_TEXT: 5, + END_GEOMETRY: 6, + FILL: 7, + MOVE_TO_LINE_TO: 8, + SET_FILL_STYLE: 9, + SET_STROKE_STYLE: 10, + SET_TEXT_STYLE: 11, + STROKE: 12 }; /** - * @classdesc - * A {@link ol.Collection} of layers that are handled together. - * * @constructor - * @extends {ol.layer.Base} - * @fires change Triggered when the group/Collection changes. - * @param {olx.layer.GroupOptions=} opt_options Layer options. - * @api stable + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @protected + * @struct */ -ol.layer.Group = function(opt_options) { +ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { + goog.base(this); - var options = goog.isDef(opt_options) ? opt_options : {}; - var baseOptions = /** @type {olx.layer.GroupOptions} */ - (goog.object.clone(options)); - delete baseOptions.layers; + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; - var layers = options.layers; + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; - goog.base(this, baseOptions); + /** + * @private + * @type {ol.Extent} + */ + this.bufferedMaxExtent_ = null; + + /** + * @protected + * @type {number} + */ + this.maxLineWidth = 0; + + /** + * @protected + * @const + * @type {number} + */ + this.resolution = resolution; /** * @private - * @type {Object.<string, goog.events.Key>} + * @type {Array.<*>} */ - this.listenerKeys_ = null; + this.beginGeometryInstruction1_ = null; - goog.events.listen(this, - ol.Object.getChangeEventType(ol.layer.GroupProperty.LAYERS), - this.handleLayersChanged_, false, this); + /** + * @private + * @type {Array.<*>} + */ + this.beginGeometryInstruction2_ = null; - if (goog.isDefAndNotNull(layers)) { - if (goog.isArray(layers)) { - layers = new ol.Collection(goog.array.clone(layers)); + /** + * @protected + * @type {Array.<*>} + */ + this.instructions = []; + + /** + * @protected + * @type {Array.<number>} + */ + this.coordinates = []; + + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.renderedTransform_ = goog.vec.Mat4.createNumber(); + + /** + * @protected + * @type {Array.<*>} + */ + this.hitDetectionInstructions = []; + + /** + * @private + * @type {Array.<number>} + */ + this.pixelCoordinates_ = []; + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.tmpLocalTransform_ = goog.vec.Mat4.createNumber(); +}; +goog.inherits(ol.render.canvas.Replay, ol.render.VectorContext); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {boolean} close Close. + * @protected + * @return {number} My end. + */ +ol.render.canvas.Replay.prototype.appendFlatCoordinates = + function(flatCoordinates, offset, end, stride, close) { + + var myEnd = this.coordinates.length; + var extent = this.getBufferedMaxExtent(); + var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var nextCoord = [NaN, NaN]; + var skipped = true; + + var i, lastRel, nextRel; + for (i = offset + stride; i < end; i += stride) { + nextCoord[0] = flatCoordinates[i]; + nextCoord[1] = flatCoordinates[i + 1]; + nextRel = ol.extent.coordinateRelationship(extent, nextCoord); + if (nextRel !== lastRel) { + if (skipped) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else if (nextRel === ol.extent.Relationship.INTERSECTING) { + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; } else { - goog.asserts.assertInstanceof(layers, ol.Collection); - layers = layers; + skipped = true; } - } else { - layers = new ol.Collection(); + lastCoord[0] = nextCoord[0]; + lastCoord[1] = nextCoord[1]; + lastRel = nextRel; } - this.setLayers(layers); + // handle case where there is only one point to append + if (i === offset + stride) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + if (close) { + this.coordinates[myEnd++] = flatCoordinates[offset]; + this.coordinates[myEnd++] = flatCoordinates[offset + 1]; + } + return myEnd; }; -goog.inherits(ol.layer.Group, ol.layer.Base); /** - * @private + * @protected + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.Feature} feature Feature. */ -ol.layer.Group.prototype.handleLayerChange_ = function() { - if (this.getVisible()) { - this.changed(); - } +ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { + this.beginGeometryInstruction1_ = + [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; + this.instructions.push(this.beginGeometryInstruction1_); + this.beginGeometryInstruction2_ = + [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; + this.hitDetectionInstructions.push(this.beginGeometryInstruction2_); }; /** - * @param {goog.events.Event} event Event. * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {Array.<*>} instructions Instructions array. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T */ -ol.layer.Group.prototype.handleLayersChanged_ = function(event) { - if (!goog.isNull(this.listenerKeys_)) { - goog.array.forEach( - goog.object.getValues(this.listenerKeys_), goog.events.unlistenByKey); - this.listenerKeys_ = null; +ol.render.canvas.Replay.prototype.replay_ = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash, + instructions, featureCallback, opt_hitExtent) { + /** @type {Array.<number>} */ + var pixelCoordinates; + if (ol.vec.Mat4.equals2D(transform, this.renderedTransform_)) { + pixelCoordinates = this.pixelCoordinates_; + } else { + pixelCoordinates = ol.geom.flat.transform.transform2D( + this.coordinates, 0, this.coordinates.length, 2, + transform, this.pixelCoordinates_); + goog.vec.Mat4.setFromArray(this.renderedTransform_, transform); + goog.asserts.assert(pixelCoordinates === this.pixelCoordinates_, + 'pixelCoordinates should be the same as this.pixelCoordinates_'); } + var i = 0; // instruction index + var ii = instructions.length; // end of instructions + var d = 0; // data index + var dd; // end of per-instruction data + var localTransform = this.tmpLocalTransform_; + while (i < ii) { + var instruction = instructions[i]; + var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); + var feature, fill, stroke, text, x, y; + switch (type) { + case ol.render.canvas.Instruction.BEGIN_GEOMETRY: + feature = /** @type {ol.Feature} */ (instruction[1]); + var featureUid = goog.getUid(feature).toString(); + if (goog.isDef(skippedFeaturesHash[featureUid])) { + i = /** @type {number} */ (instruction[2]); + } else if (goog.isDef(opt_hitExtent) && !ol.extent.intersects( + opt_hitExtent, feature.getGeometry().getExtent())) { + i = /** @type {number} */ (instruction[2]); + } else { + ++i; + } + break; + case ol.render.canvas.Instruction.BEGIN_PATH: + context.beginPath(); + ++i; + break; + case ol.render.canvas.Instruction.CIRCLE: + goog.asserts.assert(goog.isNumber(instruction[1]), + 'second instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + var x1 = pixelCoordinates[d]; + var y1 = pixelCoordinates[d + 1]; + var x2 = pixelCoordinates[d + 2]; + var y2 = pixelCoordinates[d + 3]; + var dx = x2 - x1; + var dy = y2 - y1; + var r = Math.sqrt(dx * dx + dy * dy); + context.arc(x1, y1, r, 0, 2 * Math.PI, true); + ++i; + break; + case ol.render.canvas.Instruction.CLOSE_PATH: + context.closePath(); + ++i; + break; + case ol.render.canvas.Instruction.DRAW_IMAGE: + goog.asserts.assert(goog.isNumber(instruction[1]), + 'second instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + goog.asserts.assert(goog.isNumber(instruction[2]), + 'third instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + var image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */ + (instruction[3]); + // Remaining arguments in DRAW_IMAGE are in alphabetical order + var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio; + var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio; + var height = /** @type {number} */ (instruction[6]); + var opacity = /** @type {number} */ (instruction[7]); + var originX = /** @type {number} */ (instruction[8]); + var originY = /** @type {number} */ (instruction[9]); + var rotateWithView = /** @type {boolean} */ (instruction[10]); + var rotation = /** @type {number} */ (instruction[11]); + var scale = /** @type {number} */ (instruction[12]); + var snapToPixel = /** @type {boolean} */ (instruction[13]); + var width = /** @type {number} */ (instruction[14]); + if (rotateWithView) { + rotation += viewRotation; + } + for (; d < dd; d += 2) { + x = pixelCoordinates[d] - anchorX; + y = pixelCoordinates[d + 1] - anchorY; + if (snapToPixel) { + x = (x + 0.5) | 0; + y = (y + 0.5) | 0; + } + if (scale != 1 || rotation !== 0) { + var centerX = x + anchorX; + var centerY = y + anchorY; + ol.vec.Mat4.makeTransform2D( + localTransform, centerX, centerY, scale, scale, + rotation, -centerX, -centerY); + context.setTransform( + goog.vec.Mat4.getElement(localTransform, 0, 0), + goog.vec.Mat4.getElement(localTransform, 1, 0), + goog.vec.Mat4.getElement(localTransform, 0, 1), + goog.vec.Mat4.getElement(localTransform, 1, 1), + goog.vec.Mat4.getElement(localTransform, 0, 3), + goog.vec.Mat4.getElement(localTransform, 1, 3)); + } + var alpha = context.globalAlpha; + if (opacity != 1) { + context.globalAlpha = alpha * opacity; + } - var layers = this.getLayers(); - if (goog.isDefAndNotNull(layers)) { - this.listenerKeys_ = { - 'add': goog.events.listen(layers, ol.CollectionEventType.ADD, - this.handleLayersAdd_, false, this), - 'remove': goog.events.listen(layers, ol.CollectionEventType.REMOVE, - this.handleLayersRemove_, false, this) - }; + context.drawImage(image, originX, originY, width, height, + x, y, width * pixelRatio, height * pixelRatio); - var layersArray = layers.getArray(); - var i, ii, layer; - for (i = 0, ii = layersArray.length; i < ii; i++) { - layer = layersArray[i]; - this.listenerKeys_[goog.getUid(layer).toString()] = - goog.events.listen(layer, - [ol.ObjectEventType.PROPERTYCHANGE, goog.events.EventType.CHANGE], - this.handleLayerChange_, false, this); + if (opacity != 1) { + context.globalAlpha = alpha; + } + if (scale != 1 || rotation !== 0) { + context.setTransform(1, 0, 0, 1, 0, 0); + } + } + ++i; + break; + case ol.render.canvas.Instruction.DRAW_TEXT: + goog.asserts.assert(goog.isNumber(instruction[1]), + '2nd instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + goog.asserts.assert(goog.isNumber(instruction[2]), + '3rd instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + goog.asserts.assert(goog.isString(instruction[3]), + '4th instruction should be a string'); + text = /** @type {string} */ (instruction[3]); + goog.asserts.assert(goog.isNumber(instruction[4]), + '5th instruction should be a number'); + var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio; + goog.asserts.assert(goog.isNumber(instruction[5]), + '6th instruction should be a number'); + var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio; + goog.asserts.assert(goog.isNumber(instruction[6]), + '7th instruction should be a number'); + rotation = /** @type {number} */ (instruction[6]); + goog.asserts.assert(goog.isNumber(instruction[7]), + '8th instruction should be a number'); + scale = /** @type {number} */ (instruction[7]) * pixelRatio; + goog.asserts.assert(goog.isBoolean(instruction[8]), + '9th instruction should be a boolean'); + fill = /** @type {boolean} */ (instruction[8]); + goog.asserts.assert(goog.isBoolean(instruction[9]), + '10th instruction should be a boolean'); + stroke = /** @type {boolean} */ (instruction[9]); + for (; d < dd; d += 2) { + x = pixelCoordinates[d] + offsetX; + y = pixelCoordinates[d + 1] + offsetY; + if (scale != 1 || rotation !== 0) { + ol.vec.Mat4.makeTransform2D( + localTransform, x, y, scale, scale, rotation, -x, -y); + context.setTransform( + goog.vec.Mat4.getElement(localTransform, 0, 0), + goog.vec.Mat4.getElement(localTransform, 1, 0), + goog.vec.Mat4.getElement(localTransform, 0, 1), + goog.vec.Mat4.getElement(localTransform, 1, 1), + goog.vec.Mat4.getElement(localTransform, 0, 3), + goog.vec.Mat4.getElement(localTransform, 1, 3)); + } + if (stroke) { + context.strokeText(text, x, y); + } + if (fill) { + context.fillText(text, x, y); + } + if (scale != 1 || rotation !== 0) { + context.setTransform(1, 0, 0, 1, 0, 0); + } + } + ++i; + break; + case ol.render.canvas.Instruction.END_GEOMETRY: + if (goog.isDef(featureCallback)) { + feature = /** @type {ol.Feature} */ (instruction[1]); + var result = featureCallback(feature); + if (result) { + return result; + } + } + ++i; + break; + case ol.render.canvas.Instruction.FILL: + context.fill(); + ++i; + break; + case ol.render.canvas.Instruction.MOVE_TO_LINE_TO: + goog.asserts.assert(goog.isNumber(instruction[1]), + '2nd instruction should be a number'); + d = /** @type {number} */ (instruction[1]); + goog.asserts.assert(goog.isNumber(instruction[2]), + '3rd instruction should be a number'); + dd = /** @type {number} */ (instruction[2]); + context.moveTo(pixelCoordinates[d], pixelCoordinates[d + 1]); + for (d += 2; d < dd; d += 2) { + context.lineTo(pixelCoordinates[d], pixelCoordinates[d + 1]); + } + ++i; + break; + case ol.render.canvas.Instruction.SET_FILL_STYLE: + goog.asserts.assert(goog.isString(instruction[1]), + '2nd instruction should be a string'); + context.fillStyle = /** @type {string} */ (instruction[1]); + ++i; + break; + case ol.render.canvas.Instruction.SET_STROKE_STYLE: + goog.asserts.assert(goog.isString(instruction[1]), + '2nd instruction should be a string'); + goog.asserts.assert(goog.isNumber(instruction[2]), + '3rd instruction should be a number'); + goog.asserts.assert(goog.isString(instruction[3]), + '4rd instruction should be a string'); + goog.asserts.assert(goog.isString(instruction[4]), + '5th instruction should be a string'); + goog.asserts.assert(goog.isNumber(instruction[5]), + '6th instruction should be a number'); + goog.asserts.assert(!goog.isNull(instruction[6]), + '7th instruction should not be null'); + var usePixelRatio = goog.isDef(instruction[7]) ? instruction[7] : true; + var lineWidth = /** @type {number} */ (instruction[2]); + context.strokeStyle = /** @type {string} */ (instruction[1]); + context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth; + context.lineCap = /** @type {string} */ (instruction[3]); + context.lineJoin = /** @type {string} */ (instruction[4]); + context.miterLimit = /** @type {number} */ (instruction[5]); + if (ol.has.CANVAS_LINE_DASH) { + context.setLineDash(/** @type {Array.<number>} */ (instruction[6])); + } + ++i; + break; + case ol.render.canvas.Instruction.SET_TEXT_STYLE: + goog.asserts.assert(goog.isString(instruction[1]), + '2nd instruction should be a string'); + goog.asserts.assert(goog.isString(instruction[2]), + '3rd instruction should be a string'); + goog.asserts.assert(goog.isString(instruction[3]), + '4th instruction should be a string'); + context.font = /** @type {string} */ (instruction[1]); + context.textAlign = /** @type {string} */ (instruction[2]); + context.textBaseline = /** @type {string} */ (instruction[3]); + ++i; + break; + case ol.render.canvas.Instruction.STROKE: + context.stroke(); + ++i; + break; + default: + goog.asserts.fail('Unknown canvas render instruction'); + ++i; // consume the instruction anyway, to avoid an infinite loop + break; } } - - this.changed(); -}; - - -/** - * @param {ol.CollectionEvent} collectionEvent Collection event. - * @private - */ -ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) { - var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); - this.listenerKeys_[goog.getUid(layer).toString()] = goog.events.listen( - layer, [ol.ObjectEventType.PROPERTYCHANGE, goog.events.EventType.CHANGE], - this.handleLayerChange_, false, this); - this.changed(); -}; - - -/** - * @param {ol.CollectionEvent} collectionEvent Collection event. - * @private - */ -ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) { - var layer = /** @type {ol.layer.Base} */ (collectionEvent.element); - var key = goog.getUid(layer).toString(); - goog.events.unlistenByKey(this.listenerKeys_[key]); - delete this.listenerKeys_[key]; - this.changed(); -}; - - -/** - * @return {!ol.Collection.<ol.layer.Base>} Collection of - * {@link ol.layer.Layer layers} that are part of this group. - * @observable - * @api stable - */ -ol.layer.Group.prototype.getLayers = function() { - return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get( - ol.layer.GroupProperty.LAYERS)); + // assert that all instructions were consumed + goog.asserts.assert(i == instructions.length, + 'all instructions should be consumed'); + return undefined; }; -goog.exportProperty( - ol.layer.Group.prototype, - 'getLayers', - ol.layer.Group.prototype.getLayers); /** - * @param {!ol.Collection.<ol.layer.Base>} layers Collection of - * {@link ol.layer.Layer layers} that are part of this group. - * @observable - * @api stable + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip */ -ol.layer.Group.prototype.setLayers = function(layers) { - this.set(ol.layer.GroupProperty.LAYERS, layers); +ol.render.canvas.Replay.prototype.replay = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { + var instructions = this.instructions; + this.replay_(context, pixelRatio, transform, viewRotation, + skippedFeaturesHash, instructions, undefined); }; -goog.exportProperty( - ol.layer.Group.prototype, - 'setLayers', - ol.layer.Group.prototype.setLayers); /** - * @inheritDoc + * @param {CanvasRenderingContext2D} context Context. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip + * @param {function(ol.Feature): T=} opt_featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T */ -ol.layer.Group.prototype.getLayersArray = function(opt_array) { - var array = goog.isDef(opt_array) ? opt_array : []; - this.getLayers().forEach(function(layer) { - layer.getLayersArray(array); - }); - return array; +ol.render.canvas.Replay.prototype.replayHitDetection = function( + context, transform, viewRotation, skippedFeaturesHash, + opt_featureCallback, opt_hitExtent) { + var instructions = this.hitDetectionInstructions; + return this.replay_(context, 1, transform, viewRotation, + skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent); }; /** - * @inheritDoc + * @private */ -ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) { - var states = goog.isDef(opt_states) ? opt_states : []; - - var pos = states.length; - - this.getLayers().forEach(function(layer) { - layer.getLayerStatesArray(states); - }); - - var ownLayerState = this.getLayerState(); - var i, ii, layerState; - for (i = pos, ii = states.length; i < ii; i++) { - layerState = states[i]; - layerState.brightness = goog.math.clamp( - layerState.brightness + ownLayerState.brightness, -1, 1); - layerState.contrast *= ownLayerState.contrast; - layerState.hue += ownLayerState.hue; - layerState.opacity *= ownLayerState.opacity; - layerState.saturation *= ownLayerState.saturation; - layerState.visible = layerState.visible && ownLayerState.visible; - layerState.maxResolution = Math.min( - layerState.maxResolution, ownLayerState.maxResolution); - layerState.minResolution = Math.max( - layerState.minResolution, ownLayerState.minResolution); - if (goog.isDef(ownLayerState.extent)) { - if (goog.isDef(layerState.extent)) { - layerState.extent = ol.extent.getIntersection( - layerState.extent, ownLayerState.extent); - } else { - layerState.extent = ownLayerState.extent; - } +ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ = + function() { + var hitDetectionInstructions = this.hitDetectionInstructions; + // step 1 - reverse array + hitDetectionInstructions.reverse(); + // step 2 - reverse instructions within geometry blocks + var i; + var n = hitDetectionInstructions.length; + var instruction; + var type; + var begin = -1; + for (i = 0; i < n; ++i) { + instruction = hitDetectionInstructions[i]; + type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); + if (type == ol.render.canvas.Instruction.END_GEOMETRY) { + goog.asserts.assert(begin == -1, 'begin should be -1'); + begin = i; + } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) { + instruction[2] = i; + goog.asserts.assert(begin >= 0, + 'begin should be larger than or equal to 0'); + ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i); + begin = -1; } } - - return states; }; /** - * @inheritDoc + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.Feature} feature Feature. */ -ol.layer.Group.prototype.getSourceState = function() { - return ol.source.State.READY; +ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) { + goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction1_), + 'this.beginGeometryInstruction1_ should not be null'); + this.beginGeometryInstruction1_[2] = this.instructions.length; + this.beginGeometryInstruction1_ = null; + goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction2_), + 'this.beginGeometryInstruction2_ should not be null'); + this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length; + this.beginGeometryInstruction2_ = null; + var endGeometryInstruction = + [ol.render.canvas.Instruction.END_GEOMETRY, feature]; + this.instructions.push(endGeometryInstruction); + this.hitDetectionInstructions.push(endGeometryInstruction); }; -goog.provide('ol.proj.EPSG3857'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol.math'); -goog.require('ol.proj'); -goog.require('ol.proj.Projection'); -goog.require('ol.proj.Units'); - - /** - * @classdesc - * Projection object for web/spherical Mercator (EPSG:3857). - * - * @constructor - * @extends {ol.proj.Projection} - * @param {string} code Code. - * @private + * FIXME empty description for jsdoc */ -ol.proj.EPSG3857_ = function(code) { - goog.base(this, { - code: code, - units: ol.proj.Units.METERS, - extent: ol.proj.EPSG3857.EXTENT, - global: true, - worldExtent: ol.proj.EPSG3857.WORLD_EXTENT - }); -}; -goog.inherits(ol.proj.EPSG3857_, ol.proj.Projection); +ol.render.canvas.Replay.prototype.finish = goog.nullFunction; /** - * @inheritDoc + * Get the buffered rendering extent. Rendering will be clipped to the extent + * provided to the constructor. To account for symbolizers that may intersect + * this extent, we calculate a buffered extent (e.g. based on stroke width). + * @return {ol.Extent} The buffered rendering extent. + * @protected */ -ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) { - return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS); +ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() { + return this.maxExtent; }; -/** - * @const - * @type {number} - */ -ol.proj.EPSG3857.RADIUS = 6378137; - /** - * @const - * @type {number} + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @protected + * @struct */ -ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS; - +ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution) { + goog.base(this, tolerance, maxExtent, resolution); -/** - * @const - * @type {ol.Extent} - */ -ol.proj.EPSG3857.EXTENT = [ - -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE, - ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE -]; + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.hitDetectionImage_ = null; + /** + * @private + * @type {HTMLCanvasElement|HTMLVideoElement|Image} + */ + this.image_ = null; -/** - * @const - * @type {ol.Extent} - */ -ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85]; + /** + * @private + * @type {number|undefined} + */ + this.anchorX_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.anchorY_ = undefined; -/** - * Lists several projection codes with the same meaning as EPSG:3857. - * - * @type {Array.<string>} - */ -ol.proj.EPSG3857.CODES = [ - 'EPSG:3857', - 'EPSG:102100', - 'EPSG:102113', - 'EPSG:900913', - 'urn:ogc:def:crs:EPSG:6.18:3:3857', - 'urn:ogc:def:crs:EPSG::3857', - 'http://www.opengis.net/gml/srs/epsg.xml#3857' -]; + /** + * @private + * @type {number|undefined} + */ + this.height_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; -/** - * Projections equal to EPSG:3857. - * - * @const - * @type {Array.<ol.proj.Projection>} - */ -ol.proj.EPSG3857.PROJECTIONS = goog.array.map( - ol.proj.EPSG3857.CODES, - function(code) { - return new ol.proj.EPSG3857_(code); - }); + /** + * @private + * @type {number|undefined} + */ + this.originX_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.originY_ = undefined; -/** - * Transformation from EPSG:4326 to EPSG:3857. - * - * @param {Array.<number>} input Input array of coordinate values. - * @param {Array.<number>=} opt_output Output array of coordinate values. - * @param {number=} opt_dimension Dimension (default is `2`). - * @return {Array.<number>} Output array of coordinate values. - */ -ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) { - var length = input.length, - dimension = opt_dimension > 1 ? opt_dimension : 2, - output = opt_output; - if (!goog.isDef(output)) { - if (dimension > 2) { - // preserve values beyond second dimension - output = input.slice(); - } else { - output = new Array(length); - } - } - goog.asserts.assert(output.length % dimension === 0); - for (var i = 0; i < length; i += dimension) { - output[i] = ol.proj.EPSG3857.RADIUS * Math.PI * input[i] / 180; - output[i + 1] = ol.proj.EPSG3857.RADIUS * - Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360)); - } - return output; -}; + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; -/** - * Transformation from EPSG:3857 to EPSG:4326. - * - * @param {Array.<number>} input Input array of coordinate values. - * @param {Array.<number>=} opt_output Output array of coordinate values. - * @param {number=} opt_dimension Dimension (default is `2`). - * @return {Array.<number>} Output array of coordinate values. - */ -ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) { - var length = input.length, - dimension = opt_dimension > 1 ? opt_dimension : 2, - output = opt_output; - if (!goog.isDef(output)) { - if (dimension > 2) { - // preserve values beyond second dimension - output = input.slice(); - } else { - output = new Array(length); - } - } - goog.asserts.assert(output.length % dimension === 0); - for (var i = 0; i < length; i += dimension) { - output[i] = 180 * input[i] / (ol.proj.EPSG3857.RADIUS * Math.PI); - output[i + 1] = 360 * Math.atan( - Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90; - } - return output; -}; + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; -goog.provide('ol.proj.EPSG4326'); + /** + * @private + * @type {boolean|undefined} + */ + this.snapToPixel_ = undefined; -goog.require('ol.proj'); -goog.require('ol.proj.Projection'); -goog.require('ol.proj.Units'); + /** + * @private + * @type {number|undefined} + */ + this.width_ = undefined; +}; +goog.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay); /** - * @classdesc - * Projection object for WGS84 geographic coordinates (EPSG:4326). - * - * Note that OpenLayers does not strictly comply with the EPSG definition. - * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x). - * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates. - * - * @constructor - * @extends {ol.proj.Projection} - * @param {string} code Code. - * @param {string=} opt_axisOrientation Axis orientation. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. * @private + * @return {number} My end. */ -ol.proj.EPSG4326_ = function(code, opt_axisOrientation) { - goog.base(this, { - code: code, - units: ol.proj.Units.DEGREES, - extent: ol.proj.EPSG4326.EXTENT, - axisOrientation: opt_axisOrientation, - global: true, - worldExtent: ol.proj.EPSG4326.EXTENT - }); +ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = + function(flatCoordinates, offset, end, stride) { + return this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, false); }; -goog.inherits(ol.proj.EPSG4326_, ol.proj.Projection); /** * @inheritDoc */ -ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) { - return resolution; +ol.render.canvas.ImageReplay.prototype.drawPointGeometry = + function(pointGeometry, feature) { + if (goog.isNull(this.image_)) { + return; + } + goog.asserts.assert(goog.isDef(this.anchorX_), + 'this.anchorX_ should be defined'); + goog.asserts.assert(goog.isDef(this.anchorY_), + 'this.anchorY_ should be defined'); + goog.asserts.assert(goog.isDef(this.height_), + 'this.height_ should be defined'); + goog.asserts.assert(goog.isDef(this.opacity_), + 'this.opacity_ should be defined'); + goog.asserts.assert(goog.isDef(this.originX_), + 'this.originX_ should be defined'); + goog.asserts.assert(goog.isDef(this.originY_), + 'this.originY_ should be defined'); + goog.asserts.assert(goog.isDef(this.rotateWithView_), + 'this.rotateWithView_ should be defined'); + goog.asserts.assert(goog.isDef(this.rotation_), + 'this.rotation_ should be defined'); + goog.asserts.assert(goog.isDef(this.scale_), + 'this.scale_ should be defined'); + goog.asserts.assert(goog.isDef(this.width_), + 'this.width_ should be defined'); + this.beginGeometry(pointGeometry, feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + var myBegin = this.coordinates.length; + var myEnd = this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.instructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, + this.hitDetectionImage_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.endGeometry(pointGeometry, feature); }; /** - * Extent of the EPSG:4326 projection which is the whole world. - * - * @const - * @type {ol.Extent} - */ -ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90]; - - -/** - * Projections equal to EPSG:4326. - * - * @const - * @type {Array.<ol.proj.Projection>} - */ -ol.proj.EPSG4326.PROJECTIONS = [ - new ol.proj.EPSG4326_('CRS:84'), - new ol.proj.EPSG4326_('EPSG:4326', 'neu'), - new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG::4326', 'neu'), - new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'), - new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:1.3:CRS84'), - new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:2:84'), - new ol.proj.EPSG4326_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'), - new ol.proj.EPSG4326_('urn:x-ogc:def:crs:EPSG:4326', 'neu') -]; - -goog.provide('ol.proj.common'); - -goog.require('ol.proj'); -goog.require('ol.proj.EPSG3857'); -goog.require('ol.proj.EPSG4326'); - - -/** - * FIXME empty description for jsdoc - * @api + * @inheritDoc */ -ol.proj.common.add = function() { - // Add transformations that don't alter coordinates to convert within set of - // projections with equal meaning. - ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS); - ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS); - // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like - // coordinates and back. - ol.proj.addEquivalentTransforms( - ol.proj.EPSG4326.PROJECTIONS, - ol.proj.EPSG3857.PROJECTIONS, - ol.proj.EPSG3857.fromEPSG4326, - ol.proj.EPSG3857.toEPSG4326); +ol.render.canvas.ImageReplay.prototype.drawMultiPointGeometry = + function(multiPointGeometry, feature) { + if (goog.isNull(this.image_)) { + return; + } + goog.asserts.assert(goog.isDef(this.anchorX_), + 'this.anchorX_ should be defined'); + goog.asserts.assert(goog.isDef(this.anchorY_), + 'this.anchorY_ should be defined'); + goog.asserts.assert(goog.isDef(this.height_), + 'this.height_ should be defined'); + goog.asserts.assert(goog.isDef(this.opacity_), + 'this.opacity_ should be defined'); + goog.asserts.assert(goog.isDef(this.originX_), + 'this.originX_ should be defined'); + goog.asserts.assert(goog.isDef(this.originY_), + 'this.originY_ should be defined'); + goog.asserts.assert(goog.isDef(this.rotateWithView_), + 'this.rotateWithView_ should be defined'); + goog.asserts.assert(goog.isDef(this.rotation_), + 'this.rotation_ should be defined'); + goog.asserts.assert(goog.isDef(this.scale_), + 'this.scale_ should be defined'); + goog.asserts.assert(goog.isDef(this.width_), + 'this.width_ should be defined'); + this.beginGeometry(multiPointGeometry, feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + var myBegin = this.coordinates.length; + var myEnd = this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.instructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.hitDetectionInstructions.push([ + ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, + this.hitDetectionImage_, + // Remaining arguments to DRAW_IMAGE are in alphabetical order + this.anchorX_, this.anchorY_, this.height_, this.opacity_, + this.originX_, this.originY_, this.rotateWithView_, this.rotation_, + this.scale_, this.snapToPixel_, this.width_ + ]); + this.endGeometry(multiPointGeometry, feature); }; -goog.provide('ol.layer.Image'); - -goog.require('ol.layer.Layer'); - - /** - * @classdesc - * Server-rendered images that are available for arbitrary extents and - * resolutions. - * Note that any property set in the options is set as a {@link ol.Object} - * property on the layer object; for example, setting `title: 'My Title'` in the - * options means that `title` is observable, and has get/set accessors. - * - * @constructor - * @extends {ol.layer.Layer} - * @fires ol.render.Event - * @param {olx.layer.ImageOptions=} opt_options Layer options. - * @api stable + * @inheritDoc */ -ol.layer.Image = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - goog.base(this, /** @type {olx.layer.LayerOptions} */ (options)); +ol.render.canvas.ImageReplay.prototype.finish = function() { + this.reverseHitDetectionInstructions_(); + // FIXME this doesn't really protect us against further calls to draw*Geometry + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.hitDetectionImage_ = null; + this.image_ = null; + this.height_ = undefined; + this.scale_ = undefined; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.snapToPixel_ = undefined; + this.width_ = undefined; }; -goog.inherits(ol.layer.Image, ol.layer.Layer); /** - * @function - * @return {ol.source.Image} Source. - * @api stable + * @inheritDoc */ -ol.layer.Image.prototype.getSource; - -goog.provide('ol.layer.Tile'); +ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { + goog.asserts.assert(!goog.isNull(imageStyle), + 'imageStyle should not be null'); + var anchor = imageStyle.getAnchor(); + goog.asserts.assert(!goog.isNull(anchor), 'anchor should not be null'); + var size = imageStyle.getSize(); + goog.asserts.assert(!goog.isNull(size), 'size should not be null'); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + goog.asserts.assert(!goog.isNull(hitDetectionImage), + 'hitDetectionImage should not be null'); + var image = imageStyle.getImage(1); + goog.asserts.assert(!goog.isNull(image), 'image should not be null'); + var origin = imageStyle.getOrigin(); + goog.asserts.assert(!goog.isNull(origin), 'origin should not be null'); + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.hitDetectionImage_ = hitDetectionImage; + this.image_ = image; + this.height_ = size[1]; + this.opacity_ = imageStyle.getOpacity(); + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotateWithView_ = imageStyle.getRotateWithView(); + this.rotation_ = imageStyle.getRotation(); + this.scale_ = imageStyle.getScale(); + this.snapToPixel_ = imageStyle.getSnapToPixel(); + this.width_ = size[0]; +}; -goog.require('ol.layer.Layer'); /** - * @enum {string} + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @protected + * @struct */ -ol.layer.TileProperty = { - PRELOAD: 'preload', - USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' -}; +ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution) { + goog.base(this, tolerance, maxExtent, resolution); + /** + * @private + * @type {{currentStrokeStyle: (string|undefined), + * currentLineCap: (string|undefined), + * currentLineDash: Array.<number>, + * currentLineJoin: (string|undefined), + * currentLineWidth: (number|undefined), + * currentMiterLimit: (number|undefined), + * lastStroke: number, + * strokeStyle: (string|undefined), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} + */ + this.state_ = { + currentStrokeStyle: undefined, + currentLineCap: undefined, + currentLineDash: null, + currentLineJoin: undefined, + currentLineWidth: undefined, + currentMiterLimit: undefined, + lastStroke: 0, + strokeStyle: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; -/** - * @classdesc - * For layer sources that provide pre-rendered, tiled images in grids that are - * organized by zoom levels for specific resolutions. - * Note that any property set in the options is set as a {@link ol.Object} - * property on the layer object; for example, setting `title: 'My Title'` in the - * options means that `title` is observable, and has get/set accessors. - * - * @constructor - * @extends {ol.layer.Layer} - * @fires ol.render.Event - * @param {olx.layer.TileOptions=} opt_options Tile layer options. - * @api stable - */ -ol.layer.Tile = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - goog.base(this, /** @type {olx.layer.LayerOptions} */ (options)); }; -goog.inherits(ol.layer.Tile, ol.layer.Layer); +goog.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay); /** - * @return {number|undefined} The level to preload tiles up to. - * @observable - * @api + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + * @return {number} end. */ -ol.layer.Tile.prototype.getPreload = function() { - return /** @type {number|undefined} */ ( - this.get(ol.layer.TileProperty.PRELOAD)); +ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = + function(flatCoordinates, offset, end, stride) { + var myBegin = this.coordinates.length; + var myEnd = this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, false); + var moveToLineToInstruction = + [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; + this.instructions.push(moveToLineToInstruction); + this.hitDetectionInstructions.push(moveToLineToInstruction); + return end; }; -goog.exportProperty( - ol.layer.Tile.prototype, - 'getPreload', - ol.layer.Tile.prototype.getPreload); /** - * @function - * @return {ol.source.Tile} Source. - * @api stable + * @inheritDoc */ -ol.layer.Tile.prototype.getSource; +ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() { + if (goog.isNull(this.bufferedMaxExtent_)) { + this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent); + if (this.maxLineWidth > 0) { + var width = this.resolution * (this.maxLineWidth + 1) / 2; + ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; +}; /** - * @param {number} preload The level to preload tiles up to. - * @observable - * @api + * @private */ -ol.layer.Tile.prototype.setPreload = function(preload) { - this.set(ol.layer.TileProperty.PRELOAD, preload); +ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() { + var state = this.state_; + var strokeStyle = state.strokeStyle; + var lineCap = state.lineCap; + var lineDash = state.lineDash; + var lineJoin = state.lineJoin; + var lineWidth = state.lineWidth; + var miterLimit = state.miterLimit; + goog.asserts.assert(goog.isDef(strokeStyle), + 'strokeStyle should be defined'); + goog.asserts.assert(goog.isDef(lineCap), 'lineCap should be defined'); + goog.asserts.assert(!goog.isNull(lineDash), 'lineDash should not be null'); + goog.asserts.assert(goog.isDef(lineJoin), 'lineJoin should be defined'); + goog.asserts.assert(goog.isDef(lineWidth), 'lineWidth should be defined'); + goog.asserts.assert(goog.isDef(miterLimit), 'miterLimit should be defined'); + if (state.currentStrokeStyle != strokeStyle || + state.currentLineCap != lineCap || + !goog.array.equals(state.currentLineDash, lineDash) || + state.currentLineJoin != lineJoin || + state.currentLineWidth != lineWidth || + state.currentMiterLimit != miterLimit) { + if (state.lastStroke != this.coordinates.length) { + this.instructions.push( + [ol.render.canvas.Instruction.STROKE]); + state.lastStroke = this.coordinates.length; + } + this.instructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash], + [ol.render.canvas.Instruction.BEGIN_PATH]); + state.currentStrokeStyle = strokeStyle; + state.currentLineCap = lineCap; + state.currentLineDash = lineDash; + state.currentLineJoin = lineJoin; + state.currentLineWidth = lineWidth; + state.currentMiterLimit = miterLimit; + } }; -goog.exportProperty( - ol.layer.Tile.prototype, - 'setPreload', - ol.layer.Tile.prototype.setPreload); /** - * @return {boolean|undefined} Use interim tiles on error. - * @observable - * @api + * @inheritDoc */ -ol.layer.Tile.prototype.getUseInterimTilesOnError = function() { - return /** @type {boolean|undefined} */ ( - this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR)); +ol.render.canvas.LineStringReplay.prototype.drawLineStringGeometry = + function(lineStringGeometry, feature) { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state), 'state should not be null'); + var strokeStyle = state.strokeStyle; + var lineWidth = state.lineWidth; + if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) { + return; + } + this.setStrokeStyle_(); + this.beginGeometry(lineStringGeometry, feature); + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash], + [ol.render.canvas.Instruction.BEGIN_PATH]); + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + this.drawFlatCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); + this.endGeometry(lineStringGeometry, feature); }; -goog.exportProperty( - ol.layer.Tile.prototype, - 'getUseInterimTilesOnError', - ol.layer.Tile.prototype.getUseInterimTilesOnError); /** - * @param {boolean|undefined} useInterimTilesOnError Use interim tiles on error. - * @observable - * @api + * @inheritDoc */ -ol.layer.Tile.prototype.setUseInterimTilesOnError = - function(useInterimTilesOnError) { - this.set( - ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); +ol.render.canvas.LineStringReplay.prototype.drawMultiLineStringGeometry = + function(multiLineStringGeometry, feature) { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state), 'state should not be null'); + var strokeStyle = state.strokeStyle; + var lineWidth = state.lineWidth; + if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) { + return; + } + this.setStrokeStyle_(); + this.beginGeometry(multiLineStringGeometry, feature); + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash], + [ol.render.canvas.Instruction.BEGIN_PATH]); + var ends = multiLineStringGeometry.getEnds(); + var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); + var stride = multiLineStringGeometry.getStride(); + var offset = 0; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + offset = this.drawFlatCoordinates_( + flatCoordinates, offset, ends[i], stride); + } + this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); + this.endGeometry(multiLineStringGeometry, feature); }; -goog.exportProperty( - ol.layer.Tile.prototype, - 'setUseInterimTilesOnError', - ol.layer.Tile.prototype.setUseInterimTilesOnError); - -goog.provide('ol.layer.Vector'); - -goog.require('goog.object'); -goog.require('ol.layer.Layer'); -goog.require('ol.style.Style'); /** - * @enum {string} + * @inheritDoc */ -ol.layer.VectorProperty = { - RENDER_ORDER: 'renderOrder' +ol.render.canvas.LineStringReplay.prototype.finish = function() { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state), 'state should not be null'); + if (state.lastStroke != this.coordinates.length) { + this.instructions.push([ol.render.canvas.Instruction.STROKE]); + } + this.reverseHitDetectionInstructions_(); + this.state_ = null; }; - /** - * @classdesc - * Vector data that is rendered client-side. - * Note that any property set in the options is set as a {@link ol.Object} - * property on the layer object; for example, setting `title: 'My Title'` in the - * options means that `title` is observable, and has get/set accessors. - * - * @constructor - * @extends {ol.layer.Layer} - * @fires ol.render.Event - * @param {olx.layer.VectorOptions=} opt_options Options. - * @api stable + * @inheritDoc */ -ol.layer.Vector = function(opt_options) { +ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = + function(fillStyle, strokeStyle) { + goog.asserts.assert(!goog.isNull(this.state_), + 'this.state_ should not be null'); + goog.asserts.assert(goog.isNull(fillStyle), 'fillStyle should be null'); + goog.asserts.assert(!goog.isNull(strokeStyle), + 'strokeStyle should not be null'); + var strokeStyleColor = strokeStyle.getColor(); + this.state_.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = goog.isDef(strokeStyleLineCap) ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = !goog.isNull(strokeStyleLineDash) ? + strokeStyleLineDash : ol.render.canvas.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = goog.isDef(strokeStyleLineJoin) ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var strokeStyleWidth = strokeStyle.getWidth(); + this.state_.lineWidth = goog.isDef(strokeStyleWidth) ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + this.state_.miterLimit = goog.isDef(strokeStyleMiterLimit) ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; - var options = goog.isDef(opt_options) ? - opt_options : /** @type {olx.layer.VectorOptions} */ ({}); + if (this.state_.lineWidth > this.maxLineWidth) { + this.maxLineWidth = this.state_.lineWidth; + // invalidate the buffered max extent cache + this.bufferedMaxExtent_ = null; + } +}; - var baseOptions = goog.object.clone(options); - delete baseOptions.style; - goog.base(this, /** @type {olx.layer.LayerOptions} */ (baseOptions)); - /** - * @type {number} - * @private - */ - this.renderBuffer_ = goog.isDef(options.renderBuffer) ? - options.renderBuffer : 100; +/** + * @constructor + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @protected + * @struct + */ +ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) { - /** - * User provided style. - * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} - * @private - */ - this.style_ = null; + goog.base(this, tolerance, maxExtent, resolution); /** - * Style function for use within the library. - * @type {ol.style.StyleFunction|undefined} * @private + * @type {{currentFillStyle: (string|undefined), + * currentStrokeStyle: (string|undefined), + * currentLineCap: (string|undefined), + * currentLineDash: Array.<number>, + * currentLineJoin: (string|undefined), + * currentLineWidth: (number|undefined), + * currentMiterLimit: (number|undefined), + * fillStyle: (string|undefined), + * strokeStyle: (string|undefined), + * lineCap: (string|undefined), + * lineDash: Array.<number>, + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined)}|null} */ - this.styleFunction_ = undefined; - - this.setStyle(options.style); + this.state_ = { + currentFillStyle: undefined, + currentStrokeStyle: undefined, + currentLineCap: undefined, + currentLineDash: null, + currentLineJoin: undefined, + currentLineWidth: undefined, + currentMiterLimit: undefined, + fillStyle: undefined, + strokeStyle: undefined, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined + }; }; -goog.inherits(ol.layer.Vector, ol.layer.Layer); +goog.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay); /** - * @return {number|undefined} Render buffer. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @private + * @return {number} End. */ -ol.layer.Vector.prototype.getRenderBuffer = function() { - return this.renderBuffer_; +ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = + function(flatCoordinates, offset, ends, stride) { + var state = this.state_; + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + this.instructions.push(beginPathInstruction); + this.hitDetectionInstructions.push(beginPathInstruction); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var myBegin = this.coordinates.length; + var myEnd = this.appendFlatCoordinates( + flatCoordinates, offset, end, stride, true); + var moveToLineToInstruction = + [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; + var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH]; + this.instructions.push(moveToLineToInstruction, closePathInstruction); + this.hitDetectionInstructions.push(moveToLineToInstruction, + closePathInstruction); + offset = end; + } + // FIXME is it quicker to fill and stroke each polygon individually, + // FIXME or all polygons together? + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (goog.isDef(state.fillStyle)) { + this.instructions.push(fillInstruction); + } + if (goog.isDef(state.strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth), + 'state.lineWidth should be defined'); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } + return offset; }; /** - * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render - * order. + * @inheritDoc */ -ol.layer.Vector.prototype.getRenderOrder = function() { - return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ ( - this.get(ol.layer.VectorProperty.RENDER_ORDER)); +ol.render.canvas.PolygonReplay.prototype.drawCircleGeometry = + function(circleGeometry, feature) { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state), 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { + return; + } + if (goog.isDef(strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth), + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(); + this.beginGeometry(circleGeometry, feature); + // always fill the circle for hit detection + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle)]); + if (goog.isDef(state.strokeStyle)) { + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash]); + } + var flatCoordinates = circleGeometry.getFlatCoordinates(); + var stride = circleGeometry.getStride(); + var myBegin = this.coordinates.length; + this.appendFlatCoordinates( + flatCoordinates, 0, flatCoordinates.length, stride, false); + var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; + var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin]; + this.instructions.push(beginPathInstruction, circleInstruction); + this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction); + var fillInstruction = [ol.render.canvas.Instruction.FILL]; + this.hitDetectionInstructions.push(fillInstruction); + if (goog.isDef(state.fillStyle)) { + this.instructions.push(fillInstruction); + } + if (goog.isDef(state.strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth), + 'state.lineWidth should be defined'); + var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; + this.instructions.push(strokeInstruction); + this.hitDetectionInstructions.push(strokeInstruction); + } + this.endGeometry(circleGeometry, feature); }; /** - * @function - * @return {ol.source.Vector} Source. - * @api stable + * @inheritDoc */ -ol.layer.Vector.prototype.getSource; +ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry = + function(polygonGeometry, feature) { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state), 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { + return; + } + if (goog.isDef(strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth), + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(); + this.beginGeometry(polygonGeometry, feature); + // always fill the polygon for hit detection + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle)]); + if (goog.isDef(state.strokeStyle)) { + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash]); + } + var ends = polygonGeometry.getEnds(); + var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates(); + var stride = polygonGeometry.getStride(); + this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride); + this.endGeometry(polygonGeometry, feature); +}; /** - * Get the style for features. This returns whatever was passed to the `style` - * option at construction or to the `setStyle` method. - * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} - * Layer style. - * @api stable + * @inheritDoc */ -ol.layer.Vector.prototype.getStyle = function() { - return this.style_; +ol.render.canvas.PolygonReplay.prototype.drawMultiPolygonGeometry = + function(multiPolygonGeometry, feature) { + var state = this.state_; + goog.asserts.assert(!goog.isNull(state), 'state should not be null'); + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { + return; + } + if (goog.isDef(strokeStyle)) { + goog.asserts.assert(goog.isDef(state.lineWidth), + 'state.lineWidth should be defined'); + } + this.setFillStrokeStyles_(); + this.beginGeometry(multiPolygonGeometry, feature); + // always fill the multi-polygon for hit detection + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_FILL_STYLE, + ol.color.asString(ol.render.canvas.defaultFillStyle)]); + if (goog.isDef(state.strokeStyle)) { + this.hitDetectionInstructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, + state.miterLimit, state.lineDash]); + } + var endss = multiPolygonGeometry.getEndss(); + var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); + var stride = multiPolygonGeometry.getStride(); + var offset = 0; + var i, ii; + for (i = 0, ii = endss.length; i < ii; ++i) { + offset = this.drawFlatCoordinatess_( + flatCoordinates, offset, endss[i], stride); + } + this.endGeometry(multiPolygonGeometry, feature); }; /** - * Get the style function. - * @return {ol.style.StyleFunction|undefined} Layer style function. - * @api stable + * @inheritDoc */ -ol.layer.Vector.prototype.getStyleFunction = function() { - return this.styleFunction_; +ol.render.canvas.PolygonReplay.prototype.finish = function() { + goog.asserts.assert(!goog.isNull(this.state_), + 'this.state_ should not be null'); + this.reverseHitDetectionInstructions_(); + this.state_ = null; + // We want to preserve topology when drawing polygons. Polygons are + // simplified using quantization and point elimination. However, we might + // have received a mix of quantized and non-quantized geometries, so ensure + // that all are quantized by quantizing all coordinates in the batch. + var tolerance = this.tolerance; + if (tolerance !== 0) { + var coordinates = this.coordinates; + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance); + } + } }; /** - * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder - * Render order. + * @inheritDoc */ -ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) { - this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder); +ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() { + if (goog.isNull(this.bufferedMaxExtent_)) { + this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent); + if (this.maxLineWidth > 0) { + var width = this.resolution * (this.maxLineWidth + 1) / 2; + ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_); + } + } + return this.bufferedMaxExtent_; }; /** - * Set the style for features. This can be a single style object, an array - * of styles, or a function that takes a feature and resolution and returns - * an array of styles. If it is `undefined` the default style is used. If - * it is `null` the layer has no style (a `null` style), so only features - * that have their own styles will be rendered in the layer. See - * {@link ol.style} for information on the default style. - * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined} - * style Layer style. - * @api stable + * @inheritDoc */ -ol.layer.Vector.prototype.setStyle = function(style) { - this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction; - this.styleFunction_ = goog.isNull(style) ? - undefined : ol.style.createStyleFunction(this.style_); - this.changed(); -}; +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = + function(fillStyle, strokeStyle) { + goog.asserts.assert(!goog.isNull(this.state_), + 'this.state_ should not be null'); + goog.asserts.assert(!goog.isNull(fillStyle) || !goog.isNull(strokeStyle), + 'fillStyle or strokeStyle should not be null'); + var state = this.state_; + if (!goog.isNull(fillStyle)) { + var fillStyleColor = fillStyle.getColor(); + state.fillStyle = ol.color.asString(!goog.isNull(fillStyleColor) ? + fillStyleColor : ol.render.canvas.defaultFillStyle); + } else { + state.fillStyle = undefined; + } + if (!goog.isNull(strokeStyle)) { + var strokeStyleColor = strokeStyle.getColor(); + state.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ? + strokeStyleColor : ol.render.canvas.defaultStrokeStyle); + var strokeStyleLineCap = strokeStyle.getLineCap(); + state.lineCap = goog.isDef(strokeStyleLineCap) ? + strokeStyleLineCap : ol.render.canvas.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + state.lineDash = !goog.isNull(strokeStyleLineDash) ? + strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + state.lineJoin = goog.isDef(strokeStyleLineJoin) ? + strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var strokeStyleWidth = strokeStyle.getWidth(); + state.lineWidth = goog.isDef(strokeStyleWidth) ? + strokeStyleWidth : ol.render.canvas.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + state.miterLimit = goog.isDef(strokeStyleMiterLimit) ? + strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; -// FIXME test, especially polygons with holes and multipolygons -// FIXME need to handle large thick features (where pixel size matters) -// FIXME add offset and end to ol.geom.flat.transform.transform2D? + if (state.lineWidth > this.maxLineWidth) { + this.maxLineWidth = state.lineWidth; + // invalidate the buffered max extent cache + this.bufferedMaxExtent_ = null; + } + } else { + state.strokeStyle = undefined; + state.lineCap = undefined; + state.lineDash = null; + state.lineJoin = undefined; + state.lineWidth = undefined; + state.miterLimit = undefined; + } +}; -goog.provide('ol.render.canvas.Immediate'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('goog.vec.Mat4'); -goog.require('ol.color'); -goog.require('ol.extent'); -goog.require('ol.geom.flat.transform'); -goog.require('ol.has'); -goog.require('ol.render.IVectorContext'); -goog.require('ol.render.canvas'); -goog.require('ol.vec.Mat4'); +/** + * @private + */ +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { + var state = this.state_; + var fillStyle = state.fillStyle; + var strokeStyle = state.strokeStyle; + var lineCap = state.lineCap; + var lineDash = state.lineDash; + var lineJoin = state.lineJoin; + var lineWidth = state.lineWidth; + var miterLimit = state.miterLimit; + if (goog.isDef(fillStyle) && state.currentFillStyle != fillStyle) { + this.instructions.push( + [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]); + state.currentFillStyle = state.fillStyle; + } + if (goog.isDef(strokeStyle)) { + goog.asserts.assert(goog.isDef(lineCap), 'lineCap should be defined'); + goog.asserts.assert(!goog.isNull(lineDash), 'lineDash should not be null'); + goog.asserts.assert(goog.isDef(lineJoin), 'lineJoin should be defined'); + goog.asserts.assert(goog.isDef(lineWidth), 'lineWidth should be defined'); + goog.asserts.assert(goog.isDef(miterLimit), 'miterLimit should be defined'); + if (state.currentStrokeStyle != strokeStyle || + state.currentLineCap != lineCap || + state.currentLineDash != lineDash || + state.currentLineJoin != lineJoin || + state.currentLineWidth != lineWidth || + state.currentMiterLimit != miterLimit) { + this.instructions.push( + [ol.render.canvas.Instruction.SET_STROKE_STYLE, + strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash]); + state.currentStrokeStyle = strokeStyle; + state.currentLineCap = lineCap; + state.currentLineDash = lineDash; + state.currentLineJoin = lineJoin; + state.currentLineWidth = lineWidth; + state.currentMiterLimit = miterLimit; + } + } +}; /** - * @classdesc - * A concrete subclass of {@link ol.render.IVectorContext} that implements - * direct rendering of features and geometries to an HTML5 Canvas context. - * Instances of this class are created internally by the library and - * provided to application code as vectorContext member of the - * {@link ol.render.Event} object associated with postcompose, precompose and - * render events emitted by layers and maps. - * * @constructor - * @implements {ol.render.IVectorContext} - * @param {CanvasRenderingContext2D} context Context. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.Extent} extent Extent. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {number} viewRotation View rotation. + * @extends {ol.render.canvas.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. + * @param {number} resolution Resolution. + * @protected * @struct */ -ol.render.canvas.Immediate = - function(context, pixelRatio, extent, transform, viewRotation) { - - /** - * @private - * @type {Object.<string, - * Array.<function(ol.render.canvas.Immediate)>>} - */ - this.callbacksByZIndex_ = {}; - - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = context; - - /** - * @private - * @type {number} - */ - this.pixelRatio_ = pixelRatio; - - /** - * @private - * @type {ol.Extent} - */ - this.extent_ = extent; - - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.transform_ = transform; +ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution) { - /** - * @private - * @type {number} - */ - this.viewRotation_ = viewRotation; + goog.base(this, tolerance, maxExtent, resolution); /** * @private * @type {?ol.render.canvas.FillState} */ - this.contextFillState_ = null; + this.replayFillState_ = null; /** * @private * @type {?ol.render.canvas.StrokeState} */ - this.contextStrokeState_ = null; + this.replayStrokeState_ = null; /** * @private * @type {?ol.render.canvas.TextState} */ - this.contextTextState_ = null; - - /** - * @private - * @type {?ol.render.canvas.FillState} - */ - this.fillState_ = null; - - /** - * @private - * @type {?ol.render.canvas.StrokeState} - */ - this.strokeState_ = null; - - /** - * @private - * @type {HTMLCanvasElement|HTMLVideoElement|Image} - */ - this.image_ = null; - - /** - * @private - * @type {number} - */ - this.imageAnchorX_ = 0; - - /** - * @private - * @type {number} - */ - this.imageAnchorY_ = 0; - - /** - * @private - * @type {number} - */ - this.imageHeight_ = 0; - - /** - * @private - * @type {number} - */ - this.imageOpacity_ = 0; - - /** - * @private - * @type {number} - */ - this.imageOriginX_ = 0; - - /** - * @private - * @type {number} - */ - this.imageOriginY_ = 0; - - /** - * @private - * @type {boolean} - */ - this.imageRotateWithView_ = false; - - /** - * @private - * @type {number} - */ - this.imageRotation_ = 0; - - /** - * @private - * @type {number} - */ - this.imageScale_ = 0; - - /** - * @private - * @type {boolean} - */ - this.imageSnapToPixel_ = false; - - /** - * @private - * @type {number} - */ - this.imageWidth_ = 0; + this.replayTextState_ = null; /** * @private @@ -59619,7775 +60522,7607 @@ ol.render.canvas.Immediate = * @type {?ol.render.canvas.TextState} */ this.textState_ = null; - - /** - * @private - * @type {Array.<number>} - */ - this.pixelCoordinates_ = []; - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.tmpLocalTransform_ = goog.vec.Mat4.createNumber(); - -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @private - */ -ol.render.canvas.Immediate.prototype.drawImages_ = - function(flatCoordinates, offset, end, stride) { - if (goog.isNull(this.image_)) { - return; - } - goog.asserts.assert(offset === 0); - goog.asserts.assert(end == flatCoordinates.length); - var pixelCoordinates = ol.geom.flat.transform.transform2D( - flatCoordinates, offset, end, 2, this.transform_, - this.pixelCoordinates_); - var context = this.context_; - var localTransform = this.tmpLocalTransform_; - var alpha = context.globalAlpha; - if (this.imageOpacity_ != 1) { - context.globalAlpha = alpha * this.imageOpacity_; - } - var rotation = this.imageRotation_; - if (this.imageRotateWithView_) { - rotation += this.viewRotation_; - } - var i, ii; - for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) { - var x = pixelCoordinates[i] - this.imageAnchorX_; - var y = pixelCoordinates[i + 1] - this.imageAnchorY_; - if (this.imageSnapToPixel_) { - x = (x + 0.5) | 0; - y = (y + 0.5) | 0; - } - if (rotation !== 0 || this.imageScale_ != 1) { - var centerX = x + this.imageAnchorX_; - var centerY = y + this.imageAnchorY_; - ol.vec.Mat4.makeTransform2D(localTransform, - centerX, centerY, this.imageScale_, this.imageScale_, - rotation, -centerX, -centerY); - context.setTransform( - goog.vec.Mat4.getElement(localTransform, 0, 0), - goog.vec.Mat4.getElement(localTransform, 1, 0), - goog.vec.Mat4.getElement(localTransform, 0, 1), - goog.vec.Mat4.getElement(localTransform, 1, 1), - goog.vec.Mat4.getElement(localTransform, 0, 3), - goog.vec.Mat4.getElement(localTransform, 1, 3)); - } - context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_, - this.imageWidth_, this.imageHeight_, x, y, - this.imageWidth_, this.imageHeight_); - } - if (rotation !== 0 || this.imageScale_ != 1) { - context.setTransform(1, 0, 0, 1, 0, 0); - } - if (this.imageOpacity_ != 1) { - context.globalAlpha = alpha; - } -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @private - */ -ol.render.canvas.Immediate.prototype.drawText_ = - function(flatCoordinates, offset, end, stride) { - if (goog.isNull(this.textState_) || this.text_ === '') { - return; - } - if (!goog.isNull(this.textFillState_)) { - this.setContextFillState_(this.textFillState_); - } - if (!goog.isNull(this.textStrokeState_)) { - this.setContextStrokeState_(this.textStrokeState_); - } - this.setContextTextState_(this.textState_); - goog.asserts.assert(offset === 0); - goog.asserts.assert(end == flatCoordinates.length); - var pixelCoordinates = ol.geom.flat.transform.transform2D( - flatCoordinates, offset, end, stride, this.transform_, - this.pixelCoordinates_); - var context = this.context_; - for (; offset < end; offset += stride) { - var x = pixelCoordinates[offset] + this.textOffsetX_; - var y = pixelCoordinates[offset + 1] + this.textOffsetY_; - if (this.textRotation_ !== 0 || this.textScale_ != 1) { - var localTransform = ol.vec.Mat4.makeTransform2D(this.tmpLocalTransform_, - x, y, this.textScale_, this.textScale_, this.textRotation_, -x, -y); - context.setTransform( - goog.vec.Mat4.getElement(localTransform, 0, 0), - goog.vec.Mat4.getElement(localTransform, 1, 0), - goog.vec.Mat4.getElement(localTransform, 0, 1), - goog.vec.Mat4.getElement(localTransform, 1, 1), - goog.vec.Mat4.getElement(localTransform, 0, 3), - goog.vec.Mat4.getElement(localTransform, 1, 3)); - } - if (!goog.isNull(this.textStrokeState_)) { - context.strokeText(this.text_, x, y); - } - if (!goog.isNull(this.textFillState_)) { - context.fillText(this.text_, x, y); - } - } - if (this.textRotation_ !== 0 || this.textScale_ != 1) { - context.setTransform(1, 0, 0, 1, 0, 0); - } -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {boolean} close Close. - * @private - * @return {number} end End. - */ -ol.render.canvas.Immediate.prototype.moveToLineTo_ = - function(flatCoordinates, offset, end, stride, close) { - var context = this.context_; - var pixelCoordinates = ol.geom.flat.transform.transform2D( - flatCoordinates, offset, end, stride, this.transform_, - this.pixelCoordinates_); - context.moveTo(pixelCoordinates[0], pixelCoordinates[1]); - var i; - for (i = 2; i < pixelCoordinates.length; i += 2) { - context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]); - } - if (close) { - context.lineTo(pixelCoordinates[0], pixelCoordinates[1]); - } - return end; -}; - - -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @private - * @return {number} End. - */ -ol.render.canvas.Immediate.prototype.drawRings_ = - function(flatCoordinates, offset, ends, stride) { - var context = this.context_; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - offset = this.moveToLineTo_( - flatCoordinates, offset, ends[i], stride, true); - context.closePath(); // FIXME is this needed here? - } - return offset; -}; - - -/** - * Register a function to be called for rendering at a given zIndex. The - * function will be called asynchronously. The callback will receive a - * reference to {@link ol.render.canvas.Immediate} context for drawing. - * - * @param {number} zIndex Z index. - * @param {function(ol.render.canvas.Immediate)} callback Callback. - * @api - */ -ol.render.canvas.Immediate.prototype.drawAsync = function(zIndex, callback) { - var zIndexKey = zIndex.toString(); - var callbacks = this.callbacksByZIndex_[zIndexKey]; - if (goog.isDef(callbacks)) { - callbacks.push(callback); - } else { - this.callbacksByZIndex_[zIndexKey] = [callback]; - } + }; +goog.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay); /** - * Render a circle geometry into the canvas. Rendering is immediate and uses - * the current fill and stroke styles. - * - * @param {ol.geom.Circle} circleGeometry Circle geometry. - * @param {ol.Feature} feature Feature, - * @api + * @inheritDoc */ -ol.render.canvas.Immediate.prototype.drawCircleGeometry = - function(circleGeometry, feature) { - if (!ol.extent.intersects(this.extent_, circleGeometry.getExtent())) { +ol.render.canvas.TextReplay.prototype.drawText = + function(flatCoordinates, offset, end, stride, geometry, feature) { + if (this.text_ === '' || + goog.isNull(this.textState_) || + (goog.isNull(this.textFillState_) && + goog.isNull(this.textStrokeState_))) { return; } - if (!goog.isNull(this.fillState_) || !goog.isNull(this.strokeState_)) { - if (!goog.isNull(this.fillState_)) { - this.setContextFillState_(this.fillState_); - } - if (!goog.isNull(this.strokeState_)) { - this.setContextStrokeState_(this.strokeState_); - } - var pixelCoordinates = ol.geom.transformSimpleGeometry2D( - circleGeometry, this.transform_, this.pixelCoordinates_); - var dx = pixelCoordinates[2] - pixelCoordinates[0]; - var dy = pixelCoordinates[3] - pixelCoordinates[1]; - var radius = Math.sqrt(dx * dx + dy * dy); - var context = this.context_; - context.beginPath(); - context.arc( - pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI); - if (!goog.isNull(this.fillState_)) { - context.fill(); - } - if (!goog.isNull(this.strokeState_)) { - context.stroke(); - } + if (!goog.isNull(this.textFillState_)) { + this.setReplayFillState_(this.textFillState_); } - if (this.text_ !== '') { - this.drawText_(circleGeometry.getCenter(), 0, 2, 2); + if (!goog.isNull(this.textStrokeState_)) { + this.setReplayStrokeState_(this.textStrokeState_); } + this.setReplayTextState_(this.textState_); + this.beginGeometry(geometry, feature); + var myBegin = this.coordinates.length; + var myEnd = + this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false); + var fill = !goog.isNull(this.textFillState_); + var stroke = !goog.isNull(this.textStrokeState_); + var drawTextInstruction = [ + ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_, + this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_, + fill, stroke]; + this.instructions.push(drawTextInstruction); + this.hitDetectionInstructions.push(drawTextInstruction); + this.endGeometry(geometry, feature); }; /** - * Render a feature into the canvas. In order to respect the zIndex of the - * style this method draws asynchronously and thus *after* calls to - * drawXxxxGeometry have been finished, effectively drawing the feature - * *on top* of everything else. You probably should be using - * {@link ol.FeatureOverlay} instead of calling this method directly. - * - * @param {ol.Feature} feature Feature. - * @param {ol.style.Style} style Style. - * @api + * @param {ol.render.canvas.FillState} fillState Fill state. + * @private */ -ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) { - var geometry = style.getGeometryFunction()(feature); - if (!goog.isDefAndNotNull(geometry) || - !ol.extent.intersects(this.extent_, geometry.getExtent())) { +ol.render.canvas.TextReplay.prototype.setReplayFillState_ = + function(fillState) { + var replayFillState = this.replayFillState_; + if (!goog.isNull(replayFillState) && + replayFillState.fillStyle == fillState.fillStyle) { return; } - var zIndex = style.getZIndex(); - if (!goog.isDef(zIndex)) { - zIndex = 0; + var setFillStyleInstruction = + [ol.render.canvas.Instruction.SET_FILL_STYLE, fillState.fillStyle]; + this.instructions.push(setFillStyleInstruction); + this.hitDetectionInstructions.push(setFillStyleInstruction); + if (goog.isNull(replayFillState)) { + this.replayFillState_ = { + fillStyle: fillState.fillStyle + }; + } else { + replayFillState.fillStyle = fillState.fillStyle; } - this.drawAsync(zIndex, function(render) { - render.setFillStrokeStyle(style.getFill(), style.getStroke()); - render.setImageStyle(style.getImage()); - render.setTextStyle(style.getText()); - var renderGeometry = - ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()]; - goog.asserts.assert(goog.isDef(renderGeometry)); - renderGeometry.call(render, geometry, null); - }); }; /** - * Render a GeometryCollection to the canvas. Rendering is immediate and - * uses the current styles appropriate for each geometry in the collection. - * - * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry - * collection. - * @param {ol.Feature} feature Feature. + * @param {ol.render.canvas.StrokeState} strokeState Stroke state. + * @private */ -ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry = - function(geometryCollectionGeometry, feature) { - var geometries = geometryCollectionGeometry.getGeometriesArray(); - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - var geometry = geometries[i]; - var geometryRenderer = - ol.render.canvas.Immediate.GEOMETRY_RENDERERS_[geometry.getType()]; - goog.asserts.assert(goog.isDef(geometryRenderer)); - geometryRenderer.call(this, geometry, feature); +ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = + function(strokeState) { + var replayStrokeState = this.replayStrokeState_; + if (!goog.isNull(replayStrokeState) && + replayStrokeState.lineCap == strokeState.lineCap && + replayStrokeState.lineDash == strokeState.lineDash && + replayStrokeState.lineJoin == strokeState.lineJoin && + replayStrokeState.lineWidth == strokeState.lineWidth && + replayStrokeState.miterLimit == strokeState.miterLimit && + replayStrokeState.strokeStyle == strokeState.strokeStyle) { + return; + } + var setStrokeStyleInstruction = [ + ol.render.canvas.Instruction.SET_STROKE_STYLE, strokeState.strokeStyle, + strokeState.lineWidth, strokeState.lineCap, strokeState.lineJoin, + strokeState.miterLimit, strokeState.lineDash, false + ]; + this.instructions.push(setStrokeStyleInstruction); + this.hitDetectionInstructions.push(setStrokeStyleInstruction); + if (goog.isNull(replayStrokeState)) { + this.replayStrokeState_ = { + lineCap: strokeState.lineCap, + lineDash: strokeState.lineDash, + lineJoin: strokeState.lineJoin, + lineWidth: strokeState.lineWidth, + miterLimit: strokeState.miterLimit, + strokeStyle: strokeState.strokeStyle + }; + } else { + replayStrokeState.lineCap = strokeState.lineCap; + replayStrokeState.lineDash = strokeState.lineDash; + replayStrokeState.lineJoin = strokeState.lineJoin; + replayStrokeState.lineWidth = strokeState.lineWidth; + replayStrokeState.miterLimit = strokeState.miterLimit; + replayStrokeState.strokeStyle = strokeState.strokeStyle; } }; /** - * Render a Point geometry into the canvas. Rendering is immediate and uses - * the current style. - * - * @param {ol.geom.Point} pointGeometry Point geometry. - * @param {ol.Feature} feature Feature. - * @api + * @param {ol.render.canvas.TextState} textState Text state. + * @private */ -ol.render.canvas.Immediate.prototype.drawPointGeometry = - function(pointGeometry, feature) { - var flatCoordinates = pointGeometry.getFlatCoordinates(); - var stride = pointGeometry.getStride(); - if (!goog.isNull(this.image_)) { - this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); +ol.render.canvas.TextReplay.prototype.setReplayTextState_ = + function(textState) { + var replayTextState = this.replayTextState_; + if (!goog.isNull(replayTextState) && + replayTextState.font == textState.font && + replayTextState.textAlign == textState.textAlign && + replayTextState.textBaseline == textState.textBaseline) { + return; } - if (this.text_ !== '') { - this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); + var setTextStyleInstruction = [ol.render.canvas.Instruction.SET_TEXT_STYLE, + textState.font, textState.textAlign, textState.textBaseline]; + this.instructions.push(setTextStyleInstruction); + this.hitDetectionInstructions.push(setTextStyleInstruction); + if (goog.isNull(replayTextState)) { + this.replayTextState_ = { + font: textState.font, + textAlign: textState.textAlign, + textBaseline: textState.textBaseline + }; + } else { + replayTextState.font = textState.font; + replayTextState.textAlign = textState.textAlign; + replayTextState.textBaseline = textState.textBaseline; } }; /** - * Render a MultiPoint geometry into the canvas. Rendering is immediate and - * uses the current style. - * - * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry. - * @param {ol.Feature} feature Feature. - * @api + * @inheritDoc */ -ol.render.canvas.Immediate.prototype.drawMultiPointGeometry = - function(multiPointGeometry, feature) { - var flatCoordinates = multiPointGeometry.getFlatCoordinates(); - var stride = multiPointGeometry.getStride(); - if (!goog.isNull(this.image_)) { - this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride); - } - if (this.text_ !== '') { - this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride); +ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { + if (goog.isNull(textStyle)) { + this.text_ = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (goog.isNull(textFillStyle)) { + this.textFillState_ = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + var fillStyle = ol.color.asString(!goog.isNull(textFillStyleColor) ? + textFillStyleColor : ol.render.canvas.defaultFillStyle); + if (goog.isNull(this.textFillState_)) { + this.textFillState_ = { + fillStyle: fillStyle + }; + } else { + var textFillState = this.textFillState_; + textFillState.fillStyle = fillStyle; + } + } + var textStrokeStyle = textStyle.getStroke(); + if (goog.isNull(textStrokeStyle)) { + this.textStrokeState_ = null; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); + var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); + var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); + var textStrokeStyleWidth = textStrokeStyle.getWidth(); + var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); + var lineCap = goog.isDef(textStrokeStyleLineCap) ? + textStrokeStyleLineCap : ol.render.canvas.defaultLineCap; + var lineDash = goog.isDefAndNotNull(textStrokeStyleLineDash) ? + textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; + var lineJoin = goog.isDef(textStrokeStyleLineJoin) ? + textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin; + var lineWidth = goog.isDef(textStrokeStyleWidth) ? + textStrokeStyleWidth : ol.render.canvas.defaultLineWidth; + var miterLimit = goog.isDef(textStrokeStyleMiterLimit) ? + textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; + var strokeStyle = ol.color.asString(!goog.isNull(textStrokeStyleColor) ? + textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle); + if (goog.isNull(this.textStrokeState_)) { + this.textStrokeState_ = { + lineCap: lineCap, + lineDash: lineDash, + lineJoin: lineJoin, + lineWidth: lineWidth, + miterLimit: miterLimit, + strokeStyle: strokeStyle + }; + } else { + var textStrokeState = this.textStrokeState_; + textStrokeState.lineCap = lineCap; + textStrokeState.lineDash = lineDash; + textStrokeState.lineJoin = lineJoin; + textStrokeState.lineWidth = lineWidth; + textStrokeState.miterLimit = miterLimit; + textStrokeState.strokeStyle = strokeStyle; + } + } + var textFont = textStyle.getFont(); + var textOffsetX = textStyle.getOffsetX(); + var textOffsetY = textStyle.getOffsetY(); + var textRotation = textStyle.getRotation(); + var textScale = textStyle.getScale(); + var textText = textStyle.getText(); + var textTextAlign = textStyle.getTextAlign(); + var textTextBaseline = textStyle.getTextBaseline(); + var font = goog.isDef(textFont) ? + textFont : ol.render.canvas.defaultFont; + var textAlign = goog.isDef(textTextAlign) ? + textTextAlign : ol.render.canvas.defaultTextAlign; + var textBaseline = goog.isDef(textTextBaseline) ? + textTextBaseline : ol.render.canvas.defaultTextBaseline; + if (goog.isNull(this.textState_)) { + this.textState_ = { + font: font, + textAlign: textAlign, + textBaseline: textBaseline + }; + } else { + var textState = this.textState_; + textState.font = font; + textState.textAlign = textAlign; + textState.textBaseline = textBaseline; + } + this.text_ = goog.isDef(textText) ? textText : ''; + this.textOffsetX_ = goog.isDef(textOffsetX) ? textOffsetX : 0; + this.textOffsetY_ = goog.isDef(textOffsetY) ? textOffsetY : 0; + this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0; + this.textScale_ = goog.isDef(textScale) ? textScale : 1; } }; + /** - * Render a LineString into the canvas. Rendering is immediate and uses - * the current style. - * - * @param {ol.geom.LineString} lineStringGeometry Line string geometry. - * @param {ol.Feature} feature Feature. - * @api + * @constructor + * @implements {ol.render.IReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number} resolution Resolution. + * @param {number=} opt_renderBuffer Optional rendering buffer. + * @struct */ -ol.render.canvas.Immediate.prototype.drawLineStringGeometry = - function(lineStringGeometry, feature) { - if (!ol.extent.intersects(this.extent_, lineStringGeometry.getExtent())) { - return; - } - if (!goog.isNull(this.strokeState_)) { - this.setContextStrokeState_(this.strokeState_); - var context = this.context_; - var flatCoordinates = lineStringGeometry.getFlatCoordinates(); - context.beginPath(); - this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length, - lineStringGeometry.getStride(), false); - context.stroke(); - } - if (this.text_ !== '') { - var flatMidpoint = lineStringGeometry.getFlatMidpoint(); - this.drawText_(flatMidpoint, 0, 2, 2); - } +ol.render.canvas.ReplayGroup = function( + tolerance, maxExtent, resolution, opt_renderBuffer) { + + /** + * @private + * @type {number} + */ + this.tolerance_ = tolerance; + + /** + * @private + * @type {ol.Extent} + */ + this.maxExtent_ = maxExtent; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @private + * @type {number|undefined} + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * @private + * @type {Object.<string, + * Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} + */ + this.replaysByZIndex_ = {}; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1); + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.hitDetectionTransform_ = goog.vec.Mat4.createNumber(); + }; /** - * Render a MultiLineString geometry into the canvas. Rendering is immediate - * and uses the current style. - * - * @param {ol.geom.MultiLineString} multiLineStringGeometry - * MultiLineString geometry. - * @param {ol.Feature} feature Feature. - * @api + * FIXME empty description for jsdoc */ -ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry = - function(multiLineStringGeometry, feature) { - var geometryExtent = multiLineStringGeometry.getExtent(); - if (!ol.extent.intersects(this.extent_, geometryExtent)) { - return; - } - if (!goog.isNull(this.strokeState_)) { - this.setContextStrokeState_(this.strokeState_); - var context = this.context_; - var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); - var offset = 0; - var ends = multiLineStringGeometry.getEnds(); - var stride = multiLineStringGeometry.getStride(); - context.beginPath(); - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - offset = this.moveToLineTo_( - flatCoordinates, offset, ends[i], stride, false); +ol.render.canvas.ReplayGroup.prototype.finish = function() { + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(); } - context.stroke(); - } - if (this.text_ !== '') { - var flatMidpoints = multiLineStringGeometry.getFlatMidpoints(); - this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2); } }; /** - * Render a Polygon geometry into the canvas. Rendering is immediate and uses - * the current style. - * - * @param {ol.geom.Polygon} polygonGeometry Polygon geometry. - * @param {ol.Feature} feature Feature. - * @api + * @param {ol.Coordinate} coordinate Coordinate. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip + * @param {function(ol.Feature): T} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T */ -ol.render.canvas.Immediate.prototype.drawPolygonGeometry = - function(polygonGeometry, feature) { - if (!ol.extent.intersects(this.extent_, polygonGeometry.getExtent())) { - return; - } - if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) { - if (!goog.isNull(this.fillState_)) { - this.setContextFillState_(this.fillState_); - } - if (!goog.isNull(this.strokeState_)) { - this.setContextStrokeState_(this.strokeState_); - } - var context = this.context_; - context.beginPath(); - this.drawRings_(polygonGeometry.getOrientedFlatCoordinates(), - 0, polygonGeometry.getEnds(), polygonGeometry.getStride()); - if (!goog.isNull(this.fillState_)) { - context.fill(); - } - if (!goog.isNull(this.strokeState_)) { - context.stroke(); - } - } - if (this.text_ !== '') { - var flatInteriorPoint = polygonGeometry.getFlatInteriorPoint(); - this.drawText_(flatInteriorPoint, 0, 2, 2); +ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, skippedFeaturesHash, callback) { + + var transform = this.hitDetectionTransform_; + ol.vec.Mat4.makeTransform2D(transform, 0.5, 0.5, + 1 / resolution, -1 / resolution, -rotation, + -coordinate[0], -coordinate[1]); + + var context = this.hitDetectionContext_; + context.clearRect(0, 0, 1, 1); + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (goog.isDef(this.renderBuffer_)) { + hitExtent = ol.extent.createEmpty(); + ol.extent.extendCoordinate(hitExtent, coordinate); + ol.extent.buffer(hitExtent, resolution * this.renderBuffer_, hitExtent); } + + return this.replayHitDetection_(context, transform, rotation, + skippedFeaturesHash, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = context.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + context.clearRect(0, 0, 1, 1); + } + }, hitExtent); }; /** - * Render MultiPolygon geometry into the canvas. Rendering is immediate and - * uses the current style. - * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry. - * @param {ol.Feature} feature Feature. - * @api - */ -ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry = - function(multiPolygonGeometry, feature) { - if (!ol.extent.intersects(this.extent_, multiPolygonGeometry.getExtent())) { - return; - } - if (!goog.isNull(this.strokeState_) || !goog.isNull(this.fillState_)) { - if (!goog.isNull(this.fillState_)) { - this.setContextFillState_(this.fillState_); - } - if (!goog.isNull(this.strokeState_)) { - this.setContextStrokeState_(this.strokeState_); - } - var context = this.context_; - var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); - var offset = 0; - var endss = multiPolygonGeometry.getEndss(); - var stride = multiPolygonGeometry.getStride(); - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - context.beginPath(); - offset = this.drawRings_(flatCoordinates, offset, ends, stride); - if (!goog.isNull(this.fillState_)) { - context.fill(); - } - if (!goog.isNull(this.strokeState_)) { - context.stroke(); - } - } + * @inheritDoc + */ +ol.render.canvas.ReplayGroup.prototype.getReplay = + function(zIndex, replayType) { + var zIndexKey = goog.isDef(zIndex) ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (!goog.isDef(replays)) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; } - if (this.text_ !== '') { - var flatInteriorPoints = multiPolygonGeometry.getFlatInteriorPoints(); - this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2); + var replay = replays[replayType]; + if (!goog.isDef(replay)) { + var Constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType]; + goog.asserts.assert(goog.isDef(Constructor), + replayType + + ' constructor missing from ol.render.canvas.BATCH_CONSTRUCTORS_'); + replay = new Constructor(this.tolerance_, this.maxExtent_, + this.resolution_); + replays[replayType] = replay; } + return replay; }; /** * @inheritDoc */ -ol.render.canvas.Immediate.prototype.drawText = goog.abstractMethod; +ol.render.canvas.ReplayGroup.prototype.isEmpty = function() { + return goog.object.isEmpty(this.replaysByZIndex_); +}; /** - * FIXME: empty description for jsdoc + * @param {CanvasRenderingContext2D} context Context. + * @param {number} pixelRatio Pixel ratio. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip */ -ol.render.canvas.Immediate.prototype.flush = function() { +ol.render.canvas.ReplayGroup.prototype.replay = function( + context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { + /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number); + var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number); goog.array.sort(zs); - var i, ii, callbacks, j, jj; - for (i = 0, ii = zs.length; i < ii; ++i) { - callbacks = this.callbacksByZIndex_[zs[i].toString()]; - for (j = 0, jj = callbacks.length; j < jj; ++j) { - callbacks[j](this); - } - } -}; + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning + var maxExtent = this.maxExtent_; + var minX = maxExtent[0]; + var minY = maxExtent[1]; + var maxX = maxExtent[2]; + var maxY = maxExtent[3]; + var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; + ol.geom.flat.transform.transform2D( + flatClipCoords, 0, 8, 2, transform, flatClipCoords); + context.save(); + context.beginPath(); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.closePath(); + context.clip(); -/** - * @param {ol.render.canvas.FillState} fillState Fill state. - * @private - */ -ol.render.canvas.Immediate.prototype.setContextFillState_ = - function(fillState) { - var context = this.context_; - var contextFillState = this.contextFillState_; - if (goog.isNull(contextFillState)) { - context.fillStyle = fillState.fillStyle; - this.contextFillState_ = { - fillStyle: fillState.fillStyle - }; - } else { - if (contextFillState.fillStyle != fillState.fillStyle) { - contextFillState.fillStyle = context.fillStyle = fillState.fillStyle; + var i, ii, j, jj, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = 0, jj = ol.render.REPLAY_ORDER.length; j < jj; ++j) { + replay = replays[ol.render.REPLAY_ORDER[j]]; + if (goog.isDef(replay)) { + replay.replay(context, pixelRatio, transform, viewRotation, + skippedFeaturesHash); + } } } + + context.restore(); }; /** - * @param {ol.render.canvas.StrokeState} strokeState Stroke state. * @private + * @param {CanvasRenderingContext2D} context Context. + * @param {goog.vec.Mat4.Number} transform Transform. + * @param {number} viewRotation View rotation. + * @param {Object} skippedFeaturesHash Ids of features to skip + * @param {function(ol.Feature): T} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Only check features that intersect this + * extent. + * @return {T|undefined} Callback result. + * @template T */ -ol.render.canvas.Immediate.prototype.setContextStrokeState_ = - function(strokeState) { - var context = this.context_; - var contextStrokeState = this.contextStrokeState_; - if (goog.isNull(contextStrokeState)) { - context.lineCap = strokeState.lineCap; - if (ol.has.CANVAS_LINE_DASH) { - context.setLineDash(strokeState.lineDash); - } - context.lineJoin = strokeState.lineJoin; - context.lineWidth = strokeState.lineWidth; - context.miterLimit = strokeState.miterLimit; - context.strokeStyle = strokeState.strokeStyle; - this.contextStrokeState_ = { - lineCap: strokeState.lineCap, - lineDash: strokeState.lineDash, - lineJoin: strokeState.lineJoin, - lineWidth: strokeState.lineWidth, - miterLimit: strokeState.miterLimit, - strokeStyle: strokeState.strokeStyle - }; - } else { - if (contextStrokeState.lineCap != strokeState.lineCap) { - contextStrokeState.lineCap = context.lineCap = strokeState.lineCap; - } - if (ol.has.CANVAS_LINE_DASH) { - if (!goog.array.equals( - contextStrokeState.lineDash, strokeState.lineDash)) { - context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash); +ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( + context, transform, viewRotation, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + /** @type {Array.<number>} */ + var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number); + goog.array.sort(zs, function(a, b) { return b - a; }); + + var i, ii, j, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = ol.render.REPLAY_ORDER.length - 1; j >= 0; --j) { + replay = replays[ol.render.REPLAY_ORDER[j]]; + if (goog.isDef(replay)) { + result = replay.replayHitDetection(context, transform, viewRotation, + skippedFeaturesHash, featureCallback, opt_hitExtent); + if (result) { + return result; + } } } - if (contextStrokeState.lineJoin != strokeState.lineJoin) { - contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin; - } - if (contextStrokeState.lineWidth != strokeState.lineWidth) { - contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth; - } - if (contextStrokeState.miterLimit != strokeState.miterLimit) { - contextStrokeState.miterLimit = context.miterLimit = - strokeState.miterLimit; - } - if (contextStrokeState.strokeStyle != strokeState.strokeStyle) { - contextStrokeState.strokeStyle = context.strokeStyle = - strokeState.strokeStyle; - } } + return undefined; }; /** - * @param {ol.render.canvas.TextState} textState Text state. + * @const * @private + * @type {Object.<ol.render.ReplayType, + * function(new: ol.render.canvas.Replay, number, ol.Extent, + * number)>} */ -ol.render.canvas.Immediate.prototype.setContextTextState_ = - function(textState) { - var context = this.context_; - var contextTextState = this.contextTextState_; - if (goog.isNull(contextTextState)) { - context.font = textState.font; - context.textAlign = textState.textAlign; - context.textBaseline = textState.textBaseline; - this.contextTextState_ = { - font: textState.font, - textAlign: textState.textAlign, - textBaseline: textState.textBaseline - }; - } else { - if (contextTextState.font != textState.font) { - contextTextState.font = context.font = textState.font; - } - if (contextTextState.textAlign != textState.textAlign) { - contextTextState.textAlign = context.textAlign = textState.textAlign; - } - if (contextTextState.textBaseline != textState.textBaseline) { - contextTextState.textBaseline = context.textBaseline = - textState.textBaseline; - } - } +ol.render.canvas.BATCH_CONSTRUCTORS_ = { + 'Image': ol.render.canvas.ImageReplay, + 'LineString': ol.render.canvas.LineStringReplay, + 'Polygon': ol.render.canvas.PolygonReplay, + 'Text': ol.render.canvas.TextReplay }; +goog.provide('ol.geom.Circle'); + +goog.require('goog.asserts'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.proj'); + + /** - * Set the fill and stroke style for subsequent draw operations. To clear - * either fill or stroke styles, pass null for the appropriate parameter. + * @classdesc + * Circle geometry. * - * @param {ol.style.Fill} fillStyle Fill style. - * @param {ol.style.Stroke} strokeStyle Stroke style. + * @constructor + * @extends {ol.geom.SimpleGeometry} + * @param {ol.Coordinate} center Center. + * @param {number=} opt_radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. * @api */ -ol.render.canvas.Immediate.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { - if (goog.isNull(fillStyle)) { - this.fillState_ = null; - } else { - var fillStyleColor = fillStyle.getColor(); - this.fillState_ = { - fillStyle: ol.color.asString(!goog.isNull(fillStyleColor) ? - fillStyleColor : ol.render.canvas.defaultFillStyle) - }; - } - if (goog.isNull(strokeStyle)) { - this.strokeState_ = null; - } else { - var strokeStyleColor = strokeStyle.getColor(); - var strokeStyleLineCap = strokeStyle.getLineCap(); - var strokeStyleLineDash = strokeStyle.getLineDash(); - var strokeStyleLineJoin = strokeStyle.getLineJoin(); - var strokeStyleWidth = strokeStyle.getWidth(); - var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); - this.strokeState_ = { - lineCap: goog.isDef(strokeStyleLineCap) ? - strokeStyleLineCap : ol.render.canvas.defaultLineCap, - lineDash: goog.isDefAndNotNull(strokeStyleLineDash) ? - strokeStyleLineDash : ol.render.canvas.defaultLineDash, - lineJoin: goog.isDef(strokeStyleLineJoin) ? - strokeStyleLineJoin : ol.render.canvas.defaultLineJoin, - lineWidth: this.pixelRatio_ * (goog.isDef(strokeStyleWidth) ? - strokeStyleWidth : ol.render.canvas.defaultLineWidth), - miterLimit: goog.isDef(strokeStyleMiterLimit) ? - strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, - strokeStyle: ol.color.asString(!goog.isNull(strokeStyleColor) ? - strokeStyleColor : ol.render.canvas.defaultStrokeStyle) - }; - } +ol.geom.Circle = function(center, opt_radius, opt_layout) { + goog.base(this); + var radius = goog.isDef(opt_radius) ? opt_radius : 0; + this.setCenterAndRadius(center, radius, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); }; +goog.inherits(ol.geom.Circle, ol.geom.SimpleGeometry); /** - * Set the image style for subsequent draw operations. Pass null to remove - * the image style. - * - * @param {ol.style.Image} imageStyle Image style. + * Make a complete copy of the geometry. + * @return {!ol.geom.Circle} Clone. * @api */ -ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) { - if (goog.isNull(imageStyle)) { - this.image_ = null; - } else { - var imageAnchor = imageStyle.getAnchor(); - // FIXME pixel ratio - var imageImage = imageStyle.getImage(1); - var imageOrigin = imageStyle.getOrigin(); - var imageSize = imageStyle.getSize(); - goog.asserts.assert(!goog.isNull(imageAnchor)); - goog.asserts.assert(!goog.isNull(imageImage)); - goog.asserts.assert(!goog.isNull(imageOrigin)); - goog.asserts.assert(!goog.isNull(imageSize)); - this.imageAnchorX_ = imageAnchor[0]; - this.imageAnchorY_ = imageAnchor[1]; - this.imageHeight_ = imageSize[1]; - this.image_ = imageImage; - this.imageOpacity_ = imageStyle.getOpacity(); - this.imageOriginX_ = imageOrigin[0]; - this.imageOriginY_ = imageOrigin[1]; - this.imageRotateWithView_ = imageStyle.getRotateWithView(); - this.imageRotation_ = imageStyle.getRotation(); - this.imageScale_ = imageStyle.getScale(); - this.imageSnapToPixel_ = imageStyle.getSnapToPixel(); - this.imageWidth_ = imageSize[0]; - } +ol.geom.Circle.prototype.clone = function() { + var circle = new ol.geom.Circle(null); + circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return circle; }; /** - * Set the text style for subsequent draw operations. Pass null to - * remove the text style. - * - * @param {ol.style.Text} textStyle Text style. - * @api + * @inheritDoc */ -ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) { - if (goog.isNull(textStyle)) { - this.text_ = ''; - } else { - var textFillStyle = textStyle.getFill(); - if (goog.isNull(textFillStyle)) { - this.textFillState_ = null; - } else { - var textFillStyleColor = textFillStyle.getColor(); - this.textFillState_ = { - fillStyle: ol.color.asString(!goog.isNull(textFillStyleColor) ? - textFillStyleColor : ol.render.canvas.defaultFillStyle) - }; - } - var textStrokeStyle = textStyle.getStroke(); - if (goog.isNull(textStrokeStyle)) { - this.textStrokeState_ = null; +ol.geom.Circle.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + var squaredDistance = dx * dx + dy * dy; + if (squaredDistance < minSquaredDistance) { + var i; + if (squaredDistance === 0) { + for (i = 0; i < this.stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } } else { - var textStrokeStyleColor = textStrokeStyle.getColor(); - var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); - var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); - var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); - var textStrokeStyleWidth = textStrokeStyle.getWidth(); - var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); - this.textStrokeState_ = { - lineCap: goog.isDef(textStrokeStyleLineCap) ? - textStrokeStyleLineCap : ol.render.canvas.defaultLineCap, - lineDash: goog.isDefAndNotNull(textStrokeStyleLineDash) ? - textStrokeStyleLineDash : ol.render.canvas.defaultLineDash, - lineJoin: goog.isDef(textStrokeStyleLineJoin) ? - textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin, - lineWidth: goog.isDef(textStrokeStyleWidth) ? - textStrokeStyleWidth : ol.render.canvas.defaultLineWidth, - miterLimit: goog.isDef(textStrokeStyleMiterLimit) ? - textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit, - strokeStyle: ol.color.asString(!goog.isNull(textStrokeStyleColor) ? - textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle) - }; + var delta = this.getRadius() / Math.sqrt(squaredDistance); + closestPoint[0] = flatCoordinates[0] + delta * dx; + closestPoint[1] = flatCoordinates[1] + delta * dy; + for (i = 2; i < this.stride; ++i) { + closestPoint[i] = flatCoordinates[i]; + } } - var textFont = textStyle.getFont(); - var textOffsetX = textStyle.getOffsetX(); - var textOffsetY = textStyle.getOffsetY(); - var textRotation = textStyle.getRotation(); - var textScale = textStyle.getScale(); - var textText = textStyle.getText(); - var textTextAlign = textStyle.getTextAlign(); - var textTextBaseline = textStyle.getTextBaseline(); - this.textState_ = { - font: goog.isDef(textFont) ? - textFont : ol.render.canvas.defaultFont, - textAlign: goog.isDef(textTextAlign) ? - textTextAlign : ol.render.canvas.defaultTextAlign, - textBaseline: goog.isDef(textTextBaseline) ? - textTextBaseline : ol.render.canvas.defaultTextBaseline - }; - this.text_ = goog.isDef(textText) ? textText : ''; - this.textOffsetX_ = - goog.isDef(textOffsetX) ? (this.pixelRatio_ * textOffsetX) : 0; - this.textOffsetY_ = - goog.isDef(textOffsetY) ? (this.pixelRatio_ * textOffsetY) : 0; - this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0; - this.textScale_ = this.pixelRatio_ * (goog.isDef(textScale) ? - textScale : 1); + closestPoint.length = this.stride; + return squaredDistance; + } else { + return minSquaredDistance; } }; /** - * @const - * @private - * @type {Object.<ol.geom.GeometryType, - * function(this: ol.render.canvas.Immediate, ol.geom.Geometry, - * Object)>} + * @inheritDoc */ -ol.render.canvas.Immediate.GEOMETRY_RENDERERS_ = { - 'Point': ol.render.canvas.Immediate.prototype.drawPointGeometry, - 'LineString': ol.render.canvas.Immediate.prototype.drawLineStringGeometry, - 'Polygon': ol.render.canvas.Immediate.prototype.drawPolygonGeometry, - 'MultiPoint': ol.render.canvas.Immediate.prototype.drawMultiPointGeometry, - 'MultiLineString': - ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry, - 'MultiPolygon': ol.render.canvas.Immediate.prototype.drawMultiPolygonGeometry, - 'GeometryCollection': - ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry, - 'Circle': ol.render.canvas.Immediate.prototype.drawCircleGeometry +ol.geom.Circle.prototype.containsXY = function(x, y) { + var flatCoordinates = this.flatCoordinates; + var dx = x - flatCoordinates[0]; + var dy = y - flatCoordinates[1]; + return dx * dx + dy * dy <= this.getRadiusSquared_(); }; -goog.provide('ol.render.IReplayGroup'); -goog.require('goog.functions'); -goog.require('ol.render.IVectorContext'); +/** + * Return the center of the circle as {@link ol.Coordinate coordinate}. + * @return {ol.Coordinate} Center. + * @api + */ +ol.geom.Circle.prototype.getCenter = function() { + return this.flatCoordinates.slice(0, this.stride); +}; /** - * @enum {string} + * @inheritDoc */ -ol.render.ReplayType = { - IMAGE: 'Image', - LINE_STRING: 'LineString', - POLYGON: 'Polygon', - TEXT: 'Text' +ol.geom.Circle.prototype.computeExtent = function(extent) { + var flatCoordinates = this.flatCoordinates; + var radius = flatCoordinates[this.stride] - flatCoordinates[0]; + return ol.extent.createOrUpdate( + flatCoordinates[0] - radius, flatCoordinates[1] - radius, + flatCoordinates[0] + radius, flatCoordinates[1] + radius, + extent); }; /** - * @const - * @type {Array.<ol.render.ReplayType>} + * Return the radius of the circle. + * @return {number} Radius. + * @api */ -ol.render.REPLAY_ORDER = [ - ol.render.ReplayType.POLYGON, - ol.render.ReplayType.LINE_STRING, - ol.render.ReplayType.IMAGE, - ol.render.ReplayType.TEXT -]; - +ol.geom.Circle.prototype.getRadius = function() { + return Math.sqrt(this.getRadiusSquared_()); +}; /** - * @interface + * @private + * @return {number} Radius squared. */ -ol.render.IReplayGroup = function() { +ol.geom.Circle.prototype.getRadiusSquared_ = function() { + var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0]; + var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1]; + return dx * dx + dy * dy; }; /** - * @param {number|undefined} zIndex Z index. - * @param {ol.render.ReplayType} replayType Replay type. - * @return {ol.render.IVectorContext} Replay. + * @inheritDoc + * @api */ -ol.render.IReplayGroup.prototype.getReplay = function(zIndex, replayType) { +ol.geom.Circle.prototype.getType = function() { + return ol.geom.GeometryType.CIRCLE; }; /** - * @return {boolean} Is empty. + * @inheritDoc + * @api stable */ -ol.render.IReplayGroup.prototype.isEmpty = function() { +ol.geom.Circle.prototype.intersectsExtent = function(extent) { + var circleExtent = this.getExtent(); + if (ol.extent.intersects(extent, circleExtent)) { + var center = this.getCenter(); + + if (extent[0] <= center[0] && extent[2] >= center[0]) { + return true; + } + if (extent[1] <= center[1] && extent[3] >= center[1]) { + return true; + } + + return ol.extent.forEachCorner(extent, this.containsCoordinate, this); + } + return false; + }; -// FIXME add option to apply snapToPixel to all coordinates? -// FIXME can eliminate empty set styles and strokes (when all geoms skipped) -goog.provide('ol.render.canvas.Replay'); -goog.provide('ol.render.canvas.ReplayGroup'); +/** + * Set the center of the circle as {@link ol.Coordinate coordinate}. + * @param {ol.Coordinate} center Center. + * @api + */ +ol.geom.Circle.prototype.setCenter = function(center) { + var stride = this.stride; + goog.asserts.assert(center.length == stride, + 'center array length should match stride'); + var radius = this.flatCoordinates[stride] - this.flatCoordinates[0]; + var flatCoordinates = center.slice(); + flatCoordinates[stride] = flatCoordinates[0] + radius; + var i; + for (i = 1; i < stride; ++i) { + flatCoordinates[stride + i] = center[i]; + } + this.setFlatCoordinates(this.layout, flatCoordinates); +}; -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('goog.vec.Mat4'); -goog.require('ol.array'); -goog.require('ol.color'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.extent.Relationship'); -goog.require('ol.geom.flat.simplify'); -goog.require('ol.geom.flat.transform'); -goog.require('ol.has'); -goog.require('ol.render.IReplayGroup'); -goog.require('ol.render.IVectorContext'); -goog.require('ol.render.canvas'); -goog.require('ol.vec.Mat4'); + +/** + * Set the center (as {@link ol.Coordinate coordinate}) and the radius (as + * number) of the circle. + * @param {ol.Coordinate} center Center. + * @param {number} radius Radius. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api + */ +ol.geom.Circle.prototype.setCenterAndRadius = + function(center, radius, opt_layout) { + if (goog.isNull(center)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, center, 0); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + /** @type {Array.<number>} */ + var flatCoordinates = this.flatCoordinates; + var offset = ol.geom.flat.deflate.coordinate( + flatCoordinates, 0, center, this.stride); + flatCoordinates[offset++] = flatCoordinates[0] + radius; + var i, ii; + for (i = 1, ii = this.stride; i < ii; ++i) { + flatCoordinates[offset++] = flatCoordinates[i]; + } + flatCoordinates.length = offset; + this.changed(); + } +}; /** - * @enum {number} + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. */ -ol.render.canvas.Instruction = { - BEGIN_GEOMETRY: 0, - BEGIN_PATH: 1, - CIRCLE: 2, - CLOSE_PATH: 3, - DRAW_IMAGE: 4, - DRAW_TEXT: 5, - END_GEOMETRY: 6, - FILL: 7, - MOVE_TO_LINE_TO: 8, - SET_FILL_STYLE: 9, - SET_STROKE_STYLE: 10, - SET_TEXT_STYLE: 11, - STROKE: 12 +ol.geom.Circle.prototype.setFlatCoordinates = + function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); }; - /** - * @constructor - * @implements {ol.render.IVectorContext} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Maximum extent. - * @param {number} resolution Resolution. - * @protected - * @struct + * Set the radius of the circle. The radius is in the units of the projection. + * @param {number} radius Radius. + * @api */ -ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { - - /** - * @protected - * @type {number} - */ - this.tolerance = tolerance; - - /** - * @protected - * @type {ol.Extent} - */ - this.maxExtent = maxExtent; - - /** - * @protected - * @type {number} - */ - this.maxLineWidth = 0; +ol.geom.Circle.prototype.setRadius = function(radius) { + goog.asserts.assert(!goog.isNull(this.flatCoordinates), + 'this.flatCoordinates cannot be null'); + this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius; + this.changed(); +}; - /** - * @protected - * @type {number} - */ - this.resolution = resolution; - /** - * @private - * @type {Array.<*>} - */ - this.beginGeometryInstruction1_ = null; +/** + * Transform each coordinate of the circle from one coordinate reference system + * to another. The geometry is modified in place. + * If you do not want the geometry modified in place, first clone() it and + * then use this function on the clone. + * + * Internally a circle is currently represented by two points: the center of + * the circle `[cx, cy]`, and the point to the right of the circle + * `[cx + r, cy]`. This `transform` function just transforms these two points. + * So the resulting geometry is also a circle, and that circle does not + * correspond to the shape that would be obtained by transforming every point + * of the original circle. + * + * @param {ol.proj.ProjectionLike} source The current projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @param {ol.proj.ProjectionLike} destination The desired projection. Can be a + * string identifier or a {@link ol.proj.Projection} object. + * @return {ol.geom.Circle} This geometry. Note that original geometry is + * modified in place. + * @function + * @api stable + */ +ol.geom.Circle.prototype.transform; - /** - * @private - * @type {Array.<*>} - */ - this.beginGeometryInstruction2_ = null; +goog.provide('ol.geom.GeometryCollection'); - /** - * @protected - * @type {Array.<*>} - */ - this.instructions = []; +goog.require('goog.array'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.object'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryType'); - /** - * @protected - * @type {Array.<number>} - */ - this.coordinates = []; - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.renderedTransform_ = goog.vec.Mat4.createNumber(); - /** - * @protected - * @type {Array.<*>} - */ - this.hitDetectionInstructions = []; +/** + * @classdesc + * An array of {@link ol.geom.Geometry} objects. + * + * @constructor + * @extends {ol.geom.Geometry} + * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries. + * @api stable + */ +ol.geom.GeometryCollection = function(opt_geometries) { - /** - * @private - * @type {Array.<number>} - */ - this.pixelCoordinates_ = []; + goog.base(this); /** * @private - * @type {!goog.vec.Mat4.Number} + * @type {Array.<ol.geom.Geometry>} */ - this.tmpLocalTransform_ = goog.vec.Mat4.createNumber(); + this.geometries_ = goog.isDef(opt_geometries) ? opt_geometries : null; + this.listenGeometriesChange_(); }; +goog.inherits(ol.geom.GeometryCollection, ol.geom.Geometry); /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {boolean} close Close. - * @protected - * @return {number} My end. + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * @private + * @return {Array.<ol.geom.Geometry>} Cloned geometries. */ -ol.render.canvas.Replay.prototype.appendFlatCoordinates = - function(flatCoordinates, offset, end, stride, close) { - - var myEnd = this.coordinates.length; - var extent = this.getBufferedMaxExtent(); - var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - var nextCoord = [NaN, NaN]; - var skipped = true; - - var i, lastRel, nextRel; - for (i = offset + stride; i < end; i += stride) { - nextCoord[0] = flatCoordinates[i]; - nextCoord[1] = flatCoordinates[i + 1]; - nextRel = ol.extent.coordinateRelationship(extent, nextCoord); - if (nextRel !== lastRel) { - if (skipped) { - this.coordinates[myEnd++] = lastCoord[0]; - this.coordinates[myEnd++] = lastCoord[1]; - } - this.coordinates[myEnd++] = nextCoord[0]; - this.coordinates[myEnd++] = nextCoord[1]; - skipped = false; - } else if (nextRel === ol.extent.Relationship.INTERSECTING) { - this.coordinates[myEnd++] = nextCoord[0]; - this.coordinates[myEnd++] = nextCoord[1]; - skipped = false; - } else { - skipped = true; - } - lastCoord[0] = nextCoord[0]; - lastCoord[1] = nextCoord[1]; - lastRel = nextRel; - } - - // handle case where there is only one point to append - if (i === offset + stride) { - this.coordinates[myEnd++] = lastCoord[0]; - this.coordinates[myEnd++] = lastCoord[1]; - } - - if (close) { - this.coordinates[myEnd++] = flatCoordinates[offset]; - this.coordinates[myEnd++] = flatCoordinates[offset + 1]; +ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) { + var clonedGeometries = []; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + clonedGeometries.push(geometries[i].clone()); } - return myEnd; + return clonedGeometries; }; /** - * @protected - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.Feature} feature Feature. + * @private */ -ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { - this.beginGeometryInstruction1_ = - [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; - this.instructions.push(this.beginGeometryInstruction1_); - this.beginGeometryInstruction2_ = - [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0]; - this.hitDetectionInstructions.push(this.beginGeometryInstruction2_); +ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() { + var i, ii; + if (goog.isNull(this.geometries_)) { + return; + } + for (i = 0, ii = this.geometries_.length; i < ii; ++i) { + goog.events.unlisten( + this.geometries_[i], goog.events.EventType.CHANGE, + this.changed, false, this); + } }; /** * @private - * @param {CanvasRenderingContext2D} context Context. - * @param {number} pixelRatio Pixel ratio. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {Array.<*>} instructions Instructions array. - * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T */ -ol.render.canvas.Replay.prototype.replay_ = function( - context, pixelRatio, transform, viewRotation, skippedFeaturesHash, - instructions, featureCallback) { - /** @type {Array.<number>} */ - var pixelCoordinates; - if (ol.vec.Mat4.equals2D(transform, this.renderedTransform_)) { - pixelCoordinates = this.pixelCoordinates_; - } else { - pixelCoordinates = ol.geom.flat.transform.transform2D( - this.coordinates, 0, this.coordinates.length, 2, - transform, this.pixelCoordinates_); - goog.vec.Mat4.setFromArray(this.renderedTransform_, transform); - goog.asserts.assert(pixelCoordinates === this.pixelCoordinates_); +ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() { + var i, ii; + if (goog.isNull(this.geometries_)) { + return; } - var i = 0; // instruction index - var ii = instructions.length; // end of instructions - var d = 0; // data index - var dd; // end of per-instruction data - var localTransform = this.tmpLocalTransform_; - while (i < ii) { - var instruction = instructions[i]; - var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); - var feature, fill, stroke, text, x, y; - switch (type) { - case ol.render.canvas.Instruction.BEGIN_GEOMETRY: - feature = /** @type {ol.Feature} */ (instruction[1]); - var featureUid = goog.getUid(feature).toString(); - if (!goog.isDef(goog.object.get(skippedFeaturesHash, featureUid))) { - ++i; - } else { - i = /** @type {number} */ (instruction[2]); - } - break; - case ol.render.canvas.Instruction.BEGIN_PATH: - context.beginPath(); - ++i; - break; - case ol.render.canvas.Instruction.CIRCLE: - goog.asserts.assert(goog.isNumber(instruction[1])); - d = /** @type {number} */ (instruction[1]); - var x1 = pixelCoordinates[d]; - var y1 = pixelCoordinates[d + 1]; - var x2 = pixelCoordinates[d + 2]; - var y2 = pixelCoordinates[d + 3]; - var dx = x2 - x1; - var dy = y2 - y1; - var r = Math.sqrt(dx * dx + dy * dy); - context.arc(x1, y1, r, 0, 2 * Math.PI, true); - ++i; - break; - case ol.render.canvas.Instruction.CLOSE_PATH: - context.closePath(); - ++i; - break; - case ol.render.canvas.Instruction.DRAW_IMAGE: - goog.asserts.assert(goog.isNumber(instruction[1])); - d = /** @type {number} */ (instruction[1]); - goog.asserts.assert(goog.isNumber(instruction[2])); - dd = /** @type {number} */ (instruction[2]); - var image = /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */ - (instruction[3]); - // Remaining arguments in DRAW_IMAGE are in alphabetical order - var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio; - var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio; - var height = /** @type {number} */ (instruction[6]); - var opacity = /** @type {number} */ (instruction[7]); - var originX = /** @type {number} */ (instruction[8]); - var originY = /** @type {number} */ (instruction[9]); - var rotateWithView = /** @type {boolean} */ (instruction[10]); - var rotation = /** @type {number} */ (instruction[11]); - var scale = /** @type {number} */ (instruction[12]); - var snapToPixel = /** @type {boolean} */ (instruction[13]); - var width = /** @type {number} */ (instruction[14]); - if (rotateWithView) { - rotation += viewRotation; - } - for (; d < dd; d += 2) { - x = pixelCoordinates[d] - anchorX; - y = pixelCoordinates[d + 1] - anchorY; - if (snapToPixel) { - x = (x + 0.5) | 0; - y = (y + 0.5) | 0; - } - if (scale != 1 || rotation !== 0) { - var centerX = x + anchorX; - var centerY = y + anchorY; - ol.vec.Mat4.makeTransform2D( - localTransform, centerX, centerY, scale, scale, - rotation, -centerX, -centerY); - context.setTransform( - goog.vec.Mat4.getElement(localTransform, 0, 0), - goog.vec.Mat4.getElement(localTransform, 1, 0), - goog.vec.Mat4.getElement(localTransform, 0, 1), - goog.vec.Mat4.getElement(localTransform, 1, 1), - goog.vec.Mat4.getElement(localTransform, 0, 3), - goog.vec.Mat4.getElement(localTransform, 1, 3)); - } - var alpha = context.globalAlpha; - if (opacity != 1) { - context.globalAlpha = alpha * opacity; - } - - context.drawImage(image, originX, originY, width, height, - x, y, width * pixelRatio, height * pixelRatio); - - if (opacity != 1) { - context.globalAlpha = alpha; - } - if (scale != 1 || rotation !== 0) { - context.setTransform(1, 0, 0, 1, 0, 0); - } - } - ++i; - break; - case ol.render.canvas.Instruction.DRAW_TEXT: - goog.asserts.assert(goog.isNumber(instruction[1])); - d = /** @type {number} */ (instruction[1]); - goog.asserts.assert(goog.isNumber(instruction[2])); - dd = /** @type {number} */ (instruction[2]); - goog.asserts.assert(goog.isString(instruction[3])); - text = /** @type {string} */ (instruction[3]); - goog.asserts.assert(goog.isNumber(instruction[4])); - var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio; - goog.asserts.assert(goog.isNumber(instruction[5])); - var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio; - goog.asserts.assert(goog.isNumber(instruction[6])); - rotation = /** @type {number} */ (instruction[6]); - goog.asserts.assert(goog.isNumber(instruction[7])); - scale = /** @type {number} */ (instruction[7]) * pixelRatio; - goog.asserts.assert(goog.isBoolean(instruction[8])); - fill = /** @type {boolean} */ (instruction[8]); - goog.asserts.assert(goog.isBoolean(instruction[9])); - stroke = /** @type {boolean} */ (instruction[9]); - for (; d < dd; d += 2) { - x = pixelCoordinates[d] + offsetX; - y = pixelCoordinates[d + 1] + offsetY; - if (scale != 1 || rotation !== 0) { - ol.vec.Mat4.makeTransform2D( - localTransform, x, y, scale, scale, rotation, -x, -y); - context.setTransform( - goog.vec.Mat4.getElement(localTransform, 0, 0), - goog.vec.Mat4.getElement(localTransform, 1, 0), - goog.vec.Mat4.getElement(localTransform, 0, 1), - goog.vec.Mat4.getElement(localTransform, 1, 1), - goog.vec.Mat4.getElement(localTransform, 0, 3), - goog.vec.Mat4.getElement(localTransform, 1, 3)); - } - if (stroke) { - context.strokeText(text, x, y); - } - if (fill) { - context.fillText(text, x, y); - } - if (scale != 1 || rotation !== 0) { - context.setTransform(1, 0, 0, 1, 0, 0); - } - } - ++i; - break; - case ol.render.canvas.Instruction.END_GEOMETRY: - if (goog.isDef(featureCallback)) { - feature = /** @type {ol.Feature} */ (instruction[1]); - var result = featureCallback(feature); - if (result) { - return result; - } - } - ++i; - break; - case ol.render.canvas.Instruction.FILL: - context.fill(); - ++i; - break; - case ol.render.canvas.Instruction.MOVE_TO_LINE_TO: - goog.asserts.assert(goog.isNumber(instruction[1])); - d = /** @type {number} */ (instruction[1]); - goog.asserts.assert(goog.isNumber(instruction[2])); - dd = /** @type {number} */ (instruction[2]); - context.moveTo(pixelCoordinates[d], pixelCoordinates[d + 1]); - for (d += 2; d < dd; d += 2) { - context.lineTo(pixelCoordinates[d], pixelCoordinates[d + 1]); - } - ++i; - break; - case ol.render.canvas.Instruction.SET_FILL_STYLE: - goog.asserts.assert(goog.isString(instruction[1])); - context.fillStyle = /** @type {string} */ (instruction[1]); - ++i; - break; - case ol.render.canvas.Instruction.SET_STROKE_STYLE: - goog.asserts.assert(goog.isString(instruction[1])); - goog.asserts.assert(goog.isNumber(instruction[2])); - goog.asserts.assert(goog.isString(instruction[3])); - goog.asserts.assert(goog.isString(instruction[4])); - goog.asserts.assert(goog.isNumber(instruction[5])); - goog.asserts.assert(!goog.isNull(instruction[6])); - var usePixelRatio = goog.isDef(instruction[7]) ? instruction[7] : true; - var lineWidth = /** @type {number} */ (instruction[2]); - context.strokeStyle = /** @type {string} */ (instruction[1]); - context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth; - context.lineCap = /** @type {string} */ (instruction[3]); - context.lineJoin = /** @type {string} */ (instruction[4]); - context.miterLimit = /** @type {number} */ (instruction[5]); - if (ol.has.CANVAS_LINE_DASH) { - context.setLineDash(/** @type {Array.<number>} */ (instruction[6])); - } - ++i; - break; - case ol.render.canvas.Instruction.SET_TEXT_STYLE: - goog.asserts.assert(goog.isString(instruction[1])); - goog.asserts.assert(goog.isString(instruction[2])); - goog.asserts.assert(goog.isString(instruction[3])); - context.font = /** @type {string} */ (instruction[1]); - context.textAlign = /** @type {string} */ (instruction[2]); - context.textBaseline = /** @type {string} */ (instruction[3]); - ++i; - break; - case ol.render.canvas.Instruction.STROKE: - context.stroke(); - ++i; - break; - default: - goog.asserts.fail(); - ++i; // consume the instruction anyway, to avoid an infinite loop - break; - } + for (i = 0, ii = this.geometries_.length; i < ii; ++i) { + goog.events.listen( + this.geometries_[i], goog.events.EventType.CHANGE, + this.changed, false, this); } - // assert that all instructions were consumed - goog.asserts.assert(i == instructions.length); - return undefined; }; /** - * @param {CanvasRenderingContext2D} context Context. - * @param {number} pixelRatio Pixel ratio. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features to skip + * Make a complete copy of the geometry. + * @return {!ol.geom.GeometryCollection} Clone. + * @api stable */ -ol.render.canvas.Replay.prototype.replay = function( - context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { - var instructions = this.instructions; - this.replay_(context, pixelRatio, transform, viewRotation, - skippedFeaturesHash, instructions, undefined); +ol.geom.GeometryCollection.prototype.clone = function() { + var geometryCollection = new ol.geom.GeometryCollection(null); + geometryCollection.setGeometries(this.geometries_); + return geometryCollection; }; /** - * @param {CanvasRenderingContext2D} context Context. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features to skip - * @param {function(ol.Feature): T=} opt_featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T + * @inheritDoc */ -ol.render.canvas.Replay.prototype.replayHitDetection = function( - context, transform, viewRotation, skippedFeaturesHash, - opt_featureCallback) { - var instructions = this.hitDetectionInstructions; - return this.replay_(context, 1, transform, viewRotation, - skippedFeaturesHash, instructions, opt_featureCallback); +ol.geom.GeometryCollection.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + minSquaredDistance = geometries[i].closestPointXY( + x, y, closestPoint, minSquaredDistance); + } + return minSquaredDistance; }; /** - * @private + * @inheritDoc */ -ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ = - function() { - var hitDetectionInstructions = this.hitDetectionInstructions; - // step 1 - reverse array - hitDetectionInstructions.reverse(); - // step 2 - reverse instructions within geometry blocks - var i; - var n = hitDetectionInstructions.length; - var instruction; - var type; - var begin = -1; - for (i = 0; i < n; ++i) { - instruction = hitDetectionInstructions[i]; - type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); - if (type == ol.render.canvas.Instruction.END_GEOMETRY) { - goog.asserts.assert(begin == -1); - begin = i; - } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) { - instruction[2] = i; - goog.asserts.assert(begin >= 0); - ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i); - begin = -1; +ol.geom.GeometryCollection.prototype.containsXY = function(x, y) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + if (geometries[i].containsXY(x, y)) { + return true; } } + return false; }; /** * @inheritDoc */ -ol.render.canvas.Replay.prototype.drawAsync = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.computeExtent = function(extent) { + ol.extent.createOrUpdateEmpty(extent); + var geometries = this.geometries_; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + ol.extent.extend(extent, geometries[i].getExtent()); + } + return extent; +}; /** - * @inheritDoc + * Return the geometries that make up this geometry collection. + * @return {Array.<ol.geom.Geometry>} Geometries. + * @api stable */ -ol.render.canvas.Replay.prototype.drawCircleGeometry = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.getGeometries = function() { + return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_); +}; /** - * @inheritDoc + * @return {Array.<ol.geom.Geometry>} Geometries. */ -ol.render.canvas.Replay.prototype.drawFeature = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.getGeometriesArray = function() { + return this.geometries_; +}; /** * @inheritDoc */ -ol.render.canvas.Replay.prototype.drawGeometryCollectionGeometry = - goog.abstractMethod; +ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = + function(squaredTolerance) { + if (this.simplifiedGeometryRevision != this.getRevision()) { + goog.object.clear(this.simplifiedGeometryCache); + this.simplifiedGeometryMaxMinSquaredTolerance = 0; + this.simplifiedGeometryRevision = this.getRevision(); + } + if (squaredTolerance < 0 || + (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && + squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) { + return this; + } + var key = squaredTolerance.toString(); + if (this.simplifiedGeometryCache.hasOwnProperty(key)) { + return this.simplifiedGeometryCache[key]; + } else { + var simplifiedGeometries = []; + var geometries = this.geometries_; + var simplified = false; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometry = geometries[i]; + var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); + simplifiedGeometries.push(simplifiedGeometry); + if (simplifiedGeometry !== geometry) { + simplified = true; + } + } + if (simplified) { + var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null); + simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries); + this.simplifiedGeometryCache[key] = simplifiedGeometryCollection; + return simplifiedGeometryCollection; + } else { + this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; + return this; + } + } +}; /** * @inheritDoc + * @api stable */ -ol.render.canvas.Replay.prototype.drawLineStringGeometry = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.getType = function() { + return ol.geom.GeometryType.GEOMETRY_COLLECTION; +}; /** * @inheritDoc + * @api stable */ -ol.render.canvas.Replay.prototype.drawMultiLineStringGeometry = - goog.abstractMethod; +ol.geom.GeometryCollection.prototype.intersectsExtent = function(extent) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + if (geometries[i].intersectsExtent(extent)) { + return true; + } + } + return false; +}; /** - * @inheritDoc + * @return {boolean} Is empty. */ -ol.render.canvas.Replay.prototype.drawPointGeometry = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.isEmpty = function() { + return goog.array.isEmpty(this.geometries_); +}; /** - * @inheritDoc + * Set the geometries that make up this geometry collection. + * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * @api stable */ -ol.render.canvas.Replay.prototype.drawMultiPointGeometry = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) { + this.setGeometriesArray( + ol.geom.GeometryCollection.cloneGeometries_(geometries)); +}; /** - * @inheritDoc + * @param {Array.<ol.geom.Geometry>} geometries Geometries. */ -ol.render.canvas.Replay.prototype.drawPolygonGeometry = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) { + this.unlistenGeometriesChange_(); + this.geometries_ = geometries; + this.listenGeometriesChange_(); + this.changed(); +}; /** * @inheritDoc + * @api stable */ -ol.render.canvas.Replay.prototype.drawMultiPolygonGeometry = - goog.abstractMethod; +ol.geom.GeometryCollection.prototype.applyTransform = function(transformFn) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].applyTransform(transformFn); + } + this.changed(); +}; /** - * @inheritDoc + * Translate the geometry. + * @param {number} deltaX Delta X. + * @param {number} deltaY Delta Y. + * @api */ -ol.render.canvas.Replay.prototype.drawText = goog.abstractMethod; +ol.geom.GeometryCollection.prototype.translate = function(deltaX, deltaY) { + var geometries = this.geometries_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + geometries[i].translate(deltaX, deltaY); + } + this.changed(); +}; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.Feature} feature Feature. + * @inheritDoc */ -ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) { - goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction1_)); - this.beginGeometryInstruction1_[2] = this.instructions.length; - this.beginGeometryInstruction1_ = null; - goog.asserts.assert(!goog.isNull(this.beginGeometryInstruction2_)); - this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length; - this.beginGeometryInstruction2_ = null; - var endGeometryInstruction = - [ol.render.canvas.Instruction.END_GEOMETRY, feature]; - this.instructions.push(endGeometryInstruction); - this.hitDetectionInstructions.push(endGeometryInstruction); +ol.geom.GeometryCollection.prototype.disposeInternal = function() { + this.unlistenGeometriesChange_(); + goog.base(this, 'disposeInternal'); }; +goog.provide('ol.geom.flat.interpolate'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math'); + /** - * FIXME empty description for jsdoc + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} fraction Fraction. + * @param {Array.<number>=} opt_dest Destination. + * @return {Array.<number>} Destination. */ -ol.render.canvas.Replay.prototype.finish = goog.nullFunction; +ol.geom.flat.interpolate.lineString = + function(flatCoordinates, offset, end, stride, fraction, opt_dest) { + // FIXME interpolate extra dimensions + goog.asserts.assert(0 <= fraction && fraction <= 1, + 'fraction should be in between 0 and 1'); + var pointX = NaN; + var pointY = NaN; + var n = (end - offset) / stride; + if (n === 0) { + goog.asserts.fail('n cannot be 0'); + } else if (n == 1) { + pointX = flatCoordinates[offset]; + pointY = flatCoordinates[offset + 1]; + } else if (n == 2) { + pointX = (1 - fraction) * flatCoordinates[offset] + + fraction * flatCoordinates[offset + stride]; + pointY = (1 - fraction) * flatCoordinates[offset + 1] + + fraction * flatCoordinates[offset + stride + 1]; + } else { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + var length = 0; + var cumulativeLengths = [0]; + var i; + for (i = offset + stride; i < end; i += stride) { + var x2 = flatCoordinates[i]; + var y2 = flatCoordinates[i + 1]; + length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + cumulativeLengths.push(length); + x1 = x2; + y1 = y2; + } + var target = fraction * length; + var index = goog.array.binarySearch(cumulativeLengths, target); + if (index < 0) { + var t = (target - cumulativeLengths[-index - 2]) / + (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]); + var o = offset + (-index - 2) * stride; + pointX = goog.math.lerp( + flatCoordinates[o], flatCoordinates[o + stride], t); + pointY = goog.math.lerp( + flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t); + } else { + pointX = flatCoordinates[offset + index * stride]; + pointY = flatCoordinates[offset + index * stride + 1]; + } + } + if (goog.isDefAndNotNull(opt_dest)) { + opt_dest[0] = pointX; + opt_dest[1] = pointY; + return opt_dest; + } else { + return [pointX, pointY]; + } +}; /** - * Get the buffered rendering extent. Rendering will be clipped to the extent - * provided to the constructor. To account for symbolizers that may intersect - * this extent, we calculate a buffered extent (e.g. based on stroke width). - * @return {ol.Extent} The buffered rendering extent. - * @protected + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {number} m M. + * @param {boolean} extrapolate Extrapolate. + * @return {ol.Coordinate} Coordinate. */ -ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() { - return this.maxExtent; +ol.geom.flat.lineStringCoordinateAtM = + function(flatCoordinates, offset, end, stride, m, extrapolate) { + if (end == offset) { + return null; + } + var coordinate; + if (m < flatCoordinates[offset + stride - 1]) { + if (extrapolate) { + coordinate = flatCoordinates.slice(offset, offset + stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } else if (flatCoordinates[end - 1] < m) { + if (extrapolate) { + coordinate = flatCoordinates.slice(end - stride, end); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + // FIXME use O(1) search + if (m == flatCoordinates[offset + stride - 1]) { + return flatCoordinates.slice(offset, offset + stride); + } + var lo = offset / stride; + var hi = end / stride; + while (lo < hi) { + var mid = (lo + hi) >> 1; + if (m < flatCoordinates[(mid + 1) * stride - 1]) { + hi = mid; + } else { + lo = mid + 1; + } + } + var m0 = flatCoordinates[lo * stride - 1]; + if (m == m0) { + return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride); + } + var m1 = flatCoordinates[(lo + 1) * stride - 1]; + goog.asserts.assert(m0 < m, 'm0 should be less than m'); + goog.asserts.assert(m <= m1, 'm should be less than or equal to m1'); + var t = (m - m0) / (m1 - m0); + coordinate = []; + var i; + for (i = 0; i < stride - 1; ++i) { + coordinate.push(goog.math.lerp(flatCoordinates[(lo - 1) * stride + i], + flatCoordinates[lo * stride + i], t)); + } + coordinate.push(m); + goog.asserts.assert(coordinate.length == stride, + 'length of coordinate array should match stride'); + return coordinate; }; /** - * @inheritDoc + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<number>} ends Ends. + * @param {number} stride Stride. + * @param {number} m M. + * @param {boolean} extrapolate Extrapolate. + * @param {boolean} interpolate Interpolate. + * @return {ol.Coordinate} Coordinate. */ -ol.render.canvas.Replay.prototype.setFillStrokeStyle = goog.abstractMethod; +ol.geom.flat.lineStringsCoordinateAtM = function( + flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) { + if (interpolate) { + return ol.geom.flat.lineStringCoordinateAtM( + flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate); + } + var coordinate; + if (m < flatCoordinates[stride - 1]) { + if (extrapolate) { + coordinate = flatCoordinates.slice(0, stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + if (flatCoordinates[flatCoordinates.length - 1] < m) { + if (extrapolate) { + coordinate = flatCoordinates.slice(flatCoordinates.length - stride); + coordinate[stride - 1] = m; + return coordinate; + } else { + return null; + } + } + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + if (offset == end) { + continue; + } + if (m < flatCoordinates[offset + stride - 1]) { + return null; + } else if (m <= flatCoordinates[end - 1]) { + return ol.geom.flat.lineStringCoordinateAtM( + flatCoordinates, offset, end, stride, m, false); + } + offset = end; + } + goog.asserts.fail( + 'ol.geom.flat.lineStringsCoordinateAtM should have returned'); + return null; +}; + +goog.provide('ol.geom.flat.length'); /** - * @inheritDoc + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Length. */ -ol.render.canvas.Replay.prototype.setImageStyle = goog.abstractMethod; +ol.geom.flat.length.lineString = + function(flatCoordinates, offset, end, stride) { + var x1 = flatCoordinates[offset]; + var y1 = flatCoordinates[offset + 1]; + var length = 0; + var i; + for (i = offset + stride; i < end; i += stride) { + var x2 = flatCoordinates[i]; + var y2 = flatCoordinates[i + 1]; + length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + x1 = x2; + y1 = y2; + } + return length; +}; /** - * @inheritDoc + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} Perimeter. */ -ol.render.canvas.Replay.prototype.setTextStyle = goog.abstractMethod; +ol.geom.flat.length.linearRing = + function(flatCoordinates, offset, end, stride) { + var perimeter = + ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride); + var dx = flatCoordinates[end - stride] - flatCoordinates[offset]; + var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1]; + perimeter += Math.sqrt(dx * dx + dy * dy); + return perimeter; +}; + +goog.provide('ol.geom.LineString'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interpolate'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.length'); +goog.require('ol.geom.flat.segments'); +goog.require('ol.geom.flat.simplify'); /** + * @classdesc + * Linestring geometry. + * * @constructor - * @extends {ol.render.canvas.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Maximum extent. - * @param {number} resolution Resolution. - * @protected - * @struct + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution) { +ol.geom.LineString = function(coordinates, opt_layout) { - goog.base(this, tolerance, maxExtent, resolution); + goog.base(this); /** * @private - * @type {HTMLCanvasElement|HTMLVideoElement|Image} + * @type {ol.Coordinate} */ - this.hitDetectionImage_ = null; + this.flatMidpoint_ = null; /** * @private - * @type {HTMLCanvasElement|HTMLVideoElement|Image} + * @type {number} */ - this.image_ = null; + this.flatMidpointRevision_ = -1; /** * @private - * @type {number|undefined} + * @type {number} */ - this.anchorX_ = undefined; + this.maxDelta_ = -1; /** * @private - * @type {number|undefined} + * @type {number} */ - this.anchorY_ = undefined; + this.maxDeltaRevision_ = -1; - /** - * @private - * @type {number|undefined} - */ - this.height_ = undefined; + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); - /** - * @private - * @type {number|undefined} - */ - this.opacity_ = undefined; +}; +goog.inherits(ol.geom.LineString, ol.geom.SimpleGeometry); - /** - * @private - * @type {number|undefined} - */ - this.originX_ = undefined; - /** - * @private - * @type {number|undefined} - */ - this.originY_ = undefined; +/** + * Append the passed coordinate to the coordinates of the linestring. + * @param {ol.Coordinate} coordinate Coordinate. + * @api stable + */ +ol.geom.LineString.prototype.appendCoordinate = function(coordinate) { + goog.asserts.assert(coordinate.length == this.stride, + 'length of coordinate array should match stride'); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = coordinate.slice(); + } else { + goog.array.extend(this.flatCoordinates, coordinate); + } + this.changed(); +}; - /** - * @private - * @type {boolean|undefined} - */ - this.rotateWithView_ = undefined; - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = undefined; +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.LineString} Clone. + * @api stable + */ +ol.geom.LineString.prototype.clone = function() { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return lineString; +}; - /** - * @private - * @type {number|undefined} - */ - this.scale_ = undefined; - /** - * @private - * @type {boolean|undefined} - */ - this.snapToPixel_ = undefined; +/** + * @inheritDoc + */ +ol.geom.LineString.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getClosestPoint( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); +}; + + +/** + * Iterate over each segment, calling the provided callback. + * If the callback returns a truthy value the function returns that + * value immediately. Otherwise the function returns `false`. + * + * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function + * called for each segment. + * @param {S=} opt_this The object to be used as the value of 'this' + * within callback. + * @return {T|boolean} Value. + * @template T,S + * @api + */ +ol.geom.LineString.prototype.forEachSegment = function(callback, opt_this) { + return ol.geom.flat.segments.forEach(this.flatCoordinates, 0, + this.flatCoordinates.length, this.stride, callback, opt_this); +}; + + +/** + * Returns the coordinate at `m` using linear interpolation, or `null` if no + * such coordinate exists. + * + * `opt_extrapolate` controls extrapolation beyond the range of Ms in the + * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first + * M will return the first coordinate and Ms greater than the last M will + * return the last coordinate. + * + * @param {number} m M. + * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`. + * @return {ol.Coordinate} Coordinate. + * @api stable + */ +ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) { + if (this.layout != ol.geom.GeometryLayout.XYM && + this.layout != ol.geom.GeometryLayout.XYZM) { + return null; + } + var extrapolate = goog.isDef(opt_extrapolate) ? opt_extrapolate : false; + return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0, + this.flatCoordinates.length, this.stride, m, extrapolate); +}; - /** - * @private - * @type {number|undefined} - */ - this.width_ = undefined; +/** + * Return the coordinates of the linestring. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.LineString.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); }; -goog.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay); /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @private - * @return {number} My end. + * Return the length of the linestring on projected plane. + * @return {number} Length (on projected plane). + * @api stable */ -ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = - function(flatCoordinates, offset, end, stride) { - return this.appendFlatCoordinates( - flatCoordinates, offset, end, stride, false); +ol.geom.LineString.prototype.getLength = function() { + return ol.geom.flat.length.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); }; /** - * @inheritDoc + * @return {Array.<number>} Flat midpoint. */ -ol.render.canvas.ImageReplay.prototype.drawPointGeometry = - function(pointGeometry, feature) { - if (goog.isNull(this.image_)) { - return; +ol.geom.LineString.prototype.getFlatMidpoint = function() { + if (this.flatMidpointRevision_ != this.getRevision()) { + this.flatMidpoint_ = ol.geom.flat.interpolate.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + 0.5, this.flatMidpoint_); + this.flatMidpointRevision_ = this.getRevision(); } - goog.asserts.assert(goog.isDef(this.anchorX_)); - goog.asserts.assert(goog.isDef(this.anchorY_)); - goog.asserts.assert(goog.isDef(this.height_)); - goog.asserts.assert(goog.isDef(this.opacity_)); - goog.asserts.assert(goog.isDef(this.originX_)); - goog.asserts.assert(goog.isDef(this.originY_)); - goog.asserts.assert(goog.isDef(this.rotateWithView_)); - goog.asserts.assert(goog.isDef(this.rotation_)); - goog.asserts.assert(goog.isDef(this.scale_)); - goog.asserts.assert(goog.isDef(this.width_)); - this.beginGeometry(pointGeometry, feature); - var flatCoordinates = pointGeometry.getFlatCoordinates(); - var stride = pointGeometry.getStride(); - var myBegin = this.coordinates.length; - var myEnd = this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - this.instructions.push([ - ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, - // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, this.anchorY_, this.height_, this.opacity_, - this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, this.snapToPixel_, this.width_ - ]); - this.hitDetectionInstructions.push([ - ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, - this.hitDetectionImage_, - // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, this.anchorY_, this.height_, this.opacity_, - this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, this.snapToPixel_, this.width_ - ]); - this.endGeometry(pointGeometry, feature); + return this.flatMidpoint_; }; /** * @inheritDoc */ -ol.render.canvas.ImageReplay.prototype.drawMultiPointGeometry = - function(multiPointGeometry, feature) { - if (goog.isNull(this.image_)) { - return; - } - goog.asserts.assert(goog.isDef(this.anchorX_)); - goog.asserts.assert(goog.isDef(this.anchorY_)); - goog.asserts.assert(goog.isDef(this.height_)); - goog.asserts.assert(goog.isDef(this.opacity_)); - goog.asserts.assert(goog.isDef(this.originX_)); - goog.asserts.assert(goog.isDef(this.originY_)); - goog.asserts.assert(goog.isDef(this.rotateWithView_)); - goog.asserts.assert(goog.isDef(this.rotation_)); - goog.asserts.assert(goog.isDef(this.scale_)); - goog.asserts.assert(goog.isDef(this.width_)); - this.beginGeometry(multiPointGeometry, feature); - var flatCoordinates = multiPointGeometry.getFlatCoordinates(); - var stride = multiPointGeometry.getStride(); - var myBegin = this.coordinates.length; - var myEnd = this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - this.instructions.push([ - ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_, - // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, this.anchorY_, this.height_, this.opacity_, - this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, this.snapToPixel_, this.width_ - ]); - this.hitDetectionInstructions.push([ - ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, - this.hitDetectionImage_, - // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, this.anchorY_, this.height_, this.opacity_, - this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, this.snapToPixel_, this.width_ - ]); - this.endGeometry(multiPointGeometry, feature); +ol.geom.LineString.prototype.getSimplifiedGeometryInternal = + function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + squaredTolerance, simplifiedFlatCoordinates, 0); + var simplifiedLineString = new ol.geom.LineString(null); + simplifiedLineString.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); + return simplifiedLineString; }; /** * @inheritDoc + * @api stable */ -ol.render.canvas.ImageReplay.prototype.finish = function() { - this.reverseHitDetectionInstructions_(); - // FIXME this doesn't really protect us against further calls to draw*Geometry - this.anchorX_ = undefined; - this.anchorY_ = undefined; - this.hitDetectionImage_ = null; - this.image_ = null; - this.height_ = undefined; - this.scale_ = undefined; - this.opacity_ = undefined; - this.originX_ = undefined; - this.originY_ = undefined; - this.rotateWithView_ = undefined; - this.rotation_ = undefined; - this.snapToPixel_ = undefined; - this.width_ = undefined; +ol.geom.LineString.prototype.getType = function() { + return ol.geom.GeometryType.LINE_STRING; }; /** * @inheritDoc + * @api stable */ -ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { - goog.asserts.assert(!goog.isNull(imageStyle)); - var anchor = imageStyle.getAnchor(); - goog.asserts.assert(!goog.isNull(anchor)); - var size = imageStyle.getSize(); - goog.asserts.assert(!goog.isNull(size)); - var hitDetectionImage = imageStyle.getHitDetectionImage(1); - goog.asserts.assert(!goog.isNull(hitDetectionImage)); - var image = imageStyle.getImage(1); - goog.asserts.assert(!goog.isNull(image)); - var origin = imageStyle.getOrigin(); - goog.asserts.assert(!goog.isNull(origin)); - this.anchorX_ = anchor[0]; - this.anchorY_ = anchor[1]; - this.hitDetectionImage_ = hitDetectionImage; - this.image_ = image; - this.height_ = size[1]; - this.opacity_ = imageStyle.getOpacity(); - this.originX_ = origin[0]; - this.originY_ = origin[1]; - this.rotateWithView_ = imageStyle.getRotateWithView(); - this.rotation_ = imageStyle.getRotation(); - this.scale_ = imageStyle.getScale(); - this.snapToPixel_ = imageStyle.getSnapToPixel(); - this.width_ = size[0]; +ol.geom.LineString.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.lineString( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, + extent); +}; + + +/** + * Set the coordinates of the linestring. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable + */ +ol.geom.LineString.prototype.setCoordinates = + function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); + } else { + this.setLayout(opt_layout, coordinates, 1); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); + } +}; + + +/** + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + */ +ol.geom.LineString.prototype.setFlatCoordinates = + function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); }; +goog.provide('ol.geom.MultiLineString'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interpolate'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.simplify'); + /** + * @classdesc + * Multi-linestring geometry. + * * @constructor - * @extends {ol.render.canvas.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Maximum extent. - * @param {number} resolution Resolution. - * @protected - * @struct + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution) { +ol.geom.MultiLineString = function(coordinates, opt_layout) { - goog.base(this, tolerance, maxExtent, resolution); + goog.base(this); /** + * @type {Array.<number>} * @private - * @type {{currentStrokeStyle: (string|undefined), - * currentLineCap: (string|undefined), - * currentLineDash: Array.<number>, - * currentLineJoin: (string|undefined), - * currentLineWidth: (number|undefined), - * currentMiterLimit: (number|undefined), - * lastStroke: number, - * strokeStyle: (string|undefined), - * lineCap: (string|undefined), - * lineDash: Array.<number>, - * lineJoin: (string|undefined), - * lineWidth: (number|undefined), - * miterLimit: (number|undefined)}|null} */ - this.state_ = { - currentStrokeStyle: undefined, - currentLineCap: undefined, - currentLineDash: null, - currentLineJoin: undefined, - currentLineWidth: undefined, - currentMiterLimit: undefined, - lastStroke: 0, - strokeStyle: undefined, - lineCap: undefined, - lineDash: null, - lineJoin: undefined, - lineWidth: undefined, - miterLimit: undefined - }; + this.ends_ = []; -}; -goog.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay); + /** + * @private + * @type {number} + */ + this.maxDelta_ = -1; + + /** + * @private + * @type {number} + */ + this.maxDeltaRevision_ = -1; + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); -/** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @private - * @return {number} end. - */ -ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = - function(flatCoordinates, offset, end, stride) { - var myBegin = this.coordinates.length; - var myEnd = this.appendFlatCoordinates( - flatCoordinates, offset, end, stride, false); - var moveToLineToInstruction = - [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; - this.instructions.push(moveToLineToInstruction); - this.hitDetectionInstructions.push(moveToLineToInstruction); - return end; }; +goog.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry); /** - * @inheritDoc + * Append the passed linestring to the multilinestring. + * @param {ol.geom.LineString} lineString LineString. + * @api stable */ -ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() { - var extent = this.maxExtent; - if (this.maxLineWidth) { - extent = ol.extent.buffer( - extent, this.resolution * (this.maxLineWidth + 1) / 2); +ol.geom.MultiLineString.prototype.appendLineString = function(lineString) { + goog.asserts.assert(lineString.getLayout() == this.layout, + 'layout of lineString should match the layout'); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = lineString.getFlatCoordinates().slice(); + } else { + goog.array.extend( + this.flatCoordinates, lineString.getFlatCoordinates().slice()); } - return extent; + this.ends_.push(this.flatCoordinates.length); + this.changed(); }; /** - * @private + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiLineString} Clone. + * @api stable */ -ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() { - var state = this.state_; - var strokeStyle = state.strokeStyle; - var lineCap = state.lineCap; - var lineDash = state.lineDash; - var lineJoin = state.lineJoin; - var lineWidth = state.lineWidth; - var miterLimit = state.miterLimit; - goog.asserts.assert(goog.isDef(strokeStyle)); - goog.asserts.assert(goog.isDef(lineCap)); - goog.asserts.assert(!goog.isNull(lineDash)); - goog.asserts.assert(goog.isDef(lineJoin)); - goog.asserts.assert(goog.isDef(lineWidth)); - goog.asserts.assert(goog.isDef(miterLimit)); - if (state.currentStrokeStyle != strokeStyle || - state.currentLineCap != lineCap || - !goog.array.equals(state.currentLineDash, lineDash) || - state.currentLineJoin != lineJoin || - state.currentLineWidth != lineWidth || - state.currentMiterLimit != miterLimit) { - if (state.lastStroke != this.coordinates.length) { - this.instructions.push( - [ol.render.canvas.Instruction.STROKE]); - state.lastStroke = this.coordinates.length; - } - this.instructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash], - [ol.render.canvas.Instruction.BEGIN_PATH]); - state.currentStrokeStyle = strokeStyle; - state.currentLineCap = lineCap; - state.currentLineDash = lineDash; - state.currentLineJoin = lineJoin; - state.currentLineWidth = lineWidth; - state.currentMiterLimit = miterLimit; - } +ol.geom.MultiLineString.prototype.clone = function() { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), this.ends_.slice()); + return multiLineString; }; /** * @inheritDoc */ -ol.render.canvas.LineStringReplay.prototype.drawLineStringGeometry = - function(lineStringGeometry, feature) { - var state = this.state_; - goog.asserts.assert(!goog.isNull(state)); - var strokeStyle = state.strokeStyle; - var lineWidth = state.lineWidth; - if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) { - return; +ol.geom.MultiLineString.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; } - this.setStrokeStyle_(); - this.beginGeometry(lineStringGeometry, feature); - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, - state.miterLimit, state.lineDash], - [ol.render.canvas.Instruction.BEGIN_PATH]); - var flatCoordinates = lineStringGeometry.getFlatCoordinates(); - var stride = lineStringGeometry.getStride(); - this.drawFlatCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); - this.endGeometry(lineStringGeometry, feature); + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( + this.flatCoordinates, 0, this.ends_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); + } + return ol.geom.flat.closest.getsClosestPoint( + this.flatCoordinates, 0, this.ends_, this.stride, + this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); }; /** - * @inheritDoc + * Returns the coordinate at `m` using linear interpolation, or `null` if no + * such coordinate exists. + * + * `opt_extrapolate` controls extrapolation beyond the range of Ms in the + * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first + * M will return the first coordinate and Ms greater than the last M will + * return the last coordinate. + * + * `opt_interpolate` controls interpolation between consecutive LineStrings + * within the MultiLineString. If `opt_interpolate` is `true` the coordinates + * will be linearly interpolated between the last coordinate of one LineString + * and the first coordinate of the next LineString. If `opt_interpolate` is + * `false` then the function will return `null` for Ms falling between + * LineStrings. + * + * @param {number} m M. + * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`. + * @param {boolean=} opt_interpolate Interpolate. Default is `false`. + * @return {ol.Coordinate} Coordinate. + * @api stable */ -ol.render.canvas.LineStringReplay.prototype.drawMultiLineStringGeometry = - function(multiLineStringGeometry, feature) { - var state = this.state_; - goog.asserts.assert(!goog.isNull(state)); - var strokeStyle = state.strokeStyle; - var lineWidth = state.lineWidth; - if (!goog.isDef(strokeStyle) || !goog.isDef(lineWidth)) { - return; - } - this.setStrokeStyle_(); - this.beginGeometry(multiLineStringGeometry, feature); - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, - state.miterLimit, state.lineDash], - [ol.render.canvas.Instruction.BEGIN_PATH]); - var ends = multiLineStringGeometry.getEnds(); - var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); - var stride = multiLineStringGeometry.getStride(); - var offset = 0; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - offset = this.drawFlatCoordinates_( - flatCoordinates, offset, ends[i], stride); +ol.geom.MultiLineString.prototype.getCoordinateAtM = + function(m, opt_extrapolate, opt_interpolate) { + if ((this.layout != ol.geom.GeometryLayout.XYM && + this.layout != ol.geom.GeometryLayout.XYZM) || + this.flatCoordinates.length === 0) { + return null; } - this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]); - this.endGeometry(multiLineStringGeometry, feature); + var extrapolate = goog.isDef(opt_extrapolate) ? opt_extrapolate : false; + var interpolate = goog.isDef(opt_interpolate) ? opt_interpolate : false; + return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0, + this.ends_, this.stride, m, extrapolate, interpolate); }; /** - * @inheritDoc + * Return the coordinates of the multilinestring. + * @return {Array.<Array.<ol.Coordinate>>} Coordinates. + * @api stable */ -ol.render.canvas.LineStringReplay.prototype.finish = function() { - var state = this.state_; - goog.asserts.assert(!goog.isNull(state)); - if (state.lastStroke != this.coordinates.length) { - this.instructions.push([ol.render.canvas.Instruction.STROKE]); - } - this.reverseHitDetectionInstructions_(); - this.state_ = null; +ol.geom.MultiLineString.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinatess( + this.flatCoordinates, 0, this.ends_, this.stride); }; /** - * @inheritDoc + * @return {Array.<number>} Ends. */ -ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { - goog.asserts.assert(!goog.isNull(this.state_)); - goog.asserts.assert(goog.isNull(fillStyle)); - goog.asserts.assert(!goog.isNull(strokeStyle)); - var strokeStyleColor = strokeStyle.getColor(); - this.state_.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ? - strokeStyleColor : ol.render.canvas.defaultStrokeStyle); - var strokeStyleLineCap = strokeStyle.getLineCap(); - this.state_.lineCap = goog.isDef(strokeStyleLineCap) ? - strokeStyleLineCap : ol.render.canvas.defaultLineCap; - var strokeStyleLineDash = strokeStyle.getLineDash(); - this.state_.lineDash = !goog.isNull(strokeStyleLineDash) ? - strokeStyleLineDash : ol.render.canvas.defaultLineDash; - var strokeStyleLineJoin = strokeStyle.getLineJoin(); - this.state_.lineJoin = goog.isDef(strokeStyleLineJoin) ? - strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; - var strokeStyleWidth = strokeStyle.getWidth(); - this.state_.lineWidth = goog.isDef(strokeStyleWidth) ? - strokeStyleWidth : ol.render.canvas.defaultLineWidth; - var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); - this.state_.miterLimit = goog.isDef(strokeStyleMiterLimit) ? - strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; - this.maxLineWidth = Math.max(this.maxLineWidth, this.state_.lineWidth); +ol.geom.MultiLineString.prototype.getEnds = function() { + return this.ends_; }; - /** - * @constructor - * @extends {ol.render.canvas.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Maximum extent. - * @param {number} resolution Resolution. - * @protected - * @struct + * Return the linestring at the specified index. + * @param {number} index Index. + * @return {ol.geom.LineString} LineString. + * @api stable */ -ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) { - - goog.base(this, tolerance, maxExtent, resolution); - - /** - * @private - * @type {{currentFillStyle: (string|undefined), - * currentStrokeStyle: (string|undefined), - * currentLineCap: (string|undefined), - * currentLineDash: Array.<number>, - * currentLineJoin: (string|undefined), - * currentLineWidth: (number|undefined), - * currentMiterLimit: (number|undefined), - * fillStyle: (string|undefined), - * strokeStyle: (string|undefined), - * lineCap: (string|undefined), - * lineDash: Array.<number>, - * lineJoin: (string|undefined), - * lineWidth: (number|undefined), - * miterLimit: (number|undefined)}|null} - */ - this.state_ = { - currentFillStyle: undefined, - currentStrokeStyle: undefined, - currentLineCap: undefined, - currentLineDash: null, - currentLineJoin: undefined, - currentLineWidth: undefined, - currentMiterLimit: undefined, - fillStyle: undefined, - strokeStyle: undefined, - lineCap: undefined, - lineDash: null, - lineJoin: undefined, - lineWidth: undefined, - miterLimit: undefined - }; - +ol.geom.MultiLineString.prototype.getLineString = function(index) { + goog.asserts.assert(0 <= index && index < this.ends_.length, + 'index should be in between 0 and length of the this.ends_ array'); + if (index < 0 || this.ends_.length <= index) { + return null; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); + return lineString; }; -goog.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay); /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @private - * @return {number} End. + * Return the linestrings of this multilinestring. + * @return {Array.<ol.geom.LineString>} LineStrings. + * @api stable */ -ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = - function(flatCoordinates, offset, ends, stride) { - var state = this.state_; - var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; - this.instructions.push(beginPathInstruction); - this.hitDetectionInstructions.push(beginPathInstruction); +ol.geom.MultiLineString.prototype.getLineStrings = function() { + var flatCoordinates = this.flatCoordinates; + var ends = this.ends_; + var layout = this.layout; + /** @type {Array.<ol.geom.LineString>} */ + var lineStrings = []; + var offset = 0; var i, ii; for (i = 0, ii = ends.length; i < ii; ++i) { var end = ends[i]; - var myBegin = this.coordinates.length; - var myEnd = this.appendFlatCoordinates( - flatCoordinates, offset, end, stride, true); - var moveToLineToInstruction = - [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd]; - var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH]; - this.instructions.push(moveToLineToInstruction, closePathInstruction); - this.hitDetectionInstructions.push(moveToLineToInstruction, - closePathInstruction); + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); + lineStrings.push(lineString); offset = end; } - // FIXME is it quicker to fill and stroke each polygon individually, - // FIXME or all polygons together? - var fillInstruction = [ol.render.canvas.Instruction.FILL]; - this.hitDetectionInstructions.push(fillInstruction); - if (goog.isDef(state.fillStyle)) { - this.instructions.push(fillInstruction); - } - if (goog.isDef(state.strokeStyle)) { - goog.asserts.assert(goog.isDef(state.lineWidth)); - var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; - this.instructions.push(strokeInstruction); - this.hitDetectionInstructions.push(strokeInstruction); - } - return offset; + return lineStrings; }; /** - * @inheritDoc + * @return {Array.<number>} Flat midpoints. */ -ol.render.canvas.PolygonReplay.prototype.drawCircleGeometry = - function(circleGeometry, feature) { - var state = this.state_; - goog.asserts.assert(!goog.isNull(state)); - var fillStyle = state.fillStyle; - var strokeStyle = state.strokeStyle; - if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { - return; - } - if (goog.isDef(strokeStyle)) { - goog.asserts.assert(goog.isDef(state.lineWidth)); - } - this.setFillStrokeStyles_(); - this.beginGeometry(circleGeometry, feature); - // always fill the circle for hit detection - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_FILL_STYLE, - ol.color.asString(ol.render.canvas.defaultFillStyle)]); - if (goog.isDef(state.strokeStyle)) { - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, - state.miterLimit, state.lineDash]); - } - var flatCoordinates = circleGeometry.getFlatCoordinates(); - var stride = circleGeometry.getStride(); - var myBegin = this.coordinates.length; - this.appendFlatCoordinates( - flatCoordinates, 0, flatCoordinates.length, stride, false); - var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH]; - var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin]; - this.instructions.push(beginPathInstruction, circleInstruction); - this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction); - var fillInstruction = [ol.render.canvas.Instruction.FILL]; - this.hitDetectionInstructions.push(fillInstruction); - if (goog.isDef(state.fillStyle)) { - this.instructions.push(fillInstruction); - } - if (goog.isDef(state.strokeStyle)) { - goog.asserts.assert(goog.isDef(state.lineWidth)); - var strokeInstruction = [ol.render.canvas.Instruction.STROKE]; - this.instructions.push(strokeInstruction); - this.hitDetectionInstructions.push(strokeInstruction); +ol.geom.MultiLineString.prototype.getFlatMidpoints = function() { + var midpoints = []; + var flatCoordinates = this.flatCoordinates; + var offset = 0; + var ends = this.ends_; + var stride = this.stride; + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + var end = ends[i]; + var midpoint = ol.geom.flat.interpolate.lineString( + flatCoordinates, offset, end, stride, 0.5); + goog.array.extend(midpoints, midpoint); + offset = end; } - this.endGeometry(circleGeometry, feature); + return midpoints; }; /** * @inheritDoc */ -ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry = - function(polygonGeometry, feature) { - var state = this.state_; - goog.asserts.assert(!goog.isNull(state)); - var fillStyle = state.fillStyle; - var strokeStyle = state.strokeStyle; - if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { - return; - } - if (goog.isDef(strokeStyle)) { - goog.asserts.assert(goog.isDef(state.lineWidth)); - } - this.setFillStrokeStyles_(); - this.beginGeometry(polygonGeometry, feature); - // always fill the polygon for hit detection - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_FILL_STYLE, - ol.color.asString(ol.render.canvas.defaultFillStyle)]); - if (goog.isDef(state.strokeStyle)) { - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, - state.miterLimit, state.lineDash]); - } - var ends = polygonGeometry.getEnds(); - var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates(); - var stride = polygonGeometry.getStride(); - this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride); - this.endGeometry(polygonGeometry, feature); +ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = + function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEnds = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers( + this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance, + simplifiedFlatCoordinates, 0, simplifiedEnds); + var simplifiedMultiLineString = new ol.geom.MultiLineString(null); + simplifiedMultiLineString.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); + return simplifiedMultiLineString; }; /** * @inheritDoc + * @api stable */ -ol.render.canvas.PolygonReplay.prototype.drawMultiPolygonGeometry = - function(multiPolygonGeometry, feature) { - var state = this.state_; - goog.asserts.assert(!goog.isNull(state)); - var fillStyle = state.fillStyle; - var strokeStyle = state.strokeStyle; - if (!goog.isDef(fillStyle) && !goog.isDef(strokeStyle)) { - return; - } - if (goog.isDef(strokeStyle)) { - goog.asserts.assert(goog.isDef(state.lineWidth)); - } - this.setFillStrokeStyles_(); - this.beginGeometry(multiPolygonGeometry, feature); - // always fill the multi-polygon for hit detection - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_FILL_STYLE, - ol.color.asString(ol.render.canvas.defaultFillStyle)]); - if (goog.isDef(state.strokeStyle)) { - this.hitDetectionInstructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin, - state.miterLimit, state.lineDash]); - } - var endss = multiPolygonGeometry.getEndss(); - var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates(); - var stride = multiPolygonGeometry.getStride(); - var offset = 0; - var i, ii; - for (i = 0, ii = endss.length; i < ii; ++i) { - offset = this.drawFlatCoordinatess_( - flatCoordinates, offset, endss[i], stride); - } - this.endGeometry(multiPolygonGeometry, feature); +ol.geom.MultiLineString.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_LINE_STRING; }; /** * @inheritDoc + * @api stable */ -ol.render.canvas.PolygonReplay.prototype.finish = function() { - goog.asserts.assert(!goog.isNull(this.state_)); - this.reverseHitDetectionInstructions_(); - this.state_ = null; - // We want to preserve topology when drawing polygons. Polygons are - // simplified using quantization and point elimination. However, we might - // have received a mix of quantized and non-quantized geometries, so ensure - // that all are quantized by quantizing all coordinates in the batch. - var tolerance = this.tolerance; - if (tolerance !== 0) { - var coordinates = this.coordinates; - var i, ii; - for (i = 0, ii = coordinates.length; i < ii; ++i) { - coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance); - } - } +ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.lineStrings( + this.flatCoordinates, 0, this.ends_, this.stride, extent); }; /** - * @inheritDoc + * Set the coordinates of the multilinestring. + * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() { - var extent = this.maxExtent; - if (this.maxLineWidth) { - extent = ol.extent.buffer( - extent, this.resolution * (this.maxLineWidth + 1) / 2); +ol.geom.MultiLineString.prototype.setCoordinates = + function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); + } else { + this.setLayout(opt_layout, coordinates, 2); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + var ends = ol.geom.flat.deflate.coordinatess( + this.flatCoordinates, 0, coordinates, this.stride, this.ends_); + this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; + this.changed(); } - return extent; }; /** - * @inheritDoc + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<number>} ends Ends. */ -ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { - goog.asserts.assert(!goog.isNull(this.state_)); - goog.asserts.assert(!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)); - var state = this.state_; - if (!goog.isNull(fillStyle)) { - var fillStyleColor = fillStyle.getColor(); - state.fillStyle = ol.color.asString(!goog.isNull(fillStyleColor) ? - fillStyleColor : ol.render.canvas.defaultFillStyle); - } else { - state.fillStyle = undefined; - } - if (!goog.isNull(strokeStyle)) { - var strokeStyleColor = strokeStyle.getColor(); - state.strokeStyle = ol.color.asString(!goog.isNull(strokeStyleColor) ? - strokeStyleColor : ol.render.canvas.defaultStrokeStyle); - var strokeStyleLineCap = strokeStyle.getLineCap(); - state.lineCap = goog.isDef(strokeStyleLineCap) ? - strokeStyleLineCap : ol.render.canvas.defaultLineCap; - var strokeStyleLineDash = strokeStyle.getLineDash(); - state.lineDash = !goog.isNull(strokeStyleLineDash) ? - strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; - var strokeStyleLineJoin = strokeStyle.getLineJoin(); - state.lineJoin = goog.isDef(strokeStyleLineJoin) ? - strokeStyleLineJoin : ol.render.canvas.defaultLineJoin; - var strokeStyleWidth = strokeStyle.getWidth(); - state.lineWidth = goog.isDef(strokeStyleWidth) ? - strokeStyleWidth : ol.render.canvas.defaultLineWidth; - var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); - state.miterLimit = goog.isDef(strokeStyleMiterLimit) ? - strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; - this.maxLineWidth = Math.max(this.maxLineWidth, state.lineWidth); +ol.geom.MultiLineString.prototype.setFlatCoordinates = + function(layout, flatCoordinates, ends) { + if (goog.isNull(flatCoordinates)) { + goog.asserts.assert(!goog.isNull(ends) && ends.length === 0, + 'ends cannot be null and ends.length should be 0'); + } else if (ends.length === 0) { + goog.asserts.assert(flatCoordinates.length === 0, + 'flatCoordinates should be an empty array'); } else { - state.strokeStyle = undefined; - state.lineCap = undefined; - state.lineDash = null; - state.lineJoin = undefined; - state.lineWidth = undefined; - state.miterLimit = undefined; + goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1], + 'length of flatCoordinates array should match the last value of ends'); } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.ends_ = ends; + this.changed(); }; /** - * @private + * @param {Array.<ol.geom.LineString>} lineStrings LineStrings. */ -ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { - var state = this.state_; - var fillStyle = state.fillStyle; - var strokeStyle = state.strokeStyle; - var lineCap = state.lineCap; - var lineDash = state.lineDash; - var lineJoin = state.lineJoin; - var lineWidth = state.lineWidth; - var miterLimit = state.miterLimit; - if (goog.isDef(fillStyle) && state.currentFillStyle != fillStyle) { - this.instructions.push( - [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]); - state.currentFillStyle = state.fillStyle; - } - if (goog.isDef(strokeStyle)) { - goog.asserts.assert(goog.isDef(lineCap)); - goog.asserts.assert(!goog.isNull(lineDash)); - goog.asserts.assert(goog.isDef(lineJoin)); - goog.asserts.assert(goog.isDef(lineWidth)); - goog.asserts.assert(goog.isDef(miterLimit)); - if (state.currentStrokeStyle != strokeStyle || - state.currentLineCap != lineCap || - state.currentLineDash != lineDash || - state.currentLineJoin != lineJoin || - state.currentLineWidth != lineWidth || - state.currentMiterLimit != miterLimit) { - this.instructions.push( - [ol.render.canvas.Instruction.SET_STROKE_STYLE, - strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash]); - state.currentStrokeStyle = strokeStyle; - state.currentLineCap = lineCap; - state.currentLineDash = lineDash; - state.currentLineJoin = lineJoin; - state.currentLineWidth = lineWidth; - state.currentMiterLimit = miterLimit; +ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) { + var layout = ol.geom.GeometryLayout.XY; + var flatCoordinates = []; + var ends = []; + var i, ii; + for (i = 0, ii = lineStrings.length; i < ii; ++i) { + var lineString = lineStrings[i]; + if (i === 0) { + layout = lineString.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + goog.asserts.assert(lineString.getLayout() == layout, + 'layout of lineString should match layout'); } + goog.array.extend(flatCoordinates, lineString.getFlatCoordinates()); + ends.push(flatCoordinates.length); } + this.setFlatCoordinates(layout, flatCoordinates, ends); }; +goog.provide('ol.geom.MultiPoint'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.math'); + /** + * @classdesc + * Multi-point geometry. + * * @constructor - * @extends {ol.render.canvas.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Maximum extent. - * @param {number} resolution Resolution. - * @protected - * @struct + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution) { - - goog.base(this, tolerance, maxExtent, resolution); - - /** - * @private - * @type {?ol.render.canvas.FillState} - */ - this.replayFillState_ = null; - - /** - * @private - * @type {?ol.render.canvas.StrokeState} - */ - this.replayStrokeState_ = null; +ol.geom.MultiPoint = function(coordinates, opt_layout) { + goog.base(this); + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); +}; +goog.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry); - /** - * @private - * @type {?ol.render.canvas.TextState} - */ - this.replayTextState_ = null; - /** - * @private - * @type {string} - */ - this.text_ = ''; +/** + * Append the passed point to this multipoint. + * @param {ol.geom.Point} point Point. + * @api stable + */ +ol.geom.MultiPoint.prototype.appendPoint = function(point) { + goog.asserts.assert(point.getLayout() == this.layout, + 'the layout of point should match layout'); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = point.getFlatCoordinates().slice(); + } else { + goog.array.extend(this.flatCoordinates, point.getFlatCoordinates()); + } + this.changed(); +}; - /** - * @private - * @type {number} - */ - this.textOffsetX_ = 0; - /** - * @private - * @type {number} - */ - this.textOffsetY_ = 0; +/** + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiPoint} Clone. + * @api stable + */ +ol.geom.MultiPoint.prototype.clone = function() { + var multiPoint = new ol.geom.MultiPoint(null); + multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); + return multiPoint; +}; - /** - * @private - * @type {number} - */ - this.textRotation_ = 0; - /** - * @private - * @type {number} - */ - this.textScale_ = 0; +/** + * @inheritDoc + */ +ol.geom.MultiPoint.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; + } + var flatCoordinates = this.flatCoordinates; + var stride = this.stride; + var i, ii, j; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + var squaredDistance = ol.math.squaredDistance( + x, y, flatCoordinates[i], flatCoordinates[i + 1]); + if (squaredDistance < minSquaredDistance) { + minSquaredDistance = squaredDistance; + for (j = 0; j < stride; ++j) { + closestPoint[j] = flatCoordinates[i + j]; + } + closestPoint.length = stride; + } + } + return minSquaredDistance; +}; - /** - * @private - * @type {?ol.render.canvas.FillState} - */ - this.textFillState_ = null; - /** - * @private - * @type {?ol.render.canvas.StrokeState} - */ - this.textStrokeState_ = null; +/** + * Return the coordinates of the multipoint. + * @return {Array.<ol.Coordinate>} Coordinates. + * @api stable + */ +ol.geom.MultiPoint.prototype.getCoordinates = function() { + return ol.geom.flat.inflate.coordinates( + this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +}; - /** - * @private - * @type {?ol.render.canvas.TextState} - */ - this.textState_ = null; +/** + * Return the point at the specified index. + * @param {number} index Index. + * @return {ol.geom.Point} Point. + * @api stable + */ +ol.geom.MultiPoint.prototype.getPoint = function(index) { + var n = goog.isNull(this.flatCoordinates) ? + 0 : this.flatCoordinates.length / this.stride; + goog.asserts.assert(0 <= index && index < n, + 'index should be in between 0 and n'); + if (index < 0 || n <= index) { + return null; + } + var point = new ol.geom.Point(null); + point.setFlatCoordinates(this.layout, this.flatCoordinates.slice( + index * this.stride, (index + 1) * this.stride)); + return point; }; -goog.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay); /** - * @inheritDoc + * Return the points of this multipoint. + * @return {Array.<ol.geom.Point>} Points. + * @api stable */ -ol.render.canvas.TextReplay.prototype.drawText = - function(flatCoordinates, offset, end, stride, geometry, feature) { - if (this.text_ === '' || - goog.isNull(this.textState_) || - (goog.isNull(this.textFillState_) && - goog.isNull(this.textStrokeState_))) { - return; - } - if (!goog.isNull(this.textFillState_)) { - this.setReplayFillState_(this.textFillState_); - } - if (!goog.isNull(this.textStrokeState_)) { - this.setReplayStrokeState_(this.textStrokeState_); +ol.geom.MultiPoint.prototype.getPoints = function() { + var flatCoordinates = this.flatCoordinates; + var layout = this.layout; + var stride = this.stride; + /** @type {Array.<ol.geom.Point>} */ + var points = []; + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + var point = new ol.geom.Point(null); + point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride)); + points.push(point); } - this.setReplayTextState_(this.textState_); - this.beginGeometry(geometry, feature); - var myBegin = this.coordinates.length; - var myEnd = - this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false); - var fill = !goog.isNull(this.textFillState_); - var stroke = !goog.isNull(this.textStrokeState_); - var drawTextInstruction = [ - ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_, - this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_, - fill, stroke]; - this.instructions.push(drawTextInstruction); - this.hitDetectionInstructions.push(drawTextInstruction); - this.endGeometry(geometry, feature); + return points; }; /** - * @param {ol.render.canvas.FillState} fillState Fill state. - * @private + * @inheritDoc + * @api stable */ -ol.render.canvas.TextReplay.prototype.setReplayFillState_ = - function(fillState) { - var replayFillState = this.replayFillState_; - if (!goog.isNull(replayFillState) && - replayFillState.fillStyle == fillState.fillStyle) { - return; - } - var setFillStyleInstruction = - [ol.render.canvas.Instruction.SET_FILL_STYLE, fillState.fillStyle]; - this.instructions.push(setFillStyleInstruction); - this.hitDetectionInstructions.push(setFillStyleInstruction); - if (goog.isNull(replayFillState)) { - this.replayFillState_ = { - fillStyle: fillState.fillStyle - }; - } else { - replayFillState.fillStyle = fillState.fillStyle; - } +ol.geom.MultiPoint.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_POINT; }; /** - * @param {ol.render.canvas.StrokeState} strokeState Stroke state. - * @private + * @inheritDoc + * @api stable */ -ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = - function(strokeState) { - var replayStrokeState = this.replayStrokeState_; - if (!goog.isNull(replayStrokeState) && - replayStrokeState.lineCap == strokeState.lineCap && - replayStrokeState.lineDash == strokeState.lineDash && - replayStrokeState.lineJoin == strokeState.lineJoin && - replayStrokeState.lineWidth == strokeState.lineWidth && - replayStrokeState.miterLimit == strokeState.miterLimit && - replayStrokeState.strokeStyle == strokeState.strokeStyle) { - return; - } - var setStrokeStyleInstruction = [ - ol.render.canvas.Instruction.SET_STROKE_STYLE, strokeState.strokeStyle, - strokeState.lineWidth, strokeState.lineCap, strokeState.lineJoin, - strokeState.miterLimit, strokeState.lineDash, false - ]; - this.instructions.push(setStrokeStyleInstruction); - this.hitDetectionInstructions.push(setStrokeStyleInstruction); - if (goog.isNull(replayStrokeState)) { - this.replayStrokeState_ = { - lineCap: strokeState.lineCap, - lineDash: strokeState.lineDash, - lineJoin: strokeState.lineJoin, - lineWidth: strokeState.lineWidth, - miterLimit: strokeState.miterLimit, - strokeStyle: strokeState.strokeStyle - }; - } else { - replayStrokeState.lineCap = strokeState.lineCap; - replayStrokeState.lineDash = strokeState.lineDash; - replayStrokeState.lineJoin = strokeState.lineJoin; - replayStrokeState.lineWidth = strokeState.lineWidth; - replayStrokeState.miterLimit = strokeState.miterLimit; - replayStrokeState.strokeStyle = strokeState.strokeStyle; +ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) { + var flatCoordinates = this.flatCoordinates; + var stride = this.stride; + var i, ii, x, y; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + x = flatCoordinates[i]; + y = flatCoordinates[i + 1]; + if (ol.extent.containsXY(extent, x, y)) { + return true; + } } + return false; }; /** - * @param {ol.render.canvas.TextState} textState Text state. - * @private + * Set the coordinates of the multipoint. + * @param {Array.<ol.Coordinate>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.render.canvas.TextReplay.prototype.setReplayTextState_ = - function(textState) { - var replayTextState = this.replayTextState_; - if (!goog.isNull(replayTextState) && - replayTextState.font == textState.font && - replayTextState.textAlign == textState.textAlign && - replayTextState.textBaseline == textState.textBaseline) { - return; - } - var setTextStyleInstruction = [ol.render.canvas.Instruction.SET_TEXT_STYLE, - textState.font, textState.textAlign, textState.textBaseline]; - this.instructions.push(setTextStyleInstruction); - this.hitDetectionInstructions.push(setTextStyleInstruction); - if (goog.isNull(replayTextState)) { - this.replayTextState_ = { - font: textState.font, - textAlign: textState.textAlign, - textBaseline: textState.textBaseline - }; +ol.geom.MultiPoint.prototype.setCoordinates = + function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); } else { - replayTextState.font = textState.font; - replayTextState.textAlign = textState.textAlign; - replayTextState.textBaseline = textState.textBaseline; + this.setLayout(opt_layout, coordinates, 1); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( + this.flatCoordinates, 0, coordinates, this.stride); + this.changed(); } }; /** - * @inheritDoc + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. */ -ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { - if (goog.isNull(textStyle)) { - this.text_ = ''; - } else { - var textFillStyle = textStyle.getFill(); - if (goog.isNull(textFillStyle)) { - this.textFillState_ = null; - } else { - var textFillStyleColor = textFillStyle.getColor(); - var fillStyle = ol.color.asString(!goog.isNull(textFillStyleColor) ? - textFillStyleColor : ol.render.canvas.defaultFillStyle); - if (goog.isNull(this.textFillState_)) { - this.textFillState_ = { - fillStyle: fillStyle - }; - } else { - var textFillState = this.textFillState_; - textFillState.fillStyle = fillStyle; - } - } - var textStrokeStyle = textStyle.getStroke(); - if (goog.isNull(textStrokeStyle)) { - this.textStrokeState_ = null; - } else { - var textStrokeStyleColor = textStrokeStyle.getColor(); - var textStrokeStyleLineCap = textStrokeStyle.getLineCap(); - var textStrokeStyleLineDash = textStrokeStyle.getLineDash(); - var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin(); - var textStrokeStyleWidth = textStrokeStyle.getWidth(); - var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit(); - var lineCap = goog.isDef(textStrokeStyleLineCap) ? - textStrokeStyleLineCap : ol.render.canvas.defaultLineCap; - var lineDash = goog.isDefAndNotNull(textStrokeStyleLineDash) ? - textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash; - var lineJoin = goog.isDef(textStrokeStyleLineJoin) ? - textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin; - var lineWidth = goog.isDef(textStrokeStyleWidth) ? - textStrokeStyleWidth : ol.render.canvas.defaultLineWidth; - var miterLimit = goog.isDef(textStrokeStyleMiterLimit) ? - textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit; - var strokeStyle = ol.color.asString(!goog.isNull(textStrokeStyleColor) ? - textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle); - if (goog.isNull(this.textStrokeState_)) { - this.textStrokeState_ = { - lineCap: lineCap, - lineDash: lineDash, - lineJoin: lineJoin, - lineWidth: lineWidth, - miterLimit: miterLimit, - strokeStyle: strokeStyle - }; - } else { - var textStrokeState = this.textStrokeState_; - textStrokeState.lineCap = lineCap; - textStrokeState.lineDash = lineDash; - textStrokeState.lineJoin = lineJoin; - textStrokeState.lineWidth = lineWidth; - textStrokeState.miterLimit = miterLimit; - textStrokeState.strokeStyle = strokeStyle; - } - } - var textFont = textStyle.getFont(); - var textOffsetX = textStyle.getOffsetX(); - var textOffsetY = textStyle.getOffsetY(); - var textRotation = textStyle.getRotation(); - var textScale = textStyle.getScale(); - var textText = textStyle.getText(); - var textTextAlign = textStyle.getTextAlign(); - var textTextBaseline = textStyle.getTextBaseline(); - var font = goog.isDef(textFont) ? - textFont : ol.render.canvas.defaultFont; - var textAlign = goog.isDef(textTextAlign) ? - textTextAlign : ol.render.canvas.defaultTextAlign; - var textBaseline = goog.isDef(textTextBaseline) ? - textTextBaseline : ol.render.canvas.defaultTextBaseline; - if (goog.isNull(this.textState_)) { - this.textState_ = { - font: font, - textAlign: textAlign, - textBaseline: textBaseline - }; - } else { - var textState = this.textState_; - textState.font = font; - textState.textAlign = textAlign; - textState.textBaseline = textBaseline; - } - this.text_ = goog.isDef(textText) ? textText : ''; - this.textOffsetX_ = goog.isDef(textOffsetX) ? textOffsetX : 0; - this.textOffsetY_ = goog.isDef(textOffsetY) ? textOffsetY : 0; - this.textRotation_ = goog.isDef(textRotation) ? textRotation : 0; - this.textScale_ = goog.isDef(textScale) ? textScale : 1; +ol.geom.MultiPoint.prototype.setFlatCoordinates = + function(layout, flatCoordinates) { + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.changed(); +}; + +goog.provide('ol.geom.flat.center'); + +goog.require('ol.extent'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {Array.<Array.<number>>} endss Endss. + * @param {number} stride Stride. + * @return {Array.<number>} Flat centers. + */ +ol.geom.flat.center.linearRingss = + function(flatCoordinates, offset, endss, stride) { + var flatCenters = []; + var i, ii; + var extent = ol.extent.createEmpty(); + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; + extent = ol.extent.createOrUpdateFromFlatCoordinates( + flatCoordinates, offset, ends[0], stride); + flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2); + offset = ends[ends.length - 1]; } + return flatCenters; }; +goog.provide('ol.geom.MultiPolygon'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.area'); +goog.require('ol.geom.flat.center'); +goog.require('ol.geom.flat.closest'); +goog.require('ol.geom.flat.contains'); +goog.require('ol.geom.flat.deflate'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.geom.flat.interiorpoint'); +goog.require('ol.geom.flat.intersectsextent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.simplify'); + /** + * @classdesc + * Multi-polygon geometry. + * * @constructor - * @implements {ol.render.IReplayGroup} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @param {number} resolution Resolution. - * @struct + * @extends {ol.geom.SimpleGeometry} + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.render.canvas.ReplayGroup = function(tolerance, maxExtent, resolution) { +ol.geom.MultiPolygon = function(coordinates, opt_layout) { + + goog.base(this); + + /** + * @type {Array.<Array.<number>>} + * @private + */ + this.endss_ = []; /** * @private * @type {number} */ - this.tolerance_ = tolerance; + this.flatInteriorPointsRevision_ = -1; /** * @private - * @type {ol.Extent} + * @type {Array.<number>} */ - this.maxExtent_ = maxExtent; + this.flatInteriorPoints_ = null; /** * @private * @type {number} */ - this.resolution_ = resolution; + this.maxDelta_ = -1; /** * @private - * @type {Object.<string, - * Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} + * @type {number} */ - this.replaysByZIndex_ = {}; + this.maxDeltaRevision_ = -1; /** * @private - * @type {CanvasRenderingContext2D} + * @type {number} */ - this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1); + this.orientedRevision_ = -1; /** * @private - * @type {!goog.vec.Mat4.Number} + * @type {Array.<number>} */ - this.hitDetectionTransform_ = goog.vec.Mat4.createNumber(); + this.orientedFlatCoordinates_ = null; + + this.setCoordinates(coordinates, + /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); }; +goog.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry); /** - * FIXME empty description for jsdoc + * Append the passed polygon to this multipolygon. + * @param {ol.geom.Polygon} polygon Polygon. + * @api stable */ -ol.render.canvas.ReplayGroup.prototype.finish = function() { - var zKey; - for (zKey in this.replaysByZIndex_) { - var replays = this.replaysByZIndex_[zKey]; - var replayKey; - for (replayKey in replays) { - replays[replayKey].finish(); +ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) { + goog.asserts.assert(polygon.getLayout() == this.layout, + 'layout of polygon should match layout'); + /** @type {Array.<number>} */ + var ends; + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = polygon.getFlatCoordinates().slice(); + ends = polygon.getEnds().slice(); + this.endss_.push(); + } else { + var offset = this.flatCoordinates.length; + goog.array.extend(this.flatCoordinates, polygon.getFlatCoordinates()); + ends = polygon.getEnds().slice(); + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + ends[i] += offset; } } + this.endss_.push(ends); + this.changed(); }; /** - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {Object} skippedFeaturesHash Ids of features to skip - * @param {function(ol.Feature): T} callback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function( - resolution, rotation, coordinate, skippedFeaturesHash, callback) { - - var transform = this.hitDetectionTransform_; - ol.vec.Mat4.makeTransform2D(transform, 0.5, 0.5, - 1 / resolution, -1 / resolution, -rotation, - -coordinate[0], -coordinate[1]); - - var context = this.hitDetectionContext_; - context.clearRect(0, 0, 1, 1); - - return this.replayHitDetection_(context, transform, rotation, - skippedFeaturesHash, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - var imageData = context.getImageData(0, 0, 1, 1).data; - if (imageData[3] > 0) { - var result = callback(feature); - if (result) { - return result; - } - context.clearRect(0, 0, 1, 1); - } - }); -}; - - -/** - * @inheritDoc + * Make a complete copy of the geometry. + * @return {!ol.geom.MultiPolygon} Clone. + * @api stable */ -ol.render.canvas.ReplayGroup.prototype.getReplay = - function(zIndex, replayType) { - var zIndexKey = goog.isDef(zIndex) ? zIndex.toString() : '0'; - var replays = this.replaysByZIndex_[zIndexKey]; - if (!goog.isDef(replays)) { - replays = {}; - this.replaysByZIndex_[zIndexKey] = replays; - } - var replay = replays[replayType]; - if (!goog.isDef(replay)) { - var Constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType]; - goog.asserts.assert(goog.isDef(Constructor)); - replay = new Constructor(this.tolerance_, this.maxExtent_, - this.resolution_); - replays[replayType] = replay; - } - return replay; +ol.geom.MultiPolygon.prototype.clone = function() { + var multiPolygon = new ol.geom.MultiPolygon(null); + var newEndss = /** @type {Array.<Array.<number>>} */ + (goog.object.unsafeClone(this.endss_)); + multiPolygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(), newEndss); + return multiPolygon; }; /** * @inheritDoc */ -ol.render.canvas.ReplayGroup.prototype.isEmpty = function() { - return goog.object.isEmpty(this.replaysByZIndex_); -}; - - -/** - * @param {CanvasRenderingContext2D} context Context. - * @param {number} pixelRatio Pixel ratio. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features to skip - */ -ol.render.canvas.ReplayGroup.prototype.replay = function( - context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { - - /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number); - goog.array.sort(zs); - - var maxExtent = this.maxExtent_; - var minX = maxExtent[0]; - var minY = maxExtent[1]; - var maxX = maxExtent[2]; - var maxY = maxExtent[3]; - var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; - ol.geom.flat.transform.transform2D( - flatClipCoords, 0, 8, 2, transform, flatClipCoords); - context.save(); - context.beginPath(); - context.moveTo(flatClipCoords[0], flatClipCoords[1]); - context.lineTo(flatClipCoords[2], flatClipCoords[3]); - context.lineTo(flatClipCoords[4], flatClipCoords[5]); - context.lineTo(flatClipCoords[6], flatClipCoords[7]); - context.closePath(); - context.clip(); - - var i, ii, j, jj, replays, replay, result; - for (i = 0, ii = zs.length; i < ii; ++i) { - replays = this.replaysByZIndex_[zs[i].toString()]; - for (j = 0, jj = ol.render.REPLAY_ORDER.length; j < jj; ++j) { - replay = replays[ol.render.REPLAY_ORDER[j]]; - if (goog.isDef(replay)) { - replay.replay(context, pixelRatio, transform, viewRotation, - skippedFeaturesHash); - } - } +ol.geom.MultiPolygon.prototype.closestPointXY = + function(x, y, closestPoint, minSquaredDistance) { + if (minSquaredDistance < + ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { + return minSquaredDistance; } - - context.restore(); -}; - - -/** - * @private - * @param {CanvasRenderingContext2D} context Context. - * @param {goog.vec.Mat4.Number} transform Transform. - * @param {number} viewRotation View rotation. - * @param {Object} skippedFeaturesHash Ids of features to skip - * @param {function(ol.Feature): T} featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( - context, transform, viewRotation, skippedFeaturesHash, - featureCallback) { - /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number); - goog.array.sort(zs, function(a, b) { return b - a; }); - - var i, ii, j, replays, replay, result; - for (i = 0, ii = zs.length; i < ii; ++i) { - replays = this.replaysByZIndex_[zs[i].toString()]; - for (j = ol.render.REPLAY_ORDER.length - 1; j >= 0; --j) { - replay = replays[ol.render.REPLAY_ORDER[j]]; - if (goog.isDef(replay)) { - result = replay.replayHitDetection(context, transform, viewRotation, - skippedFeaturesHash, featureCallback); - if (result) { - return result; - } - } - } + if (this.maxDeltaRevision_ != this.getRevision()) { + this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta( + this.flatCoordinates, 0, this.endss_, this.stride, 0)); + this.maxDeltaRevision_ = this.getRevision(); } - return undefined; -}; - - -/** - * @const - * @private - * @type {Object.<ol.render.ReplayType, - * function(new: ol.render.canvas.Replay, number, ol.Extent, - * number)>} - */ -ol.render.canvas.BATCH_CONSTRUCTORS_ = { - 'Image': ol.render.canvas.ImageReplay, - 'LineString': ol.render.canvas.LineStringReplay, - 'Polygon': ol.render.canvas.PolygonReplay, - 'Text': ol.render.canvas.TextReplay -}; - -goog.provide('ol.renderer.canvas.Layer'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.vec.Mat4'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.layer.Layer'); -goog.require('ol.render.Event'); -goog.require('ol.render.EventType'); -goog.require('ol.render.canvas.Immediate'); -goog.require('ol.renderer.Layer'); -goog.require('ol.vec.Mat4'); + return ol.geom.flat.closest.getssClosestPoint( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, + this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); +}; +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.containsXY = function(x, y) { + return ol.geom.flat.contains.linearRingssContainsXY( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y); +}; + /** - * @constructor - * @extends {ol.renderer.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Layer} layer Layer. + * Return the area of the multipolygon on projected plane. + * @return {number} Area (on projected plane). + * @api stable */ -ol.renderer.canvas.Layer = function(mapRenderer, layer) { +ol.geom.MultiPolygon.prototype.getArea = function() { + return ol.geom.flat.area.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride); +}; - goog.base(this, mapRenderer, layer); - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.transform_ = goog.vec.Mat4.createNumber(); +/** + * Get the coordinate array for this geometry. This array has the structure + * of a GeoJSON coordinate array for multi-polygons. + * + * @param {boolean=} opt_right Orient coordinates according to the right-hand + * rule (counter-clockwise for exterior and clockwise for interior rings). + * If `false`, coordinates will be oriented according to the left-hand rule + * (clockwise for exterior and counter-clockwise for interior rings). + * By default, coordinate orientation will depend on how the geometry was + * constructed. + * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getCoordinates = function(opt_right) { + var flatCoordinates; + if (goog.isDef(opt_right)) { + flatCoordinates = this.getOrientedFlatCoordinates().slice(); + ol.geom.flat.orient.orientLinearRingss( + flatCoordinates, 0, this.endss_, this.stride, opt_right); + } else { + flatCoordinates = this.flatCoordinates; + } + return ol.geom.flat.inflate.coordinatesss( + flatCoordinates, 0, this.endss_, this.stride); }; -goog.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer); /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.layer.LayerState} layerState Layer state. - * @param {CanvasRenderingContext2D} context Context. + * @return {Array.<Array.<number>>} Endss. */ -ol.renderer.canvas.Layer.prototype.composeFrame = - function(frameState, layerState, context) { - - this.dispatchPreComposeEvent(context, frameState); +ol.geom.MultiPolygon.prototype.getEndss = function() { + return this.endss_; +}; - var image = this.getImage(); - if (!goog.isNull(image)) { - // clipped rendering if layer extent is set - var extent = layerState.extent; - var clipped = goog.isDef(extent); - if (clipped) { - goog.asserts.assert(goog.isDef(extent)); - var topLeft = ol.extent.getTopLeft(extent); - var topRight = ol.extent.getTopRight(extent); - var bottomRight = ol.extent.getBottomRight(extent); - var bottomLeft = ol.extent.getBottomLeft(extent); +/** + * @return {Array.<number>} Flat interior points. + */ +ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() { + if (this.flatInteriorPointsRevision_ != this.getRevision()) { + var flatCenters = ol.geom.flat.center.linearRingss( + this.flatCoordinates, 0, this.endss_, this.stride); + this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, + flatCenters); + this.flatInteriorPointsRevision_ = this.getRevision(); + } + return this.flatInteriorPoints_; +}; - ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, - topLeft, topLeft); - ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, - topRight, topRight); - ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, - bottomRight, bottomRight); - ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, - bottomLeft, bottomLeft); - context.save(); - context.beginPath(); - context.moveTo(topLeft[0], topLeft[1]); - context.lineTo(topRight[0], topRight[1]); - context.lineTo(bottomRight[0], bottomRight[1]); - context.lineTo(bottomLeft[0], bottomLeft[1]); - context.clip(); - } +/** + * Return the interior points as {@link ol.geom.MultiPoint multipoint}. + * @return {ol.geom.MultiPoint} Interior points. + * @api stable + */ +ol.geom.MultiPolygon.prototype.getInteriorPoints = function() { + var interiorPoints = new ol.geom.MultiPoint(null); + interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY, + this.getFlatInteriorPoints().slice()); + return interiorPoints; +}; - var imageTransform = this.getImageTransform(); - // for performance reasons, context.save / context.restore is not used - // to save and restore the transformation matrix and the opacity. - // see http://jsperf.com/context-save-restore-versus-variable - var alpha = context.globalAlpha; - context.globalAlpha = layerState.opacity; - // for performance reasons, context.setTransform is only used - // when the view is rotated. see http://jsperf.com/canvas-transform - if (frameState.viewState.rotation === 0) { - var dx = goog.vec.Mat4.getElement(imageTransform, 0, 3); - var dy = goog.vec.Mat4.getElement(imageTransform, 1, 3); - var dw = image.width * goog.vec.Mat4.getElement(imageTransform, 0, 0); - var dh = image.height * goog.vec.Mat4.getElement(imageTransform, 1, 1); - context.drawImage(image, 0, 0, +image.width, +image.height, - Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); +/** + * @return {Array.<number>} Oriented flat coordinates. + */ +ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() { + if (this.orientedRevision_ != this.getRevision()) { + var flatCoordinates = this.flatCoordinates; + if (ol.geom.flat.orient.linearRingssAreOriented( + flatCoordinates, 0, this.endss_, this.stride)) { + this.orientedFlatCoordinates_ = flatCoordinates; } else { - context.setTransform( - goog.vec.Mat4.getElement(imageTransform, 0, 0), - goog.vec.Mat4.getElement(imageTransform, 1, 0), - goog.vec.Mat4.getElement(imageTransform, 0, 1), - goog.vec.Mat4.getElement(imageTransform, 1, 1), - goog.vec.Mat4.getElement(imageTransform, 0, 3), - goog.vec.Mat4.getElement(imageTransform, 1, 3)); - context.drawImage(image, 0, 0); - context.setTransform(1, 0, 0, 1, 0, 0); - } - context.globalAlpha = alpha; - - if (clipped) { - context.restore(); + this.orientedFlatCoordinates_ = flatCoordinates.slice(); + this.orientedFlatCoordinates_.length = + ol.geom.flat.orient.orientLinearRingss( + this.orientedFlatCoordinates_, 0, this.endss_, this.stride); } + this.orientedRevision_ = this.getRevision(); } + return this.orientedFlatCoordinates_; +}; - this.dispatchPostComposeEvent(context, frameState); +/** + * @inheritDoc + */ +ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = + function(squaredTolerance) { + var simplifiedFlatCoordinates = []; + var simplifiedEndss = []; + simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess( + this.flatCoordinates, 0, this.endss_, this.stride, + Math.sqrt(squaredTolerance), + simplifiedFlatCoordinates, 0, simplifiedEndss); + var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null); + simplifiedMultiPolygon.setFlatCoordinates( + ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss); + return simplifiedMultiPolygon; }; /** - * @param {ol.render.EventType} type Event type. - * @param {CanvasRenderingContext2D} context Context. - * @param {olx.FrameState} frameState Frame state. - * @param {goog.vec.Mat4.Number=} opt_transform Transform. - * @private + * Return the polygon at the specified index. + * @param {number} index Index. + * @return {ol.geom.Polygon} Polygon. + * @api stable */ -ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = - function(type, context, frameState, opt_transform) { - var layer = this.getLayer(); - if (layer.hasListener(type)) { - var transform = goog.isDef(opt_transform) ? - opt_transform : this.getTransform(frameState); - var render = new ol.render.canvas.Immediate( - context, frameState.pixelRatio, frameState.extent, transform, - frameState.viewState.rotation); - var composeEvent = new ol.render.Event(type, layer, render, null, - frameState, context, null); - layer.dispatchEvent(composeEvent); - render.flush(); +ol.geom.MultiPolygon.prototype.getPolygon = function(index) { + goog.asserts.assert(0 <= index && index < this.endss_.length, + 'index should be in between 0 and the length of this.endss_'); + if (index < 0 || this.endss_.length <= index) { + return null; + } + var offset; + if (index === 0) { + offset = 0; + } else { + var prevEnds = this.endss_[index - 1]; + offset = prevEnds[prevEnds.length - 1]; + } + var ends = this.endss_[index].slice(); + var end = ends[ends.length - 1]; + if (offset !== 0) { + var i, ii; + for (i = 0, ii = ends.length; i < ii; ++i) { + ends[i] -= offset; + } } + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + this.layout, this.flatCoordinates.slice(offset, end), ends); + return polygon; }; /** - * @param {CanvasRenderingContext2D} context Context. - * @param {olx.FrameState} frameState Frame state. - * @param {goog.vec.Mat4.Number=} opt_transform Transform. - * @protected + * Return the polygons of this multipolygon. + * @return {Array.<ol.geom.Polygon>} Polygons. + * @api stable */ -ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent = - function(context, frameState, opt_transform) { - this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context, - frameState, opt_transform); +ol.geom.MultiPolygon.prototype.getPolygons = function() { + var layout = this.layout; + var flatCoordinates = this.flatCoordinates; + var endss = this.endss_; + var polygons = []; + var offset = 0; + var i, ii, j, jj; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i].slice(); + var end = ends[ends.length - 1]; + if (offset !== 0) { + for (j = 0, jj = ends.length; j < jj; ++j) { + ends[j] -= offset; + } + } + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates( + layout, flatCoordinates.slice(offset, end), ends); + polygons.push(polygon); + offset = end; + } + return polygons; }; /** - * @param {CanvasRenderingContext2D} context Context. - * @param {olx.FrameState} frameState Frame state. - * @param {goog.vec.Mat4.Number=} opt_transform Transform. - * @protected + * @inheritDoc + * @api stable */ -ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent = - function(context, frameState, opt_transform) { - this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context, - frameState, opt_transform); +ol.geom.MultiPolygon.prototype.getType = function() { + return ol.geom.GeometryType.MULTI_POLYGON; }; /** - * @param {CanvasRenderingContext2D} context Context. - * @param {olx.FrameState} frameState Frame state. - * @param {goog.vec.Mat4.Number=} opt_transform Transform. - * @protected + * @inheritDoc + * @api stable */ -ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = - function(context, frameState, opt_transform) { - this.dispatchComposeEvent_(ol.render.EventType.RENDER, context, - frameState, opt_transform); +ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) { + return ol.geom.flat.intersectsextent.linearRingss( + this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent); }; /** - * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas. + * Set the coordinates of the multipolygon. + * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. + * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @api stable */ -ol.renderer.canvas.Layer.prototype.getImage = goog.abstractMethod; +ol.geom.MultiPolygon.prototype.setCoordinates = + function(coordinates, opt_layout) { + if (goog.isNull(coordinates)) { + this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_); + } else { + this.setLayout(opt_layout, coordinates, 3); + if (goog.isNull(this.flatCoordinates)) { + this.flatCoordinates = []; + } + var endss = ol.geom.flat.deflate.coordinatesss( + this.flatCoordinates, 0, coordinates, this.stride, this.endss_); + if (endss.length === 0) { + this.flatCoordinates.length = 0; + } else { + var lastEnds = endss[endss.length - 1]; + this.flatCoordinates.length = lastEnds.length === 0 ? + 0 : lastEnds[lastEnds.length - 1]; + } + this.changed(); + } +}; /** - * @return {!goog.vec.Mat4.Number} Image transform. + * @param {ol.geom.GeometryLayout} layout Layout. + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Array.<Array.<number>>} endss Endss. */ -ol.renderer.canvas.Layer.prototype.getImageTransform = goog.abstractMethod; +ol.geom.MultiPolygon.prototype.setFlatCoordinates = + function(layout, flatCoordinates, endss) { + goog.asserts.assert(!goog.isNull(endss), 'endss cannot be null'); + if (goog.isNull(flatCoordinates) || flatCoordinates.length === 0) { + goog.asserts.assert(endss.length === 0, 'the length of endss should be 0'); + } else { + goog.asserts.assert(endss.length > 0, 'endss cannot be an empty array'); + var ends = endss[endss.length - 1]; + goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1], + 'the length of flatCoordinates should be the last value of ends'); + } + this.setFlatCoordinatesInternal(layout, flatCoordinates); + this.endss_ = endss; + this.changed(); +}; /** - * @param {olx.FrameState} frameState Frame state. - * @protected - * @return {!goog.vec.Mat4.Number} Transform. + * @param {Array.<ol.geom.Polygon>} polygons Polygons. */ -ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) { - var viewState = frameState.viewState; - var pixelRatio = frameState.pixelRatio; - return ol.vec.Mat4.makeTransform2D(this.transform_, - pixelRatio * frameState.size[0] / 2, - pixelRatio * frameState.size[1] / 2, - pixelRatio / viewState.resolution, - -pixelRatio / viewState.resolution, - -viewState.rotation, - -viewState.center[0], -viewState.center[1]); +ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) { + var layout = ol.geom.GeometryLayout.XY; + var flatCoordinates = []; + var endss = []; + var i, ii, ends; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var polygon = polygons[i]; + if (i === 0) { + layout = polygon.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + goog.asserts.assert(polygon.getLayout() == layout, + 'layout of polygon should be layout'); + } + var offset = flatCoordinates.length; + ends = polygon.getEnds(); + var j, jj; + for (j = 0, jj = ends.length; j < jj; ++j) { + ends[j] += offset; + } + goog.array.extend(flatCoordinates, polygon.getFlatCoordinates()); + endss.push(ends); + } + this.setFlatCoordinates(layout, flatCoordinates, endss); }; +goog.provide('ol.renderer.vector'); -/** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.layer.LayerState} layerState Layer state. - * @return {boolean} whether composeFrame should be called. - */ -ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod; +goog.require('goog.asserts'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.render.IReplayGroup'); +goog.require('ol.style.ImageState'); +goog.require('ol.style.Style'); /** - * @param {ol.Size} size Size. - * @return {boolean} True when the canvas with the current size does not exceed - * the maximum dimensions. + * @param {ol.Feature} feature1 Feature 1. + * @param {ol.Feature} feature2 Feature 2. + * @return {number} Order. */ -ol.renderer.canvas.Layer.testCanvasSize = (function() { +ol.renderer.vector.defaultOrder = function(feature1, feature2) { + return goog.getUid(feature1) - goog.getUid(feature2); +}; - /** - * @type {CanvasRenderingContext2D} - */ - var context = null; - /** - * @type {ImageData} - */ - var imageData = null; +/** + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @return {number} Squared pixel tolerance. + */ +ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) { + var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); + return tolerance * tolerance; +}; - return function(size) { - if (goog.isNull(context)) { - context = ol.dom.createCanvasContext2D(1, 1); - imageData = context.createImageData(1, 1); - var data = imageData.data; - data[0] = 42; - data[1] = 84; - data[2] = 126; - data[3] = 255; - } - var canvas = context.canvas; - var good = size[0] <= canvas.width && size[1] <= canvas.height; - if (!good) { - canvas.width = size[0]; - canvas.height = size[1]; - var x = size[0] - 1; - var y = size[1] - 1; - context.putImageData(imageData, x, y); - var result = context.getImageData(x, y, 1, 1); - good = goog.array.equals(imageData.data, result.data); - } - return good; - }; -})(); -goog.provide('ol.renderer.canvas.ImageLayer'); +/** + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @return {number} Pixel tolerance. + */ +ol.renderer.vector.getTolerance = function(resolution, pixelRatio) { + return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio; +}; -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.vec.Mat4'); -goog.require('ol.ImageBase'); -goog.require('ol.ImageState'); -goog.require('ol.ViewHint'); -goog.require('ol.extent'); -goog.require('ol.layer.Image'); -goog.require('ol.proj'); -goog.require('ol.renderer.Map'); -goog.require('ol.renderer.canvas.Layer'); -goog.require('ol.vec.Mat4'); +/** + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderCircleGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.Circle, + 'geometry should be an ol.geom.Circle'); + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawCircleGeometry(geometry, feature); + } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature); + } +}; /** - * @constructor - * @extends {ol.renderer.canvas.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Image} imageLayer Single image layer. + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @param {number} squaredTolerance Squared tolerance. + * @param {function(this: T, goog.events.Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {boolean} `true` if style is loading. + * @template T */ -ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) { +ol.renderer.vector.renderFeature = function( + replayGroup, feature, style, squaredTolerance, listener, thisArg) { + var loading = false; + var imageStyle, imageState; + imageStyle = style.getImage(); + if (!goog.isNull(imageStyle)) { + imageState = imageStyle.getImageState(); + if (imageState == ol.style.ImageState.LOADED || + imageState == ol.style.ImageState.ERROR) { + imageStyle.unlistenImageChange(listener, thisArg); + } else { + if (imageState == ol.style.ImageState.IDLE) { + imageStyle.load(); + } + imageState = imageStyle.getImageState(); + goog.asserts.assert(imageState == ol.style.ImageState.LOADING, + 'imageState should be LOADING'); + imageStyle.listenImageChange(listener, thisArg); + loading = true; + } + } + ol.renderer.vector.renderFeature_(replayGroup, feature, style, + squaredTolerance); + return loading; +}; - goog.base(this, mapRenderer, imageLayer); - /** - * @private - * @type {?ol.ImageBase} - */ - this.image_ = null; +/** + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.Feature} feature Feature. + * @param {ol.style.Style} style Style. + * @param {number} squaredTolerance Squared tolerance. + * @private + */ +ol.renderer.vector.renderFeature_ = function( + replayGroup, feature, style, squaredTolerance) { + var geometry = style.getGeometryFunction()(feature); + if (!goog.isDefAndNotNull(geometry)) { + return; + } + var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); + var geometryRenderer = + ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()]; + goog.asserts.assert(goog.isDef(geometryRenderer), + 'geometryRenderer should be defined'); + geometryRenderer(replayGroup, simplifiedGeometry, style, feature); +}; - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.imageTransform_ = goog.vec.Mat4.createNumber(); +/** + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderGeometryCollectionGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection, + 'geometry should be an ol.geom.GeometryCollection'); + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometryRenderer = + ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()]; + goog.asserts.assert(goog.isDef(geometryRenderer), + 'geometryRenderer should be defined'); + geometryRenderer(replayGroup, geometries[i], style, feature); + } }; -goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer); /** - * @inheritDoc + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private */ -ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg) { - var layer = this.getLayer(); - var source = layer.getSource(); - var resolution = frameState.viewState.resolution; - var rotation = frameState.viewState.rotation; - var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtPixel( - resolution, rotation, coordinate, skippedFeatureUids, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - return callback.call(thisArg, feature, layer); - }); +ol.renderer.vector.renderLineStringGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + var strokeStyle = style.getStroke(); + if (!goog.isNull(strokeStyle)) { + var lineStringReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.LINE_STRING); + lineStringReplay.setFillStrokeStyle(null, strokeStyle); + lineStringReplay.drawLineStringGeometry(geometry, feature); + } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature); + } }; /** - * @inheritDoc + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private */ -ol.renderer.canvas.ImageLayer.prototype.getImage = function() { - return goog.isNull(this.image_) ? - null : this.image_.getImage(); +ol.renderer.vector.renderMultiLineStringGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, + 'geometry should be an ol.geom.MultiLineString'); + var strokeStyle = style.getStroke(); + if (!goog.isNull(strokeStyle)) { + var lineStringReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.LINE_STRING); + lineStringReplay.setFillStrokeStyle(null, strokeStyle); + lineStringReplay.drawMultiLineStringGeometry(geometry, feature); + } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatMidpointCoordinates = geometry.getFlatMidpoints(); + textReplay.drawText(flatMidpointCoordinates, 0, + flatMidpointCoordinates.length, 2, geometry, feature); + } }; /** - * @inheritDoc + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private */ -ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() { - return this.imageTransform_; +ol.renderer.vector.renderMultiPolygonGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon, + 'geometry should be an ol.geom.MultiPolygon'); + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (!goog.isNull(strokeStyle) || !goog.isNull(fillStyle)) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawMultiPolygonGeometry(geometry, feature); + } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints(); + textReplay.drawText(flatInteriorPointCoordinates, 0, + flatInteriorPointCoordinates.length, 2, geometry, feature); + } }; /** - * @inheritDoc + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private */ -ol.renderer.canvas.ImageLayer.prototype.prepareFrame = - function(frameState, layerState) { +ol.renderer.vector.renderPointGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + var imageStyle = style.getImage(); + if (!goog.isNull(imageStyle)) { + if (imageStyle.getImageState() != ol.style.ImageState.LOADED) { + return; + } + var imageReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.IMAGE); + imageReplay.setImageStyle(imageStyle); + imageReplay.drawPointGeometry(geometry, feature); + } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText(geometry.getCoordinates(), 0, 2, 2, geometry, feature); + } +}; - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var viewCenter = viewState.center; - var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; - var image; - var imageLayer = this.getLayer(); - goog.asserts.assertInstanceof(imageLayer, ol.layer.Image); - var imageSource = imageLayer.getSource(); +/** + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderMultiPointGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint, + 'geometry should be an ol.goem.MultiPoint'); + var imageStyle = style.getImage(); + if (!goog.isNull(imageStyle)) { + if (imageStyle.getImageState() != ol.style.ImageState.LOADED) { + return; + } + var imageReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.IMAGE); + imageReplay.setImageStyle(imageStyle); + imageReplay.drawMultiPointGeometry(geometry, feature); + } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + var flatCoordinates = geometry.getFlatCoordinates(); + textReplay.drawText(flatCoordinates, 0, flatCoordinates.length, + geometry.getStride(), geometry, feature); + } +}; - var hints = frameState.viewHints; - var renderedExtent = frameState.extent; - if (goog.isDef(layerState.extent)) { - renderedExtent = ol.extent.getIntersection( - renderedExtent, layerState.extent); +/** + * @param {ol.render.IReplayGroup} replayGroup Replay group. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.style.Style} style Style. + * @param {ol.Feature} feature Feature. + * @private + */ +ol.renderer.vector.renderPolygonGeometry_ = + function(replayGroup, geometry, style, feature) { + goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, + 'geometry should be an ol.geom.Polygon'); + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) { + var polygonReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.POLYGON); + polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); + polygonReplay.drawPolygonGeometry(geometry, feature); } - - if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && - !ol.extent.isEmpty(renderedExtent)) { - var projection = viewState.projection; - var sourceProjection = imageSource.getProjection(); - if (!goog.isNull(sourceProjection)) { - goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection)); - projection = sourceProjection; - } - image = imageSource.getImage( - renderedExtent, viewResolution, pixelRatio, projection); - if (!goog.isNull(image)) { - var imageState = image.getState(); - if (imageState == ol.ImageState.IDLE) { - goog.events.listenOnce(image, goog.events.EventType.CHANGE, - this.handleImageChange, false, this); - image.load(); - } else if (imageState == ol.ImageState.LOADED) { - this.image_ = image; - } - } + var textStyle = style.getText(); + if (!goog.isNull(textStyle)) { + var textReplay = replayGroup.getReplay( + style.getZIndex(), ol.render.ReplayType.TEXT); + textReplay.setTextStyle(textStyle); + textReplay.drawText( + geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature); } +}; - if (!goog.isNull(this.image_)) { - image = this.image_; - var imageExtent = image.getExtent(); - var imageResolution = image.getResolution(); - var imagePixelRatio = image.getPixelRatio(); - var scale = pixelRatio * imageResolution / - (viewResolution * imagePixelRatio); - ol.vec.Mat4.makeTransform2D(this.imageTransform_, - pixelRatio * frameState.size[0] / 2, - pixelRatio * frameState.size[1] / 2, - scale, scale, - viewRotation, - imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, - imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); - this.updateAttributions(frameState.attributions, image.getAttributions()); - this.updateLogos(frameState, imageSource); - } - return true; +/** + * @const + * @private + * @type {Object.<ol.geom.GeometryType, + * function(ol.render.IReplayGroup, ol.geom.Geometry, + * ol.style.Style, Object)>} + */ +ol.renderer.vector.GEOMETRY_RENDERERS_ = { + 'Point': ol.renderer.vector.renderPointGeometry_, + 'LineString': ol.renderer.vector.renderLineStringGeometry_, + 'Polygon': ol.renderer.vector.renderPolygonGeometry_, + 'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_, + 'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_, + 'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_, + 'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_, + 'Circle': ol.renderer.vector.renderCircleGeometry_ }; -// FIXME find correct globalCompositeOperation -// FIXME optimize :-) - -goog.provide('ol.renderer.canvas.TileLayer'); +goog.provide('ol.ImageCanvas'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('goog.vec.Mat4'); -goog.require('ol.Size'); -goog.require('ol.TileRange'); -goog.require('ol.TileState'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.layer.Tile'); -goog.require('ol.renderer.Map'); -goog.require('ol.renderer.canvas.Layer'); -goog.require('ol.tilecoord'); -goog.require('ol.vec.Mat4'); +goog.require('ol.ImageBase'); +goog.require('ol.ImageState'); /** * @constructor - * @extends {ol.renderer.canvas.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Tile} tileLayer Tile layer. + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.Attribution>} attributions Attributions. + * @param {HTMLCanvasElement} canvas Canvas. + * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to + * support asynchronous canvas drawing. */ -ol.renderer.canvas.TileLayer = function(mapRenderer, tileLayer) { - - goog.base(this, mapRenderer, tileLayer); - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = null; - - /** - * @private - * @type {ol.Size} - */ - this.canvasSize_ = null; - - /** - * @private - * @type {boolean} - */ - this.canvasTooBig_ = false; +ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions, + canvas, opt_loader) { /** + * Optional canvas loader function. + * @type {?ol.ImageCanvasLoader} * @private - * @type {CanvasRenderingContext2D} */ - this.context_ = null; + this.loader_ = goog.isDef(opt_loader) ? opt_loader : null; - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.imageTransform_ = goog.vec.Mat4.createNumber(); + var state = goog.isDef(opt_loader) ? + ol.ImageState.IDLE : ol.ImageState.LOADED; - /** - * @private - * @type {number} - */ - this.renderedCanvasZ_ = NaN; + goog.base(this, extent, resolution, pixelRatio, state, attributions); /** * @private - * @type {ol.TileRange} + * @type {HTMLCanvasElement} */ - this.renderedCanvasTileRange_ = null; + this.canvas_ = canvas; /** * @private - * @type {Array.<ol.Tile|undefined>} + * @type {Error} */ - this.renderedTiles_ = null; - -}; -goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer); + this.error_ = null; - -/** - * @inheritDoc - */ -ol.renderer.canvas.TileLayer.prototype.getImage = function() { - return this.canvas_; }; +goog.inherits(ol.ImageCanvas, ol.ImageBase); /** - * @inheritDoc + * Get any error associated with asynchronous rendering. + * @return {Error} Any error that occurred during rendering. */ -ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() { - return this.imageTransform_; +ol.ImageCanvas.prototype.getError = function() { + return this.error_; }; /** - * @inheritDoc - */ -ol.renderer.canvas.TileLayer.prototype.prepareFrame = - function(frameState, layerState) { - - // - // Warning! You're entering a dangerous zone! - // - // The canvas tile layer renderering is highly optimized, hence - // the complexity of this function. For best performance we try - // to minimize the number of pixels to update on the canvas. This - // includes: - // - // - Only drawing pixels that will be visible. - // - Not re-drawing pixels/tiles that are already correct. - // - Minimizing calls to clearRect. - // - Never shrink the canvas. Just make it bigger when necessary. - // Re-sizing the canvas also clears it, which further means - // re-creating it (expensive). - // - // The various steps performed by this functions: - // - // - Create a canvas element if none has been created yet. - // - // - Make the canvas bigger if it's too small. Note that we never shrink - // the canvas, we just make it bigger when necessary, when rotating for - // example. Note also that the canvas always contains a whole number - // of tiles. - // - // - Invalidate the canvas tile range (renderedCanvasTileRange_ = null) - // if (1) the canvas has been enlarged, or (2) the zoom level changes, - // or (3) the canvas tile range doesn't contain the required tile - // range. This canvas tile range invalidation thing is related to - // an optimization where we attempt to redraw as few pixels as - // possible on each prepareFrame call. - // - // - If the canvas tile range has been invalidated we reset - // renderedCanvasTileRange_ and reset the renderedTiles_ array. - // The renderedTiles_ array is the structure used to determine - // the canvas pixels that need not be redrawn from one prepareFrame - // call to another. It records while tile has been rendered at - // which position in the canvas. - // - // - We then determine the tiles to draw on the canvas. Tiles for - // the target resolution may not be loaded yet. In that case we - // use low-resolution/interim tiles if loaded already. And, if - // for a non-yet-loaded tile we haven't found a corresponding - // low-resolution tile we indicate that the pixels for that - // tile must be cleared on the canvas. Note: determining the - // interim tiles is based on tile extents instead of tile - // coords, this is to be able to handler irregular tile grids. - // - // - We're now ready to render. We start by calling clearRect - // for the tiles that aren't loaded yet and are not fully covered - // by a low-resolution tile (if they're loaded, we'll draw them; - // if they're fully covered by a low-resolution tile then there's - // no need to clear). We then render the tiles "back to front", - // i.e. starting with the low-resolution tiles. - // - // - After rendering some bookkeeping is performed (updateUsedTiles, - // etc.). manageTilePyramid is what enqueue tiles in the tile - // queue for loading. - // - // - The last step involves updating the image transform matrix, - // which will be used by the map renderer for the final - // composition and positioning. - // - - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var projection = viewState.projection; - - var tileLayer = this.getLayer(); - goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile); - var tileSource = tileLayer.getSource(); - var tileGrid = tileSource.getTileGridForProjection(projection); - var tileGutter = tileSource.getGutter(); - var z = tileGrid.getZForResolution(viewState.resolution); - var tilePixelSize = - tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); - var tilePixelRatio = tilePixelSize / tileGrid.getTileSize(z); - var tileResolution = tileGrid.getResolution(z); - var tilePixelResolution = tileResolution / tilePixelRatio; - var center = viewState.center; - var extent; - if (tileResolution == viewState.resolution) { - center = this.snapCenterToPixel(center, tileResolution, frameState.size); - extent = ol.extent.getForViewAndSize( - center, tileResolution, viewState.rotation, frameState.size); - } else { - extent = frameState.extent; - } - - if (goog.isDef(layerState.extent)) { - extent = ol.extent.getIntersection(extent, layerState.extent); - } - if (ol.extent.isEmpty(extent)) { - // Return false to prevent the rendering of the layer. - return false; - } - - var tileRange = tileGrid.getTileRangeForExtentAndResolution( - extent, tileResolution); - - var canvasWidth = tilePixelSize * tileRange.getWidth(); - var canvasHeight = tilePixelSize * tileRange.getHeight(); - - var canvas, context; - if (goog.isNull(this.canvas_)) { - goog.asserts.assert(goog.isNull(this.canvasSize_)); - goog.asserts.assert(goog.isNull(this.context_)); - goog.asserts.assert(goog.isNull(this.renderedCanvasTileRange_)); - context = ol.dom.createCanvasContext2D(canvasWidth, canvasHeight); - this.canvas_ = context.canvas; - this.canvasSize_ = [canvasWidth, canvasHeight]; - this.context_ = context; - this.canvasTooBig_ = - !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_); - } else { - goog.asserts.assert(!goog.isNull(this.canvasSize_)); - goog.asserts.assert(!goog.isNull(this.context_)); - canvas = this.canvas_; - context = this.context_; - if (this.canvasSize_[0] < canvasWidth || - this.canvasSize_[1] < canvasHeight || - (this.canvasTooBig_ && (this.canvasSize_[0] > canvasWidth || - this.canvasSize_[1] > canvasHeight))) { - // Canvas is too small, resize it. We never shrink the canvas, unless - // we know that the current canvas size exceeds the maximum size - canvas.width = canvasWidth; - canvas.height = canvasHeight; - this.canvasSize_ = [canvasWidth, canvasHeight]; - this.canvasTooBig_ = - !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_); - this.renderedCanvasTileRange_ = null; - } else { - canvasWidth = this.canvasSize_[0]; - canvasHeight = this.canvasSize_[1]; - if (z != this.renderedCanvasZ_ || - !this.renderedCanvasTileRange_.containsTileRange(tileRange)) { - this.renderedCanvasTileRange_ = null; - } - } - } - - var canvasTileRange, canvasTileRangeWidth, minX, minY; - if (goog.isNull(this.renderedCanvasTileRange_)) { - canvasTileRangeWidth = canvasWidth / tilePixelSize; - var canvasTileRangeHeight = canvasHeight / tilePixelSize; - minX = tileRange.minX - - Math.floor((canvasTileRangeWidth - tileRange.getWidth()) / 2); - minY = tileRange.minY - - Math.floor((canvasTileRangeHeight - tileRange.getHeight()) / 2); - this.renderedCanvasZ_ = z; - this.renderedCanvasTileRange_ = new ol.TileRange( - minX, minX + canvasTileRangeWidth - 1, - minY, minY + canvasTileRangeHeight - 1); - this.renderedTiles_ = - new Array(canvasTileRangeWidth * canvasTileRangeHeight); - canvasTileRange = this.renderedCanvasTileRange_; - } else { - canvasTileRange = this.renderedCanvasTileRange_; - canvasTileRangeWidth = canvasTileRange.getWidth(); - } - - goog.asserts.assert(canvasTileRange.containsTileRange(tileRange)); - - /** - * @type {Object.<number, Object.<string, ol.Tile>>} - */ - var tilesToDrawByZ = {}; - tilesToDrawByZ[z] = {}; - /** @type {Array.<ol.Tile>} */ - var tilesToClear = []; - - var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) { - return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED; - }, tileSource, pixelRatio, projection); - var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource, - tilesToDrawByZ, getTileIfLoaded); - - var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); - if (!goog.isDef(useInterimTilesOnError)) { - useInterimTilesOnError = true; - } - - var tmpExtent = ol.extent.createEmpty(); - var tmpTileRange = new ol.TileRange(0, 0, 0, 0); - var childTileRange, fullyLoaded, tile, tileState, x, y; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - tileState = tile.getState(); - if (tileState == ol.TileState.LOADED || - tileState == ol.TileState.EMPTY || - (tileState == ol.TileState.ERROR && !useInterimTilesOnError)) { - tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile; - continue; - } - - fullyLoaded = tileGrid.forEachTileCoordParentTileRange( - tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); - if (!fullyLoaded) { - // FIXME we do not need to clear the tile if it is fully covered by its - // children - tilesToClear.push(tile); - childTileRange = tileGrid.getTileCoordChildTileRange( - tile.tileCoord, tmpTileRange, tmpExtent); - if (!goog.isNull(childTileRange)) { - findLoadedTiles(z + 1, childTileRange); - } - } - - } + * Handle async drawing complete. + * @param {Error} err Any error during drawing. + * @private + */ +ol.ImageCanvas.prototype.handleLoad_ = function(err) { + if (err) { + this.error_ = err; + this.state = ol.ImageState.ERROR; + } else { + this.state = ol.ImageState.LOADED; } + this.changed(); +}; - var i, ii; - for (i = 0, ii = tilesToClear.length; i < ii; ++i) { - tile = tilesToClear[i]; - x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX); - y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]); - context.clearRect(x, y, tilePixelSize, tilePixelSize); - } - /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); - goog.array.sort(zs); - var opaque = tileSource.getOpaque(); - var origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent( - [z, canvasTileRange.minX, canvasTileRange.maxY], - tmpExtent)); - var currentZ, index, scale, tileCoordKey, tileExtent, tilesToDraw; - var ix, iy, interimTileExtent, interimTileRange, maxX, maxY; - var height, width; - for (i = 0, ii = zs.length; i < ii; ++i) { - currentZ = zs[i]; - tilePixelSize = - tileSource.getTilePixelSize(currentZ, pixelRatio, projection); - tilesToDraw = tilesToDrawByZ[currentZ]; - if (currentZ == z) { - for (tileCoordKey in tilesToDraw) { - tile = tilesToDraw[tileCoordKey]; - index = - (tile.tileCoord[2] - canvasTileRange.minY) * canvasTileRangeWidth + - (tile.tileCoord[1] - canvasTileRange.minX); - if (this.renderedTiles_[index] != tile) { - x = tilePixelSize * (tile.tileCoord[1] - canvasTileRange.minX); - y = tilePixelSize * (canvasTileRange.maxY - tile.tileCoord[2]); - tileState = tile.getState(); - if (tileState == ol.TileState.EMPTY || - (tileState == ol.TileState.ERROR && !useInterimTilesOnError) || - !opaque) { - context.clearRect(x, y, tilePixelSize, tilePixelSize); - } - if (tileState == ol.TileState.LOADED) { - context.drawImage(tile.getImage(), - tileGutter, tileGutter, tilePixelSize, tilePixelSize, - x, y, tilePixelSize, tilePixelSize); - } - this.renderedTiles_[index] = tile; - } - } - } else { - scale = tileGrid.getResolution(currentZ) / tileResolution; - for (tileCoordKey in tilesToDraw) { - tile = tilesToDraw[tileCoordKey]; - tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); - x = (tileExtent[0] - origin[0]) / tilePixelResolution; - y = (origin[1] - tileExtent[3]) / tilePixelResolution; - width = scale * tilePixelSize; - height = scale * tilePixelSize; - tileState = tile.getState(); - if (tileState == ol.TileState.EMPTY || !opaque) { - context.clearRect(x, y, width, height); - } - if (tileState == ol.TileState.LOADED) { - context.drawImage(tile.getImage(), - tileGutter, tileGutter, tilePixelSize, tilePixelSize, - x, y, width, height); - } - interimTileRange = - tileGrid.getTileRangeForExtentAndZ(tileExtent, z, tmpTileRange); - minX = Math.max(interimTileRange.minX, canvasTileRange.minX); - maxX = Math.min(interimTileRange.maxX, canvasTileRange.maxX); - minY = Math.max(interimTileRange.minY, canvasTileRange.minY); - maxY = Math.min(interimTileRange.maxY, canvasTileRange.maxY); - for (ix = minX; ix <= maxX; ++ix) { - for (iy = minY; iy <= maxY; ++iy) { - index = (iy - canvasTileRange.minY) * canvasTileRangeWidth + - (ix - canvasTileRange.minX); - this.renderedTiles_[index] = undefined; - } - } - } - } +/** + * Trigger drawing on canvas. + */ +ol.ImageCanvas.prototype.load = function() { + if (this.state == ol.ImageState.IDLE) { + goog.asserts.assert(!goog.isNull(this.loader_)); + this.state = ol.ImageState.LOADING; + this.changed(); + this.loader_(goog.bind(this.handleLoad_, this)); } +}; - this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, - projection, extent, z, tileLayer.getPreload()); - this.scheduleExpireCache(frameState, tileSource); - this.updateLogos(frameState, tileSource); - - ol.vec.Mat4.makeTransform2D(this.imageTransform_, - pixelRatio * frameState.size[0] / 2, - pixelRatio * frameState.size[1] / 2, - pixelRatio * tilePixelResolution / viewState.resolution, - pixelRatio * tilePixelResolution / viewState.resolution, - viewState.rotation, - (origin[0] - center[0]) / tilePixelResolution, - (center[1] - origin[1]) / tilePixelResolution); - return true; +/** + * @inheritDoc + */ +ol.ImageCanvas.prototype.getImage = function(opt_context) { + return this.canvas_; }; -goog.provide('ol.geom.Circle'); +/** + * A function that is called to trigger asynchronous canvas drawing. It is + * called with a "done" callback that should be called when drawing is done. + * If any error occurs during drawing, the "done" callback should be called with + * that error. + * + * @typedef {function(function(Error))} + */ +ol.ImageCanvasLoader; + +goog.provide('ol.source.Image'); + +goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.proj'); +goog.require('goog.events.Event'); +goog.require('ol.Attribution'); +goog.require('ol.Extent'); +goog.require('ol.ImageState'); +goog.require('ol.array'); +goog.require('ol.source.Source'); + + +/** + * @typedef {{attributions: (Array.<ol.Attribution>|undefined), + * extent: (null|ol.Extent|undefined), + * logo: (string|olx.LogoOptions|undefined), + * projection: ol.proj.ProjectionLike, + * resolutions: (Array.<number>|undefined), + * state: (ol.source.State|undefined)}} + */ +ol.source.ImageOptions; /** * @classdesc - * Circle geometry. + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for sources providing a single image. * * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {ol.Coordinate} center Center. - * @param {number=} opt_radius Radius. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. + * @extends {ol.source.Source} + * @param {ol.source.ImageOptions} options Single image source options. * @api */ -ol.geom.Circle = function(center, opt_radius, opt_layout) { - goog.base(this); - var radius = goog.isDef(opt_radius) ? opt_radius : 0; - this.setCenterAndRadius(center, radius, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); +ol.source.Image = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection, + state: options.state + }); + + /** + * @private + * @type {Array.<number>} + */ + this.resolutions_ = goog.isDef(options.resolutions) ? + options.resolutions : null; + goog.asserts.assert(goog.isNull(this.resolutions_) || + goog.array.isSorted(this.resolutions_, + function(a, b) { + return b - a; + }, true), 'resolutions must be null or sorted in descending order'); + }; -goog.inherits(ol.geom.Circle, ol.geom.SimpleGeometry); +goog.inherits(ol.source.Image, ol.source.Source); /** - * Make a complete copy of the geometry. - * @return {!ol.geom.Circle} Clone. - * @api + * @return {Array.<number>} Resolutions. */ -ol.geom.Circle.prototype.clone = function() { - var circle = new ol.geom.Circle(null); - circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); - return circle; +ol.source.Image.prototype.getResolutions = function() { + return this.resolutions_; }; /** - * @inheritDoc + * @protected + * @param {number} resolution Resolution. + * @return {number} Resolution. */ -ol.geom.Circle.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - var flatCoordinates = this.flatCoordinates; - var dx = x - flatCoordinates[0]; - var dy = y - flatCoordinates[1]; - var squaredDistance = dx * dx + dy * dy; - if (squaredDistance < minSquaredDistance) { - var i; - if (squaredDistance === 0) { - for (i = 0; i < this.stride; ++i) { - closestPoint[i] = flatCoordinates[i]; - } - } else { - var delta = this.getRadius() / Math.sqrt(squaredDistance); - closestPoint[0] = flatCoordinates[0] + delta * dx; - closestPoint[1] = flatCoordinates[1] + delta * dy; - for (i = 2; i < this.stride; ++i) { - closestPoint[i] = flatCoordinates[i]; - } - } - closestPoint.length = this.stride; - return squaredDistance; - } else { - return minSquaredDistance; +ol.source.Image.prototype.findNearestResolution = + function(resolution) { + if (!goog.isNull(this.resolutions_)) { + var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0); + resolution = this.resolutions_[idx]; } + return resolution; }; /** - * @inheritDoc + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.ImageBase} Single image. */ -ol.geom.Circle.prototype.containsXY = function(x, y) { - var flatCoordinates = this.flatCoordinates; - var dx = x - flatCoordinates[0]; - var dy = y - flatCoordinates[1]; - return dx * dx + dy * dy <= this.getRadiusSquared_(); -}; +ol.source.Image.prototype.getImage = goog.abstractMethod; /** - * @return {ol.Coordinate} Center. - * @api + * Handle image change events. + * @param {goog.events.Event} event Event. + * @protected */ -ol.geom.Circle.prototype.getCenter = function() { - return this.flatCoordinates.slice(0, this.stride); +ol.source.Image.prototype.handleImageChange = function(event) { + var image = /** @type {ol.Image} */ (event.target); + switch (image.getState()) { + case ol.ImageState.LOADING: + this.dispatchEvent( + new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADSTART, + image)); + break; + case ol.ImageState.LOADED: + this.dispatchEvent( + new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADEND, + image)); + break; + case ol.ImageState.ERROR: + this.dispatchEvent( + new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADERROR, + image)); + break; + } }; /** - * @inheritDoc - * @api + * Default image load function for image sources that use ol.Image image + * instances. + * @param {ol.Image} image Image. + * @param {string} src Source. */ -ol.geom.Circle.prototype.getExtent = function(opt_extent) { - if (this.extentRevision != this.getRevision()) { - var flatCoordinates = this.flatCoordinates; - var radius = flatCoordinates[this.stride] - flatCoordinates[0]; - this.extent = ol.extent.createOrUpdate( - flatCoordinates[0] - radius, flatCoordinates[1] - radius, - flatCoordinates[0] + radius, flatCoordinates[1] + radius, - this.extent); - this.extentRevision = this.getRevision(); - } - goog.asserts.assert(goog.isDef(this.extent)); - return ol.extent.returnOrUpdate(this.extent, opt_extent); +ol.source.Image.defaultImageLoadFunction = function(image, src) { + image.getImage().src = src; }; + /** - * @return {number} Radius. - * @api + * @classdesc + * Events emitted by {@link ol.source.Image} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.source.ImageEvent} + * @param {string} type Type. + * @param {ol.Image} image The image. */ -ol.geom.Circle.prototype.getRadius = function() { - return Math.sqrt(this.getRadiusSquared_()); +ol.source.ImageEvent = function(type, image) { + + goog.base(this, type); + + /** + * The image related to the event. + * @type {ol.Image} + * @api + */ + this.image = image; + }; +goog.inherits(ol.source.ImageEvent, goog.events.Event); /** - * @private - * @return {number} Radius squared. + * @enum {string} */ -ol.geom.Circle.prototype.getRadiusSquared_ = function() { - var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0]; - var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1]; - return dx * dx + dy * dy; +ol.source.ImageEventType = { + + /** + * Triggered when an image starts loading. + * @event ol.source.ImageEvent#imageloadstart + * @api + */ + IMAGELOADSTART: 'imageloadstart', + + /** + * Triggered when an image finishes loading. + * @event ol.source.ImageEvent#imageloadend + * @api + */ + IMAGELOADEND: 'imageloadend', + + /** + * Triggered if image loading results in an error. + * @event ol.source.ImageEvent#imageloaderror + * @api + */ + IMAGELOADERROR: 'imageloaderror' + }; +goog.provide('ol.source.ImageCanvas'); + +goog.require('ol.CanvasFunctionType'); +goog.require('ol.ImageCanvas'); +goog.require('ol.extent'); +goog.require('ol.source.Image'); + + /** - * @inheritDoc + * @classdesc + * Base class for image sources where a canvas element is the image. + * + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageCanvasOptions} options * @api */ -ol.geom.Circle.prototype.getType = function() { - return ol.geom.GeometryType.CIRCLE; +ol.source.ImageCanvas = function(options) { + + goog.base(this, { + attributions: options.attributions, + logo: options.logo, + projection: options.projection, + resolutions: options.resolutions, + state: goog.isDef(options.state) ? + /** @type {ol.source.State} */ (options.state) : undefined + }); + + /** + * @private + * @type {ol.CanvasFunctionType} + */ + this.canvasFunction_ = options.canvasFunction; + + /** + * @private + * @type {ol.ImageCanvas} + */ + this.canvas_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {number} + */ + this.ratio_ = goog.isDef(options.ratio) ? + options.ratio : 1.5; + }; +goog.inherits(ol.source.ImageCanvas, ol.source.Image); /** - * @param {ol.Coordinate} center Center. - * @api + * @inheritDoc */ -ol.geom.Circle.prototype.setCenter = function(center) { - var stride = this.stride; - goog.asserts.assert(center.length == stride); - var radius = this.flatCoordinates[stride] - this.flatCoordinates[0]; - var flatCoordinates = center.slice(); - flatCoordinates[stride] = flatCoordinates[0] + radius; - var i; - for (i = 1; i < stride; ++i) { - flatCoordinates[stride + i] = center[i]; +ol.source.ImageCanvas.prototype.getImage = + function(extent, resolution, pixelRatio, projection) { + resolution = this.findNearestResolution(resolution); + + var canvas = this.canvas_; + if (!goog.isNull(canvas) && + this.renderedRevision_ == this.getRevision() && + canvas.getResolution() == resolution && + canvas.getPixelRatio() == pixelRatio && + ol.extent.containsExtent(canvas.getExtent(), extent)) { + return canvas; } - this.setFlatCoordinates(this.layout, flatCoordinates); + + extent = extent.slice(); + ol.extent.scaleFromCenter(extent, this.ratio_); + var width = ol.extent.getWidth(extent) / resolution; + var height = ol.extent.getHeight(extent) / resolution; + var size = [width * pixelRatio, height * pixelRatio]; + + var canvasElement = this.canvasFunction_( + extent, resolution, pixelRatio, size, projection); + if (!goog.isNull(canvasElement)) { + canvas = new ol.ImageCanvas(extent, resolution, pixelRatio, + this.getAttributions(), canvasElement); + } + this.canvas_ = canvas; + this.renderedRevision_ = this.getRevision(); + + return canvas; }; +goog.provide('ol.Feature'); +goog.provide('ol.FeatureStyleFunction'); + +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.functions'); +goog.require('ol.Object'); +goog.require('ol.geom.Geometry'); +goog.require('ol.style.Style'); + + /** - * @param {ol.Coordinate} center Center. - * @param {number} radius Radius. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api + * @classdesc + * A vector object for geographic features with a geometry and other + * attribute properties, similar to the features in vector file formats like + * GeoJSON. + * + * Features can be styled individually with `setStyle`; otherwise they use the + * style of their vector layer. + * + * Note that attribute properties are set as {@link ol.Object} properties on + * the feature object, so they are observable, and have get/set accessors. + * + * Typically, a feature has a single geometry property. You can set the + * geometry using the `setGeometry` method and get it with `getGeometry`. + * It is possible to store more than one geometry on a feature using attribute + * properties. By default, the geometry used for rendering is identified by + * the property name `geometry`. If you want to use another geometry property + * for rendering, use the `setGeometryName` method to change the attribute + * property associated with the geometry for the feature. For example: + * + * ```js + * var feature = new ol.Feature({ + * geometry: new ol.geom.Polygon(polyCoords), + * labelPoint: new ol.geom.Point(labelCoords), + * name: 'My Polygon' + * }); + * + * // get the polygon geometry + * var poly = feature.getGeometry(); + * + * // Render the feature as a point using the coordinates from labelPoint + * feature.setGeometryName('labelPoint'); + * + * // get the point geometry + * var point = feature.getGeometry(); + * ``` + * + * @constructor + * @extends {ol.Object} + * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties + * You may pass a Geometry object directly, or an object literal + * containing properties. If you pass an object literal, you may + * include a Geometry associated with a `geometry` key. + * @api stable */ -ol.geom.Circle.prototype.setCenterAndRadius = - function(center, radius, opt_layout) { - if (goog.isNull(center)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); - } else { - this.setLayout(opt_layout, center, 0); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; - } - /** @type {Array.<number>} */ - var flatCoordinates = this.flatCoordinates; - var offset = ol.geom.flat.deflate.coordinate( - flatCoordinates, 0, center, this.stride); - flatCoordinates[offset++] = flatCoordinates[0] + radius; - var i, ii; - for (i = 1, ii = this.stride; i < ii; ++i) { - flatCoordinates[offset++] = flatCoordinates[i]; +ol.Feature = function(opt_geometryOrProperties) { + + goog.base(this); + + /** + * @private + * @type {number|string|undefined} + */ + this.id_ = undefined; + + /** + * @type {string} + * @private + */ + this.geometryName_ = 'geometry'; + + /** + * User provided style. + * @private + * @type {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} + */ + this.style_ = null; + + /** + * @private + * @type {ol.FeatureStyleFunction|undefined} + */ + this.styleFunction_ = undefined; + + /** + * @private + * @type {goog.events.Key} + */ + this.geometryChangeKey_ = null; + + goog.events.listen( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, false, this); + + if (goog.isDef(opt_geometryOrProperties)) { + if (opt_geometryOrProperties instanceof ol.geom.Geometry || + goog.isNull(opt_geometryOrProperties)) { + var geometry = /** @type {ol.geom.Geometry} */ (opt_geometryOrProperties); + this.setGeometry(geometry); + } else { + goog.asserts.assert(goog.isObject(opt_geometryOrProperties), + 'opt_geometryOrProperties should be an Object'); + var properties = /** @type {Object.<string, *>} */ + (opt_geometryOrProperties); + this.setProperties(properties); } - flatCoordinates.length = offset; - this.changed(); } }; +goog.inherits(ol.Feature, ol.Object); + + +/** + * Clone this feature. If the original feature has a geometry it + * is also cloned. The feature id is not set in the clone. + * @return {ol.Feature} The clone. + * @api stable + */ +ol.Feature.prototype.clone = function() { + var clone = new ol.Feature(this.getProperties()); + clone.setGeometryName(this.getGeometryName()); + var geometry = this.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + clone.setGeometry(geometry.clone()); + } + var style = this.getStyle(); + if (!goog.isNull(style)) { + clone.setStyle(style); + } + return clone; +}; /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. + * Get the feature's default geometry. A feature may have any number of named + * geometries. The "default" geometry (the one that is rendered by default) is + * set when calling {@link ol.Feature#setGeometry}. + * @return {ol.geom.Geometry|undefined} The default geometry for the feature. + * @api stable + * @observable */ -ol.geom.Circle.prototype.setFlatCoordinates = - function(layout, flatCoordinates) { - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.changed(); +ol.Feature.prototype.getGeometry = function() { + return /** @type {ol.geom.Geometry|undefined} */ ( + this.get(this.geometryName_)); }; /** - * The radius is in the units of the projection. - * @param {number} radius Radius. - * @api + * @return {number|string|undefined} Id. + * @api stable + * @observable */ -ol.geom.Circle.prototype.setRadius = function(radius) { - goog.asserts.assert(!goog.isNull(this.flatCoordinates)); - this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius; - this.changed(); +ol.Feature.prototype.getId = function() { + return this.id_; }; /** - * Transform each coordinate of the circle from one coordinate reference system - * to another. The geometry is modified in place. - * If you do not want the geometry modified in place, first clone() it and - * then use this function on the clone. - * - * Internally a circle is currently represented by two points: the center of - * the circle `[cx, cy]`, and the point to the right of the circle - * `[cx + r, cy]`. This `transform` function just transforms these two points. - * So the resulting geometry is also a circle, and that circle does not - * correspond to the shape that would be obtained by transforming every point - * of the original circle. - * - * @param {ol.proj.ProjectionLike} source The current projection. Can be a - * string identifier or a {@link ol.proj.Projection} object. - * @param {ol.proj.ProjectionLike} destination The desired projection. Can be a - * string identifier or a {@link ol.proj.Projection} object. - * @return {ol.geom.Circle} This geometry. Note that original geometry is - * modified in place. - * @function + * Get the name of the feature's default geometry. By default, the default + * geometry is named `geometry`. + * @return {string} Get the property name associated with the default geometry + * for this feature. * @api stable */ -ol.geom.Circle.prototype.transform; - -goog.provide('ol.geom.GeometryCollection'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('ol.extent'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.GeometryType'); - +ol.Feature.prototype.getGeometryName = function() { + return this.geometryName_; +}; /** - * @classdesc - * An array of {@link ol.geom.Geometry} objects. - * - * @constructor - * @extends {ol.geom.Geometry} - * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries. + * Get the feature's style. This return for this method depends on what was + * provided to the {@link ol.Feature#setStyle} method. + * @return {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} The feature style. * @api stable + * @observable */ -ol.geom.GeometryCollection = function(opt_geometries) { - - goog.base(this); +ol.Feature.prototype.getStyle = function() { + return this.style_; +}; - /** - * @private - * @type {Array.<ol.geom.Geometry>} - */ - this.geometries_ = goog.isDef(opt_geometries) ? opt_geometries : null; - this.listenGeometriesChange_(); +/** + * Get the feature's style function. + * @return {ol.FeatureStyleFunction|undefined} Return a function + * representing the current style of this feature. + * @api stable + */ +ol.Feature.prototype.getStyleFunction = function() { + return this.styleFunction_; }; -goog.inherits(ol.geom.GeometryCollection, ol.geom.Geometry); /** - * @param {Array.<ol.geom.Geometry>} geometries Geometries. * @private - * @return {Array.<ol.geom.Geometry>} Cloned geometries. */ -ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) { - var clonedGeometries = []; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - clonedGeometries.push(geometries[i].clone()); - } - return clonedGeometries; +ol.Feature.prototype.handleGeometryChange_ = function() { + this.changed(); }; /** * @private */ -ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() { - var i, ii; - if (goog.isNull(this.geometries_)) { - return; +ol.Feature.prototype.handleGeometryChanged_ = function() { + if (!goog.isNull(this.geometryChangeKey_)) { + goog.events.unlistenByKey(this.geometryChangeKey_); + this.geometryChangeKey_ = null; } - for (i = 0, ii = this.geometries_.length; i < ii; ++i) { - goog.events.unlisten( - this.geometries_[i], goog.events.EventType.CHANGE, - this.changed, false, this); + var geometry = this.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + this.geometryChangeKey_ = goog.events.listen(geometry, + goog.events.EventType.CHANGE, this.handleGeometryChange_, false, this); } + this.changed(); }; /** - * @private + * Set the default geometry for the feature. This will update the property + * with the name returned by {@link ol.Feature#getGeometryName}. + * @param {ol.geom.Geometry|undefined} geometry The new geometry. + * @api stable + * @observable */ -ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() { - var i, ii; - if (goog.isNull(this.geometries_)) { - return; - } - for (i = 0, ii = this.geometries_.length; i < ii; ++i) { - goog.events.listen( - this.geometries_[i], goog.events.EventType.CHANGE, - this.changed, false, this); - } +ol.Feature.prototype.setGeometry = function(geometry) { + this.set(this.geometryName_, geometry); }; /** - * Make a complete copy of the geometry. - * @return {!ol.geom.GeometryCollection} Clone. + * Set the style for the feature. This can be a single style object, an array + * of styles, or a function that takes a resolution and returns an array of + * styles. If it is `null` the feature has no style (a `null` style). + * @param {ol.style.Style|Array.<ol.style.Style>| + * ol.FeatureStyleFunction} style Style for this feature. * @api stable + * @observable */ -ol.geom.GeometryCollection.prototype.clone = function() { - var geometryCollection = new ol.geom.GeometryCollection(null); - geometryCollection.setGeometries(this.geometries_); - return geometryCollection; +ol.Feature.prototype.setStyle = function(style) { + this.style_ = style; + this.styleFunction_ = goog.isNull(style) ? + undefined : ol.Feature.createStyleFunction(style); + this.changed(); }; /** - * @inheritDoc + * Set the feature id. The feature id is considered stable and may be used when + * requesting features or comparing identifiers returned from a remote source. + * The feature id can be used with the {@link ol.source.Vector#getFeatureById} + * method. + * @param {number|string|undefined} id The feature id. + * @api stable + * @observable */ -ol.geom.GeometryCollection.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - var geometries = this.geometries_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - minSquaredDistance = geometries[i].closestPointXY( - x, y, closestPoint, minSquaredDistance); - } - return minSquaredDistance; +ol.Feature.prototype.setId = function(id) { + this.id_ = id; + this.changed(); }; /** - * @inheritDoc + * Set the property name to be used when getting the feature's default geometry. + * When calling {@link ol.Feature#getGeometry}, the value of the property with + * this name will be returned. + * @param {string} name The property name of the default geometry. + * @api stable */ -ol.geom.GeometryCollection.prototype.containsXY = function(x, y) { - var geometries = this.geometries_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - if (geometries[i].containsXY(x, y)) { - return true; - } - } - return false; +ol.Feature.prototype.setGeometryName = function(name) { + goog.events.unlisten( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, false, this); + this.geometryName_ = name; + goog.events.listen( + this, ol.Object.getChangeEventType(this.geometryName_), + this.handleGeometryChanged_, false, this); + this.handleGeometryChanged_(); }; /** - * @inheritDoc + * A function that returns a style given a resolution. The `this` keyword inside + * the function references the {@link ol.Feature} to be styled. + * + * @typedef {function(this: ol.Feature, number): Array.<ol.style.Style>} * @api stable */ -ol.geom.GeometryCollection.prototype.getExtent = function(opt_extent) { - if (this.extentRevision != this.getRevision()) { - var extent = ol.extent.createOrUpdateEmpty(this.extent); - var geometries = this.geometries_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - ol.extent.extend(extent, geometries[i].getExtent()); +ol.FeatureStyleFunction; + + +/** + * Convert the provided object into a feature style function. Functions passed + * through unchanged. Arrays of ol.style.Style or single style objects wrapped + * in a new feature style function. + * @param {ol.FeatureStyleFunction|!Array.<ol.style.Style>|!ol.style.Style} obj + * A feature style function, a single style, or an array of styles. + * @return {ol.FeatureStyleFunction} A style function. + */ +ol.Feature.createStyleFunction = function(obj) { + /** + * @type {ol.FeatureStyleFunction} + */ + var styleFunction; + + if (goog.isFunction(obj)) { + styleFunction = /** @type {ol.FeatureStyleFunction} */ (obj); + } else { + /** + * @type {Array.<ol.style.Style>} + */ + var styles; + if (goog.isArray(obj)) { + styles = obj; + } else { + goog.asserts.assertInstanceof(obj, ol.style.Style, + 'obj should be an ol.style.Style'); + styles = [obj]; } - this.extent = extent; - this.extentRevision = this.getRevision(); + styleFunction = goog.functions.constant(styles); } - goog.asserts.assert(goog.isDef(this.extent)); - return ol.extent.returnOrUpdate(this.extent, opt_extent); + return styleFunction; }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @return {Array.<ol.geom.Geometry>} Geometries. - * @api stable + * @fileoverview Common events for the network classes. */ -ol.geom.GeometryCollection.prototype.getGeometries = function() { - return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_); -}; + + +goog.provide('goog.net.EventType'); /** - * @return {Array.<ol.geom.Geometry>} Geometries. + * Event names for network events + * @enum {string} */ -ol.geom.GeometryCollection.prototype.getGeometriesArray = function() { - return this.geometries_; +goog.net.EventType = { + COMPLETE: 'complete', + SUCCESS: 'success', + ERROR: 'error', + ABORT: 'abort', + READY: 'ready', + READY_STATE_CHANGE: 'readystatechange', + TIMEOUT: 'timeout', + INCREMENTAL_DATA: 'incrementaldata', + PROGRESS: 'progress' }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.Thenable'); + + /** - * @inheritDoc + * Provides a more strict interface for Thenables in terms of + * http://promisesaplus.com for interop with {@see goog.Promise}. + * + * @interface + * @extends {IThenable<TYPE>} + * @template TYPE */ -ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = - function(squaredTolerance) { - if (this.simplifiedGeometryRevision != this.getRevision()) { - goog.object.clear(this.simplifiedGeometryCache); - this.simplifiedGeometryMaxMinSquaredTolerance = 0; - this.simplifiedGeometryRevision = this.getRevision(); - } - if (squaredTolerance < 0 || - (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && - squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) { - return this; - } - var key = squaredTolerance.toString(); - if (this.simplifiedGeometryCache.hasOwnProperty(key)) { - return this.simplifiedGeometryCache[key]; - } else { - var simplifiedGeometries = []; - var geometries = this.geometries_; - var simplified = false; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - var geometry = geometries[i]; - var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); - simplifiedGeometries.push(simplifiedGeometry); - if (simplifiedGeometry !== geometry) { - simplified = true; - } - } - if (simplified) { - var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null); - simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries); - this.simplifiedGeometryCache[key] = simplifiedGeometryCollection; - return simplifiedGeometryCollection; - } else { - this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; - return this; - } - } -}; +goog.Thenable = function() {}; /** - * @inheritDoc - * @api stable + * Adds callbacks that will operate on the result of the Thenable, returning a + * new child Promise. + * + * If the Thenable is fulfilled, the {@code onFulfilled} callback will be + * invoked with the fulfillment value as argument, and the child Promise will + * be fulfilled with the return value of the callback. If the callback throws + * an exception, the child Promise will be rejected with the thrown value + * instead. + * + * If the Thenable is rejected, the {@code onRejected} callback will be invoked + * with the rejection reason as argument, and the child Promise will be rejected + * with the return value of the callback or thrown value. + * + * @param {?(function(this:THIS, TYPE): + * (RESULT|IThenable<RESULT>|Thenable))=} opt_onFulfilled A + * function that will be invoked with the fulfillment value if the Promise + * is fullfilled. + * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will + * be invoked with the rejection reason if the Promise is rejected. + * @param {THIS=} opt_context An optional context object that will be the + * execution context for the callbacks. By default, functions are executed + * with the default this. + * @return {!goog.Promise<RESULT>} A new Promise that will receive the result + * of the fulfillment or rejection callback. + * @template RESULT,THIS */ -ol.geom.GeometryCollection.prototype.getType = function() { - return ol.geom.GeometryType.GEOMETRY_COLLECTION; +goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected, + opt_context) {}; + + +/** + * An expando property to indicate that an object implements + * {@code goog.Thenable}. + * + * {@see addImplementation}. + * + * @const + */ +goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable'; + + +/** + * Marks a given class (constructor) as an implementation of Thenable, so + * that we can query that fact at runtime. The class must have already + * implemented the interface. + * Exports a 'then' method on the constructor prototype, so that the objects + * also implement the extern {@see goog.Thenable} interface for interop with + * other Promise implementations. + * @param {function(new:goog.Thenable,...?)} ctor The class constructor. The + * corresponding class must have already implemented the interface. + */ +goog.Thenable.addImplementation = function(ctor) { + goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then); + if (COMPILED) { + ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true; + } else { + // Avoids dictionary access in uncompiled mode. + ctor.prototype.$goog_Thenable = true; + } }; /** - * @inheritDoc - * @api + * @param {*} object + * @return {boolean} Whether a given instance implements {@code goog.Thenable}. + * The class/superclass of the instance must call {@code addImplementation}. */ -ol.geom.GeometryCollection.prototype.intersectsExtent = function(extent) { - var geometries = this.geometries_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - if (geometries[i].intersectsExtent(extent)) { - return true; +goog.Thenable.isImplementedBy = function(object) { + if (!object) { + return false; + } + try { + if (COMPILED) { + return !!object[goog.Thenable.IMPLEMENTED_BY_PROP]; } + return !!object.$goog_Thenable; + } catch (e) { + // Property access seems to be forbidden. + return false; } - return false; }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple notifiers for the Closure testing framework. + * + * @author johnlenz@google.com (John Lenz) + */ + +goog.provide('goog.testing.watchers'); + + +/** @private {!Array<function()>} */ +goog.testing.watchers.resetWatchers_ = []; + /** - * @return {boolean} Is empty. + * Fires clock reset watching functions. */ -ol.geom.GeometryCollection.prototype.isEmpty = function() { - return goog.array.isEmpty(this.geometries_); +goog.testing.watchers.signalClockReset = function() { + var watchers = goog.testing.watchers.resetWatchers_; + for (var i = 0; i < watchers.length; i++) { + goog.testing.watchers.resetWatchers_[i](); + } }; /** - * @param {Array.<ol.geom.Geometry>} geometries Geometries. - * @api stable + * Enqueues a function to be called when the clock used for setTimeout is reset. + * @param {function()} fn */ -ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) { - this.setGeometriesArray( - ol.geom.GeometryCollection.cloneGeometries_(geometries)); +goog.testing.watchers.watchClockReset = function(fn) { + goog.testing.watchers.resetWatchers_.push(fn); }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.async.run'); + +goog.require('goog.async.nextTick'); +goog.require('goog.async.throwException'); +goog.require('goog.testing.watchers'); + + /** - * @param {Array.<ol.geom.Geometry>} geometries Geometries. + * Fires the provided callback just before the current callstack unwinds, or as + * soon as possible after the current JS execution context. + * @param {function(this:THIS)} callback + * @param {THIS=} opt_context Object to use as the "this value" when calling + * the provided function. + * @template THIS */ -ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) { - this.unlistenGeometriesChange_(); - this.geometries_ = geometries; - this.listenGeometriesChange_(); - this.changed(); +goog.async.run = function(callback, opt_context) { + if (!goog.async.run.schedule_) { + goog.async.run.initializeRunner_(); + } + if (!goog.async.run.workQueueScheduled_) { + // Nothing is currently scheduled, schedule it now. + goog.async.run.schedule_(); + goog.async.run.workQueueScheduled_ = true; + } + + goog.async.run.workQueue_.push( + new goog.async.run.WorkItem_(callback, opt_context)); }; /** - * @inheritDoc - * @api stable + * Initializes the function to use to process the work queue. + * @private */ -ol.geom.GeometryCollection.prototype.applyTransform = function(transformFn) { - var geometries = this.geometries_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - geometries[i].applyTransform(transformFn); +goog.async.run.initializeRunner_ = function() { + // If native Promises are available in the browser, just schedule the callback + // on a fulfilled promise, which is specified to be async, but as fast as + // possible. + if (goog.global.Promise && goog.global.Promise.resolve) { + var promise = goog.global.Promise.resolve(); + goog.async.run.schedule_ = function() { + promise.then(goog.async.run.processWorkQueue); + }; + } else { + goog.async.run.schedule_ = function() { + goog.async.nextTick(goog.async.run.processWorkQueue); + }; } - this.changed(); }; /** - * Translate the geometry. - * @param {number} deltaX Delta X. - * @param {number} deltaY Delta Y. - * @api + * Forces goog.async.run to use nextTick instead of Promise. + * + * This should only be done in unit tests. It's useful because MockClock + * replaces nextTick, but not the browser Promise implementation, so it allows + * Promise-based code to be tested with MockClock. */ -ol.geom.GeometryCollection.prototype.translate = function(deltaX, deltaY) { - var geometries = this.geometries_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - geometries[i].translate(deltaX, deltaY); - } - this.changed(); +goog.async.run.forceNextTick = function() { + goog.async.run.schedule_ = function() { + goog.async.nextTick(goog.async.run.processWorkQueue); + }; }; /** - * @inheritDoc + * The function used to schedule work asynchronousely. + * @private {function()} */ -ol.geom.GeometryCollection.prototype.disposeInternal = function() { - this.unlistenGeometriesChange_(); - goog.base(this, 'disposeInternal'); -}; +goog.async.run.schedule_; -goog.provide('ol.geom.flat.interpolate'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.math'); +/** @private {boolean} */ +goog.async.run.workQueueScheduled_ = false; + + +/** @private {!Array<!goog.async.run.WorkItem_>} */ +goog.async.run.workQueue_ = []; + + +if (goog.DEBUG) { + /** + * Reset the event queue. + * @private + */ + goog.async.run.resetQueue_ = function() { + goog.async.run.workQueueScheduled_ = false; + goog.async.run.workQueue_ = []; + }; + + // If there is a clock implemenation in use for testing + // and it is reset, reset the queue. + goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_); +} /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} fraction Fraction. - * @param {Array.<number>=} opt_dest Destination. - * @return {Array.<number>} Destination. + * Run any pending goog.async.run work items. This function is not intended + * for general use, but for use by entry point handlers to run items ahead of + * goog.async.nextTick. */ -ol.geom.flat.interpolate.lineString = - function(flatCoordinates, offset, end, stride, fraction, opt_dest) { - // FIXME interpolate extra dimensions - goog.asserts.assert(0 <= fraction && fraction <= 1); - var pointX = NaN; - var pointY = NaN; - var n = (end - offset) / stride; - if (n === 0) { - goog.asserts.fail(); - } else if (n == 1) { - pointX = flatCoordinates[offset]; - pointY = flatCoordinates[offset + 1]; - } else if (n == 2) { - pointX = (1 - fraction) * flatCoordinates[offset] + - fraction * flatCoordinates[offset + stride]; - pointY = (1 - fraction) * flatCoordinates[offset + 1] + - fraction * flatCoordinates[offset + stride + 1]; - } else { - var x1 = flatCoordinates[offset]; - var y1 = flatCoordinates[offset + 1]; - var length = 0; - var cumulativeLengths = [0]; - var i; - for (i = offset + stride; i < end; i += stride) { - var x2 = flatCoordinates[i]; - var y2 = flatCoordinates[i + 1]; - length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); - cumulativeLengths.push(length); - x1 = x2; - y1 = y2; - } - var target = fraction * length; - var index = goog.array.binarySearch(cumulativeLengths, target); - if (index < 0) { - var t = (target - cumulativeLengths[-index - 2]) / - (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]); - var o = offset + (-index - 2) * stride; - pointX = goog.math.lerp( - flatCoordinates[o], flatCoordinates[o + stride], t); - pointY = goog.math.lerp( - flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t); - } else { - pointX = flatCoordinates[offset + index * stride]; - pointY = flatCoordinates[offset + index * stride + 1]; +goog.async.run.processWorkQueue = function() { + // NOTE: additional work queue items may be pushed while processing. + while (goog.async.run.workQueue_.length) { + // Don't let the work queue grow indefinitely. + var workItems = goog.async.run.workQueue_; + goog.async.run.workQueue_ = []; + for (var i = 0; i < workItems.length; i++) { + var workItem = workItems[i]; + try { + workItem.fn.call(workItem.scope); + } catch (e) { + goog.async.throwException(e); + } } } - if (goog.isDefAndNotNull(opt_dest)) { - opt_dest[0] = pointX; - opt_dest[1] = pointY; - return opt_dest; - } else { - return [pointX, pointY]; - } + + // There are no more work items, reset the work queue. + goog.async.run.workQueueScheduled_ = false; }; + /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {number} m M. - * @param {boolean} extrapolate Extrapolate. - * @return {ol.Coordinate} Coordinate. + * @constructor + * @final + * @struct + * @private + * + * @param {function()} fn + * @param {Object|null|undefined} scope */ -ol.geom.flat.lineStringCoordinateAtM = - function(flatCoordinates, offset, end, stride, m, extrapolate) { - if (end == offset) { - return null; - } - var coordinate; - if (m < flatCoordinates[offset + stride - 1]) { - if (extrapolate) { - coordinate = flatCoordinates.slice(offset, offset + stride); - coordinate[stride - 1] = m; - return coordinate; - } else { - return null; - } - } else if (flatCoordinates[end - 1] < m) { - if (extrapolate) { - coordinate = flatCoordinates.slice(end - stride, end); - coordinate[stride - 1] = m; - return coordinate; - } else { - return null; - } - } - // FIXME use O(1) search - if (m == flatCoordinates[offset + stride - 1]) { - return flatCoordinates.slice(offset, offset + stride); - } - var lo = offset / stride; - var hi = end / stride; - while (lo < hi) { - var mid = (lo + hi) >> 1; - if (m < flatCoordinates[(mid + 1) * stride - 1]) { - hi = mid; - } else { - lo = mid + 1; - } - } - var m0 = flatCoordinates[lo * stride - 1]; - if (m == m0) { - return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride); - } - var m1 = flatCoordinates[(lo + 1) * stride - 1]; - goog.asserts.assert(m0 < m); - goog.asserts.assert(m <= m1); - var t = (m - m0) / (m1 - m0); - coordinate = []; - var i; - for (i = 0; i < stride - 1; ++i) { - coordinate.push(goog.math.lerp(flatCoordinates[(lo - 1) * stride + i], - flatCoordinates[lo * stride + i], t)); - } - coordinate.push(m); - goog.asserts.assert(coordinate.length == stride); - return coordinate; +goog.async.run.WorkItem_ = function(fn, scope) { + /** @const */ this.fn = fn; + /** @const */ this.scope = scope; }; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.promise.Resolver'); + + /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<number>} ends Ends. - * @param {number} stride Stride. - * @param {number} m M. - * @param {boolean} extrapolate Extrapolate. - * @param {boolean} interpolate Interpolate. - * @return {ol.Coordinate} Coordinate. + * Resolver interface for promises. The resolver is a convenience interface that + * bundles the promise and its associated resolve and reject functions together, + * for cases where the resolver needs to be persisted internally. + * + * @interface + * @template TYPE */ -ol.geom.flat.lineStringsCoordinateAtM = function( - flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) { - if (interpolate) { - return ol.geom.flat.lineStringCoordinateAtM( - flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate); - } - var coordinate; - if (m < flatCoordinates[stride - 1]) { - if (extrapolate) { - coordinate = flatCoordinates.slice(0, stride); - coordinate[stride - 1] = m; - return coordinate; - } else { - return null; - } - } - if (flatCoordinates[flatCoordinates.length - 1] < m) { - if (extrapolate) { - coordinate = flatCoordinates.slice(flatCoordinates.length - stride); - coordinate[stride - 1] = m; - return coordinate; - } else { - return null; - } - } - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - if (offset == end) { - continue; - } - if (m < flatCoordinates[offset + stride - 1]) { - return null; - } else if (m <= flatCoordinates[end - 1]) { - return ol.geom.flat.lineStringCoordinateAtM( - flatCoordinates, offset, end, stride, m, false); - } - offset = end; - } - goog.asserts.fail(); - return null; -}; +goog.promise.Resolver = function() {}; -goog.provide('ol.geom.flat.length'); + +/** + * The promise that created this resolver. + * @type {!goog.Promise<TYPE>} + */ +goog.promise.Resolver.prototype.promise; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} Length. + * Resolves this resolver with the specified value. + * @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)} */ -ol.geom.flat.length.lineString = - function(flatCoordinates, offset, end, stride) { - var x1 = flatCoordinates[offset]; - var y1 = flatCoordinates[offset + 1]; - var length = 0; - var i; - for (i = offset + stride; i < end; i += stride) { - var x2 = flatCoordinates[i]; - var y2 = flatCoordinates[i + 1]; - length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); - x1 = x2; - y1 = y2; - } - return length; -}; +goog.promise.Resolver.prototype.resolve; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} Perimeter. + * Rejects this resolver with the specified reason. + * @type {function(*): void} */ -ol.geom.flat.length.linearRing = - function(flatCoordinates, offset, end, stride) { - var perimeter = - ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride); - var dx = flatCoordinates[end - stride] - flatCoordinates[offset]; - var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1]; - perimeter += Math.sqrt(dx * dx + dy * dy); - return perimeter; -}; +goog.promise.Resolver.prototype.reject; -goog.provide('ol.geom.LineString'); +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -goog.require('goog.array'); +goog.provide('goog.Promise'); + +goog.require('goog.Thenable'); goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.closest'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.geom.flat.interpolate'); -goog.require('ol.geom.flat.intersectsextent'); -goog.require('ol.geom.flat.length'); -goog.require('ol.geom.flat.simplify'); +goog.require('goog.async.run'); +goog.require('goog.async.throwException'); +goog.require('goog.debug.Error'); +goog.require('goog.promise.Resolver'); /** - * @classdesc - * Linestring geometry. + * Promises provide a result that may be resolved asynchronously. A Promise may + * be resolved by being fulfilled or rejected with a value, which will be known + * as the fulfillment value or the rejection reason. Whether fulfilled or + * rejected, the Promise result is immutable once it is set. + * + * Promises may represent results of any type, including undefined. Rejection + * reasons are typically Errors, but may also be of any type. Closure Promises + * allow for optional type annotations that enforce that fulfillment values are + * of the appropriate types at compile time. + * + * The result of a Promise is accessible by calling {@code then} and registering + * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise + * resolves, the relevant callbacks are invoked with the fulfillment value or + * rejection reason as argument. Callbacks are always invoked in the order they + * were registered, even when additional {@code then} calls are made from inside + * another callback. A callback is always run asynchronously sometime after the + * scope containing the registering {@code then} invocation has returned. + * + * If a Promise is resolved with another Promise, the first Promise will block + * until the second is resolved, and then assumes the same result as the second + * Promise. This allows Promises to depend on the results of other Promises, + * linking together multiple asynchronous operations. + * + * This implementation is compatible with the Promises/A+ specification and + * passes that specification's conformance test suite. A Closure Promise may be + * resolved with a Promise instance (or sufficiently compatible Promise-like + * object) created by other Promise implementations. From the specification, + * Promise-like objects are known as "Thenables". * + * @see http://promisesaplus.com/ + * + * @param {function( + * this:RESOLVER_CONTEXT, + * function((TYPE|IThenable<TYPE>|Thenable)=), + * function(*)): void} resolver + * Initialization function that is invoked immediately with {@code resolve} + * and {@code reject} functions as arguments. The Promise is resolved or + * rejected with the first argument passed to either function. + * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the + * resolver function. If unspecified, the resolver function will be executed + * in the default scope. * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @struct + * @final + * @implements {goog.Thenable<TYPE>} + * @template TYPE,RESOLVER_CONTEXT */ -ol.geom.LineString = function(coordinates, opt_layout) { - - goog.base(this); +goog.Promise = function(resolver, opt_context) { + /** + * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or + * BLOCKED. + * @private {goog.Promise.State_} + */ + this.state_ = goog.Promise.State_.PENDING; /** - * @private - * @type {ol.Coordinate} + * The resolved result of the Promise. Immutable once set with either a + * fulfillment value or rejection reason. + * @private {*} */ - this.flatMidpoint_ = null; + this.result_ = undefined; /** - * @private - * @type {number} + * For Promises created by calling {@code then()}, the originating parent. + * @private {goog.Promise} */ - this.flatMidpointRevision_ = -1; + this.parent_ = null; /** - * @private - * @type {number} + * The list of {@code onFulfilled} and {@code onRejected} callbacks added to + * this Promise by calls to {@code then()}. + * @private {Array<goog.Promise.CallbackEntry_>} */ - this.maxDelta_ = -1; + this.callbackEntries_ = null; /** - * @private - * @type {number} + * Whether the Promise is in the queue of Promises to execute. + * @private {boolean} */ - this.maxDeltaRevision_ = -1; + this.executing_ = false; - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); + if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { + /** + * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater + * than 0 milliseconds. The ID is set when the Promise is rejected, and + * cleared only if an {@code onRejected} callback is invoked for the + * Promise (or one of its descendants) before the delay is exceeded. + * + * If the rejection is not handled before the timeout completes, the + * rejection reason is passed to the unhandled rejection handler. + * @private {number} + */ + this.unhandledRejectionId_ = 0; + } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { + /** + * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a + * boolean that is set if the Promise is rejected, and reset to false if an + * {@code onRejected} callback is invoked for the Promise (or one of its + * descendants). If the rejection is not handled before the next timestep, + * the rejection reason is passed to the unhandled rejection handler. + * @private {boolean} + */ + this.hadUnhandledRejection_ = false; + } + + if (goog.Promise.LONG_STACK_TRACES) { + /** + * A list of stack trace frames pointing to the locations where this Promise + * was created or had callbacks added to it. Saved to add additional context + * to stack traces when an exception is thrown. + * @private {!Array<string>} + */ + this.stack_ = []; + this.addStackTrace_(new Error('created')); + + /** + * Index of the most recently executed stack frame entry. + * @private {number} + */ + this.currentStep_ = 0; + } + try { + var self = this; + resolver.call( + opt_context, + function(value) { + self.resolve_(goog.Promise.State_.FULFILLED, value); + }, + function(reason) { + if (goog.DEBUG && + !(reason instanceof goog.Promise.CancellationError)) { + try { + // Promise was rejected. Step up one call frame to see why. + if (reason instanceof Error) { + throw reason; + } else { + throw new Error('Promise rejected.'); + } + } catch (e) { + // Only thrown so browser dev tools can catch rejections of + // promises when the option to break on caught exceptions is + // activated. + } + } + self.resolve_(goog.Promise.State_.REJECTED, reason); + }); + } catch (e) { + this.resolve_(goog.Promise.State_.REJECTED, e); + } }; -goog.inherits(ol.geom.LineString, ol.geom.SimpleGeometry); /** - * @param {ol.Coordinate} coordinate Coordinate. - * @api stable + * @define {boolean} Whether traces of {@code then} calls should be included in + * exceptions thrown */ -ol.geom.LineString.prototype.appendCoordinate = function(coordinate) { - goog.asserts.assert(coordinate.length == this.stride); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = coordinate.slice(); - } else { - goog.array.extend(this.flatCoordinates, coordinate); - } - this.changed(); -}; +goog.define('goog.Promise.LONG_STACK_TRACES', false); /** - * Make a complete copy of the geometry. - * @return {!ol.geom.LineString} Clone. - * @api stable + * @define {number} The delay in milliseconds before a rejected Promise's reason + * is passed to the rejection handler. By default, the rejection handler + * rethrows the rejection reason so that it appears in the developer console or + * {@code window.onerror} handler. + * + * Rejections are rethrown as quickly as possible by default. A negative value + * disables rejection handling entirely. */ -ol.geom.LineString.prototype.clone = function() { - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); - return lineString; -}; +goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0); /** - * @inheritDoc + * The possible internal states for a Promise. These states are not directly + * observable to external callers. + * @enum {number} + * @private */ -ol.geom.LineString.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); - } - return ol.geom.flat.closest.getClosestPoint( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); +goog.Promise.State_ = { + /** The Promise is waiting for resolution. */ + PENDING: 0, + + /** The Promise is blocked waiting for the result of another Thenable. */ + BLOCKED: 1, + + /** The Promise has been resolved with a fulfillment value. */ + FULFILLED: 2, + + /** The Promise has been resolved with a rejection reason. */ + REJECTED: 3 }; /** - * Returns the coordinate at `m` using linear interpolation, or `null` if no - * such coordinate exists. - * - * `opt_extrapolate` controls extrapolation beyond the range of Ms in the - * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first - * M will return the first coordinate and Ms greater than the last M will - * return the last coordinate. + * Typedef for entries in the callback chain. Each call to {@code then}, + * {@code thenCatch}, or {@code thenAlways} creates an entry containing the + * functions that may be invoked once the Promise is resolved. * - * @param {number} m M. - * @param {boolean=} opt_extrapolate Extrapolate. - * @return {ol.Coordinate} Coordinate. - * @api stable + * @typedef {{ + * child: goog.Promise, + * onFulfilled: function(*), + * onRejected: function(*) + * }} + * @private */ -ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) { - if (this.layout != ol.geom.GeometryLayout.XYM && - this.layout != ol.geom.GeometryLayout.XYZM) { - return null; - } - var extrapolate = goog.isDef(opt_extrapolate) ? opt_extrapolate : false; - return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0, - this.flatCoordinates.length, this.stride, m, extrapolate); -}; +goog.Promise.CallbackEntry_; /** - * @return {Array.<ol.Coordinate>} Coordinates. - * @api stable + * @param {(TYPE|goog.Thenable<TYPE>|Thenable)=} opt_value + * @return {!goog.Promise<TYPE>} A new Promise that is immediately resolved + * with the given value. + * @template TYPE */ -ol.geom.LineString.prototype.getCoordinates = function() { - return ol.geom.flat.inflate.coordinates( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +goog.Promise.resolve = function(opt_value) { + return new goog.Promise(function(resolve, reject) { + resolve(opt_value); + }); }; /** - * @return {number} Length (on projected plane). - * @api stable + * @param {*=} opt_reason + * @return {!goog.Promise} A new Promise that is immediately rejected with the + * given reason. */ -ol.geom.LineString.prototype.getLength = function() { - return ol.geom.flat.length.lineString( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); +goog.Promise.reject = function(opt_reason) { + return new goog.Promise(function(resolve, reject) { + reject(opt_reason); + }); }; /** - * @return {Array.<number>} Flat midpoint. + * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises + * @return {!goog.Promise<TYPE>} A Promise that receives the result of the + * first Promise (or Promise-like) input to complete. + * @template TYPE */ -ol.geom.LineString.prototype.getFlatMidpoint = function() { - if (this.flatMidpointRevision_ != this.getRevision()) { - this.flatMidpoint_ = ol.geom.flat.interpolate.lineString( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - 0.5, this.flatMidpoint_); - this.flatMidpointRevision_ = this.getRevision(); - } - return this.flatMidpoint_; +goog.Promise.race = function(promises) { + return new goog.Promise(function(resolve, reject) { + if (!promises.length) { + resolve(undefined); + } + for (var i = 0, promise; promise = promises[i]; i++) { + promise.then(resolve, reject); + } + }); }; /** - * @inheritDoc + * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises + * @return {!goog.Promise<!Array<TYPE>>} A Promise that receives a list of + * every fulfilled value once every input Promise (or Promise-like) is + * successfully fulfilled, or is rejected by the first rejection result. + * @template TYPE */ -ol.geom.LineString.prototype.getSimplifiedGeometryInternal = - function(squaredTolerance) { - var simplifiedFlatCoordinates = []; - simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - squaredTolerance, simplifiedFlatCoordinates, 0); - var simplifiedLineString = new ol.geom.LineString(null); - simplifiedLineString.setFlatCoordinates( - ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates); - return simplifiedLineString; -}; +goog.Promise.all = function(promises) { + return new goog.Promise(function(resolve, reject) { + var toFulfill = promises.length; + var values = []; + if (!toFulfill) { + resolve(values); + return; + } -/** - * @inheritDoc - * @api stable - */ -ol.geom.LineString.prototype.getType = function() { - return ol.geom.GeometryType.LINE_STRING; -}; + var onFulfill = function(index, value) { + toFulfill--; + values[index] = value; + if (toFulfill == 0) { + resolve(values); + } + }; + var onReject = function(reason) { + reject(reason); + }; -/** - * @inheritDoc - * @api - */ -ol.geom.LineString.prototype.intersectsExtent = function(extent) { - return ol.geom.flat.intersectsextent.lineString( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, - extent); + for (var i = 0, promise; promise = promises[i]; i++) { + promise.then(goog.partial(onFulfill, i), onReject); + } + }); }; /** - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises + * @return {!goog.Promise<TYPE>} A Promise that receives the value of the first + * input to be fulfilled, or is rejected with a list of every rejection + * reason if all inputs are rejected. + * @template TYPE */ -ol.geom.LineString.prototype.setCoordinates = - function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); - } else { - this.setLayout(opt_layout, coordinates, 1); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; +goog.Promise.firstFulfilled = function(promises) { + return new goog.Promise(function(resolve, reject) { + var toReject = promises.length; + var reasons = []; + + if (!toReject) { + resolve(undefined); + return; } - this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( - this.flatCoordinates, 0, coordinates, this.stride); - this.changed(); - } + + var onFulfill = function(value) { + resolve(value); + }; + + var onReject = function(index, reason) { + toReject--; + reasons[index] = reason; + if (toReject == 0) { + reject(reasons); + } + }; + + for (var i = 0, promise; promise = promises[i]; i++) { + promise.then(onFulfill, goog.partial(onReject, i)); + } + }); }; /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. + * @return {!goog.promise.Resolver<TYPE>} Resolver wrapping the promise and its + * resolve / reject functions. Resolving or rejecting the resolver + * resolves or rejects the promise. + * @template TYPE */ -ol.geom.LineString.prototype.setFlatCoordinates = - function(layout, flatCoordinates) { - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.changed(); +goog.Promise.withResolver = function() { + var resolve, reject; + var promise = new goog.Promise(function(rs, rj) { + resolve = rs; + reject = rj; + }); + return new goog.Promise.Resolver_(promise, resolve, reject); }; -goog.provide('ol.geom.MultiLineString'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.closest'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.geom.flat.interpolate'); -goog.require('ol.geom.flat.intersectsextent'); -goog.require('ol.geom.flat.simplify'); - - /** - * @classdesc - * Multi-linestring geometry. + * Adds callbacks that will operate on the result of the Promise, returning a + * new child Promise. * - * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked + * with the fulfillment value as argument, and the child Promise will be + * fulfilled with the return value of the callback. If the callback throws an + * exception, the child Promise will be rejected with the thrown value instead. + * + * If the Promise is rejected, the {@code onRejected} callback will be invoked + * with the rejection reason as argument, and the child Promise will be resolved + * with the return value or rejected with the thrown value of the callback. + * + * @override */ -ol.geom.MultiLineString = function(coordinates, opt_layout) { +goog.Promise.prototype.then = function( + opt_onFulfilled, opt_onRejected, opt_context) { - goog.base(this); + if (opt_onFulfilled != null) { + goog.asserts.assertFunction(opt_onFulfilled, + 'opt_onFulfilled should be a function.'); + } + if (opt_onRejected != null) { + goog.asserts.assertFunction(opt_onRejected, + 'opt_onRejected should be a function. Did you pass opt_context ' + + 'as the second argument instead of the third?'); + } - /** - * @type {Array.<number>} - * @private - */ - this.ends_ = []; + if (goog.Promise.LONG_STACK_TRACES) { + this.addStackTrace_(new Error('then')); + } - /** - * @private - * @type {number} - */ - this.maxDelta_ = -1; + return this.addChildPromise_( + goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null, + goog.isFunction(opt_onRejected) ? opt_onRejected : null, + opt_context); +}; +goog.Thenable.addImplementation(goog.Promise); - /** - * @private - * @type {number} - */ - this.maxDeltaRevision_ = -1; - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); +/** + * Adds a callback that will be invoked whether the Promise is fulfilled or + * rejected. The callback receives no argument, and no new child Promise is + * created. This is useful for ensuring that cleanup takes place after certain + * asynchronous operations. Callbacks added with {@code thenAlways} will be + * executed in the same order with other calls to {@code then}, + * {@code thenAlways}, or {@code thenCatch}. + * + * Since it does not produce a new child Promise, cancellation propagation is + * not prevented by adding callbacks with {@code thenAlways}. A Promise that has + * a cleanup handler added with {@code thenAlways} will be canceled if all of + * its children created by {@code then} (or {@code thenCatch}) are canceled. + * Additionally, since any rejections are not passed to the callback, it does + * not stop the unhandled rejection handler from running. + * + * @param {function(this:THIS): void} onResolved A function that will be invoked + * when the Promise is resolved. + * @param {THIS=} opt_context An optional context object that will be the + * execution context for the callbacks. By default, functions are executed + * in the global scope. + * @return {!goog.Promise<TYPE>} This Promise, for chaining additional calls. + * @template THIS + */ +goog.Promise.prototype.thenAlways = function(onResolved, opt_context) { + if (goog.Promise.LONG_STACK_TRACES) { + this.addStackTrace_(new Error('thenAlways')); + } + + var callback = function() { + try { + // Ensure that no arguments are passed to onResolved. + onResolved.call(opt_context); + } catch (err) { + goog.Promise.handleRejection_.call(null, err); + } + }; + this.addCallbackEntry_({ + child: null, + onRejected: callback, + onFulfilled: callback + }); + return this; }; -goog.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry); /** - * @param {ol.geom.LineString} lineString LineString. - * @api stable + * Adds a callback that will be invoked only if the Promise is rejected. This + * is equivalent to {@code then(null, onRejected)}. + * + * @param {!function(this:THIS, *): *} onRejected A function that will be + * invoked with the rejection reason if the Promise is rejected. + * @param {THIS=} opt_context An optional context object that will be the + * execution context for the callbacks. By default, functions are executed + * in the global scope. + * @return {!goog.Promise} A new Promise that will receive the result of the + * callback. + * @template THIS */ -ol.geom.MultiLineString.prototype.appendLineString = function(lineString) { - goog.asserts.assert(lineString.getLayout() == this.layout); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = lineString.getFlatCoordinates().slice(); - } else { - goog.array.extend( - this.flatCoordinates, lineString.getFlatCoordinates().slice()); +goog.Promise.prototype.thenCatch = function(onRejected, opt_context) { + if (goog.Promise.LONG_STACK_TRACES) { + this.addStackTrace_(new Error('thenCatch')); } - this.ends_.push(this.flatCoordinates.length); - this.changed(); + return this.addChildPromise_(null, onRejected, opt_context); }; /** - * Make a complete copy of the geometry. - * @return {!ol.geom.MultiLineString} Clone. - * @api stable + * Cancels the Promise if it is still pending by rejecting it with a cancel + * Error. No action is performed if the Promise is already resolved. + * + * All child Promises of the canceled Promise will be rejected with the same + * cancel error, as with normal Promise rejection. If the Promise to be canceled + * is the only child of a pending Promise, the parent Promise will also be + * canceled. Cancellation may propagate upward through multiple generations. + * + * @param {string=} opt_message An optional debugging message for describing the + * cancellation reason. */ -ol.geom.MultiLineString.prototype.clone = function() { - var multiLineString = new ol.geom.MultiLineString(null); - multiLineString.setFlatCoordinates( - this.layout, this.flatCoordinates.slice(), this.ends_.slice()); - return multiLineString; +goog.Promise.prototype.cancel = function(opt_message) { + if (this.state_ == goog.Promise.State_.PENDING) { + goog.async.run(function() { + var err = new goog.Promise.CancellationError(opt_message); + this.cancelInternal_(err); + }, this); + } }; /** - * @inheritDoc + * Cancels this Promise with the given error. + * + * @param {!Error} err The cancellation error. + * @private */ -ol.geom.MultiLineString.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta( - this.flatCoordinates, 0, this.ends_, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); +goog.Promise.prototype.cancelInternal_ = function(err) { + if (this.state_ == goog.Promise.State_.PENDING) { + if (this.parent_) { + // Cancel the Promise and remove it from the parent's child list. + this.parent_.cancelChild_(this, err); + } else { + this.resolve_(goog.Promise.State_.REJECTED, err); + } } - return ol.geom.flat.closest.getsClosestPoint( - this.flatCoordinates, 0, this.ends_, this.stride, - this.maxDelta_, false, x, y, closestPoint, minSquaredDistance); }; /** - * Returns the coordinate at `m` using linear interpolation, or `null` if no - * such coordinate exists. - * - * `opt_extrapolate` controls extrapolation beyond the range of Ms in the - * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first - * M will return the first coordinate and Ms greater than the last M will - * return the last coordinate. - * - * `opt_interpolate` controls interpolation between consecutive LineStrings - * within the MultiLineString. If `opt_interpolate` is `true` the coordinates - * will be linearly interpolated between the last coordinate of one LineString - * and the first coordinate of the next LineString. If `opt_interpolate` is - * `false` then the function will return `null` for Ms falling between - * LineStrings. + * Cancels a child Promise from the list of callback entries. If the Promise has + * not already been resolved, reject it with a cancel error. If there are no + * other children in the list of callback entries, propagate the cancellation + * by canceling this Promise as well. * - * @param {number} m M. - * @param {boolean=} opt_extrapolate Extrapolate. - * @param {boolean=} opt_interpolate Interpolate. - * @return {ol.Coordinate} Coordinate. - * @api stable + * @param {!goog.Promise} childPromise The Promise to cancel. + * @param {!Error} err The cancel error to use for rejecting the Promise. + * @private */ -ol.geom.MultiLineString.prototype.getCoordinateAtM = - function(m, opt_extrapolate, opt_interpolate) { - if ((this.layout != ol.geom.GeometryLayout.XYM && - this.layout != ol.geom.GeometryLayout.XYZM) || - this.flatCoordinates.length === 0) { - return null; +goog.Promise.prototype.cancelChild_ = function(childPromise, err) { + if (!this.callbackEntries_) { + return; + } + var childCount = 0; + var childIndex = -1; + + // Find the callback entry for the childPromise, and count whether there are + // additional child Promises. + for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) { + var child = entry.child; + if (child) { + childCount++; + if (child == childPromise) { + childIndex = i; + } + if (childIndex >= 0 && childCount > 1) { + break; + } + } + } + + // If the child Promise was the only child, cancel this Promise as well. + // Otherwise, reject only the child Promise with the cancel error. + if (childIndex >= 0) { + if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) { + this.cancelInternal_(err); + } else { + var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0]; + this.executeCallback_( + callbackEntry, goog.Promise.State_.REJECTED, err); + } } - var extrapolate = goog.isDef(opt_extrapolate) ? opt_extrapolate : false; - var interpolate = goog.isDef(opt_interpolate) ? opt_interpolate : false; - return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0, - this.ends_, this.stride, m, extrapolate, interpolate); }; /** - * @return {Array.<Array.<ol.Coordinate>>} Coordinates. - * @api stable + * Adds a callback entry to the current Promise, and schedules callback + * execution if the Promise has already been resolved. + * + * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing + * {@code onFulfilled} and {@code onRejected} callbacks to execute after + * the Promise is resolved. + * @private */ -ol.geom.MultiLineString.prototype.getCoordinates = function() { - return ol.geom.flat.inflate.coordinatess( - this.flatCoordinates, 0, this.ends_, this.stride); +goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) { + if ((!this.callbackEntries_ || !this.callbackEntries_.length) && + (this.state_ == goog.Promise.State_.FULFILLED || + this.state_ == goog.Promise.State_.REJECTED)) { + this.scheduleCallbacks_(); + } + if (!this.callbackEntries_) { + this.callbackEntries_ = []; + } + this.callbackEntries_.push(callbackEntry); }; /** - * @return {Array.<number>} Ends. + * Creates a child Promise and adds it to the callback entry list. The result of + * the child Promise is determined by the state of the parent Promise and the + * result of the {@code onFulfilled} or {@code onRejected} callbacks as + * specified in the Promise resolution procedure. + * + * @see http://promisesaplus.com/#the__method + * + * @param {?function(this:THIS, TYPE): + * (RESULT|goog.Promise<RESULT>|Thenable)} onFulfilled A callback that + * will be invoked if the Promise is fullfilled, or null. + * @param {?function(this:THIS, *): *} onRejected A callback that will be + * invoked if the Promise is rejected, or null. + * @param {THIS=} opt_context An optional execution context for the callbacks. + * in the default calling context. + * @return {!goog.Promise} The child Promise. + * @template RESULT,THIS + * @private */ -ol.geom.MultiLineString.prototype.getEnds = function() { - return this.ends_; +goog.Promise.prototype.addChildPromise_ = function( + onFulfilled, onRejected, opt_context) { + + var callbackEntry = { + child: null, + onFulfilled: null, + onRejected: null + }; + + callbackEntry.child = new goog.Promise(function(resolve, reject) { + // Invoke onFulfilled, or resolve with the parent's value if absent. + callbackEntry.onFulfilled = onFulfilled ? function(value) { + try { + var result = onFulfilled.call(opt_context, value); + resolve(result); + } catch (err) { + reject(err); + } + } : resolve; + + // Invoke onRejected, or reject with the parent's reason if absent. + callbackEntry.onRejected = onRejected ? function(reason) { + try { + var result = onRejected.call(opt_context, reason); + if (!goog.isDef(result) && + reason instanceof goog.Promise.CancellationError) { + // Propagate cancellation to children if no other result is returned. + reject(reason); + } else { + resolve(result); + } + } catch (err) { + reject(err); + } + } : reject; + }); + + callbackEntry.child.parent_ = this; + this.addCallbackEntry_( + /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry)); + return callbackEntry.child; }; /** - * @param {number} index Index. - * @return {ol.geom.LineString} LineString. - * @api stable + * Unblocks the Promise and fulfills it with the given value. + * + * @param {TYPE} value + * @private */ -ol.geom.MultiLineString.prototype.getLineString = function(index) { - goog.asserts.assert(0 <= index && index < this.ends_.length); - if (index < 0 || this.ends_.length <= index) { - return null; - } - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice( - index === 0 ? 0 : this.ends_[index - 1], this.ends_[index])); - return lineString; +goog.Promise.prototype.unblockAndFulfill_ = function(value) { + goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED); + this.state_ = goog.Promise.State_.PENDING; + this.resolve_(goog.Promise.State_.FULFILLED, value); }; /** - * @return {Array.<ol.geom.LineString>} LineStrings. - * @api stable + * Unblocks the Promise and rejects it with the given rejection reason. + * + * @param {*} reason + * @private */ -ol.geom.MultiLineString.prototype.getLineStrings = function() { - var flatCoordinates = this.flatCoordinates; - var ends = this.ends_; - var layout = this.layout; - /** @type {Array.<ol.geom.LineString>} */ - var lineStrings = []; - var offset = 0; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end)); - lineStrings.push(lineString); - offset = end; - } - return lineStrings; +goog.Promise.prototype.unblockAndReject_ = function(reason) { + goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED); + this.state_ = goog.Promise.State_.PENDING; + this.resolve_(goog.Promise.State_.REJECTED, reason); }; /** - * @return {Array.<number>} Flat midpoints. + * Attempts to resolve a Promise with a given resolution state and value. This + * is a no-op if the given Promise has already been resolved. + * + * If the given result is a Thenable (such as another Promise), the Promise will + * be resolved with the same state and result as the Thenable once it is itself + * resolved. + * + * If the given result is not a Thenable, the Promise will be fulfilled or + * rejected with that result based on the given state. + * + * @see http://promisesaplus.com/#the_promise_resolution_procedure + * + * @param {goog.Promise.State_} state + * @param {*} x The result to apply to the Promise. + * @private */ -ol.geom.MultiLineString.prototype.getFlatMidpoints = function() { - var midpoints = []; - var flatCoordinates = this.flatCoordinates; - var offset = 0; - var ends = this.ends_; - var stride = this.stride; - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - var end = ends[i]; - var midpoint = ol.geom.flat.interpolate.lineString( - flatCoordinates, offset, end, stride, 0.5); - goog.array.extend(midpoints, midpoint); - offset = end; +goog.Promise.prototype.resolve_ = function(state, x) { + if (this.state_ != goog.Promise.State_.PENDING) { + return; } - return midpoints; -}; + if (this == x) { + state = goog.Promise.State_.REJECTED; + x = new TypeError('Promise cannot resolve to itself'); -/** - * @inheritDoc - */ -ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = - function(squaredTolerance) { - var simplifiedFlatCoordinates = []; - var simplifiedEnds = []; - simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers( - this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance, - simplifiedFlatCoordinates, 0, simplifiedEnds); - var simplifiedMultiLineString = new ol.geom.MultiLineString(null); - simplifiedMultiLineString.setFlatCoordinates( - ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds); - return simplifiedMultiLineString; -}; + } else if (goog.Thenable.isImplementedBy(x)) { + x = /** @type {!goog.Thenable} */ (x); + this.state_ = goog.Promise.State_.BLOCKED; + x.then(this.unblockAndFulfill_, this.unblockAndReject_, this); + return; + + } else if (goog.isObject(x)) { + try { + var then = x['then']; + if (goog.isFunction(then)) { + this.tryThen_(x, then); + return; + } + } catch (e) { + state = goog.Promise.State_.REJECTED; + x = e; + } + } + this.result_ = x; + this.state_ = state; + this.scheduleCallbacks_(); -/** - * @inheritDoc - * @api stable - */ -ol.geom.MultiLineString.prototype.getType = function() { - return ol.geom.GeometryType.MULTI_LINE_STRING; + if (state == goog.Promise.State_.REJECTED && + !(x instanceof goog.Promise.CancellationError)) { + goog.Promise.addUnhandledRejection_(this, x); + } }; /** - * @inheritDoc - * @api + * Attempts to call the {@code then} method on an object in the hopes that it is + * a Promise-compatible instance. This allows interoperation between different + * Promise implementations, however a non-compliant object may cause a Promise + * to hang indefinitely. If the {@code then} method throws an exception, the + * dependent Promise will be rejected with the thrown value. + * + * @see http://promisesaplus.com/#point-70 + * + * @param {Thenable} thenable An object with a {@code then} method that may be + * compatible with the Promise/A+ specification. + * @param {!Function} then The {@code then} method of the Thenable object. + * @private */ -ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) { - return ol.geom.flat.intersectsextent.lineStrings( - this.flatCoordinates, 0, this.ends_, this.stride, extent); -}; +goog.Promise.prototype.tryThen_ = function(thenable, then) { + this.state_ = goog.Promise.State_.BLOCKED; + var promise = this; + var called = false; + var resolve = function(value) { + if (!called) { + called = true; + promise.unblockAndFulfill_(value); + } + }; -/** - * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable - */ -ol.geom.MultiLineString.prototype.setCoordinates = - function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_); - } else { - this.setLayout(opt_layout, coordinates, 2); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; + var reject = function(reason) { + if (!called) { + called = true; + promise.unblockAndReject_(reason); } - var ends = ol.geom.flat.deflate.coordinatess( - this.flatCoordinates, 0, coordinates, this.stride, this.ends_); - this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1]; - this.changed(); + }; + + try { + then.call(thenable, resolve, reject); + } catch (e) { + reject(e); } }; /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {Array.<number>} ends Ends. + * Executes the pending callbacks of a resolved Promise after a timeout. + * + * Section 2.2.4 of the Promises/A+ specification requires that Promise + * callbacks must only be invoked from a call stack that only contains Promise + * implementation code, which we accomplish by invoking callback execution after + * a timeout. If {@code startExecution_} is called multiple times for the same + * Promise, the callback chain will be evaluated only once. Additional callbacks + * may be added during the evaluation phase, and will be executed in the same + * event loop. + * + * All Promises added to the waiting list during the same browser event loop + * will be executed in one batch to avoid using a separate timeout per Promise. + * + * @private */ -ol.geom.MultiLineString.prototype.setFlatCoordinates = - function(layout, flatCoordinates, ends) { - if (goog.isNull(flatCoordinates)) { - goog.asserts.assert(!goog.isNull(ends) && ends.length === 0); - } else if (ends.length === 0) { - goog.asserts.assert(flatCoordinates.length === 0); - } else { - goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]); +goog.Promise.prototype.scheduleCallbacks_ = function() { + if (!this.executing_) { + this.executing_ = true; + goog.async.run(this.executeCallbacks_, this); } - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.ends_ = ends; - this.changed(); }; /** - * @param {Array.<ol.geom.LineString>} lineStrings LineStrings. + * Executes all pending callbacks for this Promise. + * + * @private */ -ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) { - var layout = ol.geom.GeometryLayout.XY; - var flatCoordinates = []; - var ends = []; - var i, ii; - for (i = 0, ii = lineStrings.length; i < ii; ++i) { - var lineString = lineStrings[i]; - if (i === 0) { - layout = lineString.getLayout(); - } else { - // FIXME better handle the case of non-matching layouts - goog.asserts.assert(lineString.getLayout() == layout); +goog.Promise.prototype.executeCallbacks_ = function() { + while (this.callbackEntries_ && this.callbackEntries_.length) { + var entries = this.callbackEntries_; + this.callbackEntries_ = []; + + for (var i = 0; i < entries.length; i++) { + if (goog.Promise.LONG_STACK_TRACES) { + this.currentStep_++; + } + this.executeCallback_(entries[i], this.state_, this.result_); } - goog.array.extend(flatCoordinates, lineString.getFlatCoordinates()); - ends.push(flatCoordinates.length); } - this.setFlatCoordinates(layout, flatCoordinates, ends); + this.executing_ = false; }; -goog.provide('ol.geom.MultiPoint'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.math'); - - /** - * @classdesc - * Multi-point geometry. + * Executes a pending callback for this Promise. Invokes an {@code onFulfilled} + * or {@code onRejected} callback based on the resolved state of the Promise. * - * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable - */ -ol.geom.MultiPoint = function(coordinates, opt_layout) { - goog.base(this); - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); -}; -goog.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry); - - -/** - * @param {ol.geom.Point} point Point. - * @api stable + * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the + * onFulfilled and/or onRejected callbacks for this step. + * @param {goog.Promise.State_} state The resolution status of the Promise, + * either FULFILLED or REJECTED. + * @param {*} result The resolved result of the Promise. + * @private */ -ol.geom.MultiPoint.prototype.appendPoint = function(point) { - goog.asserts.assert(point.getLayout() == this.layout); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = point.getFlatCoordinates().slice(); +goog.Promise.prototype.executeCallback_ = function( + callbackEntry, state, result) { + if (state == goog.Promise.State_.FULFILLED) { + callbackEntry.onFulfilled(result); } else { - goog.array.extend(this.flatCoordinates, point.getFlatCoordinates()); + if (callbackEntry.child) { + this.removeUnhandledRejection_(); + } + callbackEntry.onRejected(result); } - this.changed(); }; /** - * Make a complete copy of the geometry. - * @return {!ol.geom.MultiPoint} Clone. - * @api stable + * Records a stack trace entry for functions that call {@code then} or the + * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}. + * + * @param {!Error} err An Error object created by the calling function for + * providing a stack trace. + * @private */ -ol.geom.MultiPoint.prototype.clone = function() { - var multiPoint = new ol.geom.MultiPoint(null); - multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice()); - return multiPoint; +goog.Promise.prototype.addStackTrace_ = function(err) { + if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) { + // Extract the third line of the stack trace, which is the entry for the + // user function that called into Promise code. + var trace = err.stack.split('\n', 4)[3]; + var message = err.message; + + // Pad the message to align the traces. + message += Array(11 - message.length).join(' '); + this.stack_.push(message + trace); + } }; /** - * @inheritDoc + * Adds extra stack trace information to an exception for the list of + * asynchronous {@code then} calls that have been run for this Promise. Stack + * trace information is recorded in {@see #addStackTrace_}, and appended to + * rethrown errors when {@code LONG_STACK_TRACES} is enabled. + * + * @param {*} err An unhandled exception captured during callback execution. + * @private */ -ol.geom.MultiPoint.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - var flatCoordinates = this.flatCoordinates; - var stride = this.stride; - var i, ii, j; - for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { - var squaredDistance = ol.math.squaredDistance( - x, y, flatCoordinates[i], flatCoordinates[i + 1]); - if (squaredDistance < minSquaredDistance) { - minSquaredDistance = squaredDistance; - for (j = 0; j < stride; ++j) { - closestPoint[j] = flatCoordinates[i + j]; +goog.Promise.prototype.appendLongStack_ = function(err) { + if (goog.Promise.LONG_STACK_TRACES && + err && goog.isString(err.stack) && this.stack_.length) { + var longTrace = ['Promise trace:']; + + for (var promise = this; promise; promise = promise.parent_) { + for (var i = this.currentStep_; i >= 0; i--) { + longTrace.push(promise.stack_[i]); } - closestPoint.length = stride; + longTrace.push('Value: ' + + '[' + (promise.state_ == goog.Promise.State_.REJECTED ? + 'REJECTED' : 'FULFILLED') + '] ' + + '<' + String(promise.result_) + '>'); } + err.stack += '\n\n' + longTrace.join('\n'); } - return minSquaredDistance; -}; - - -/** - * @return {Array.<ol.Coordinate>} Coordinates. - * @api stable - */ -ol.geom.MultiPoint.prototype.getCoordinates = function() { - return ol.geom.flat.inflate.coordinates( - this.flatCoordinates, 0, this.flatCoordinates.length, this.stride); }; /** - * @param {number} index Index. - * @return {ol.geom.Point} Point. - * @api stable + * Marks this rejected Promise as having being handled. Also marks any parent + * Promises in the rejected state as handled. The rejection handler will no + * longer be invoked for this Promise (if it has not been called already). + * + * @private */ -ol.geom.MultiPoint.prototype.getPoint = function(index) { - var n = goog.isNull(this.flatCoordinates) ? - 0 : this.flatCoordinates.length / this.stride; - goog.asserts.assert(0 <= index && index < n); - if (index < 0 || n <= index) { - return null; +goog.Promise.prototype.removeUnhandledRejection_ = function() { + if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { + for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) { + goog.global.clearTimeout(p.unhandledRejectionId_); + p.unhandledRejectionId_ = 0; + } + } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { + for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) { + p.hadUnhandledRejection_ = false; + } } - var point = new ol.geom.Point(null); - point.setFlatCoordinates(this.layout, this.flatCoordinates.slice( - index * this.stride, (index + 1) * this.stride)); - return point; }; /** - * @return {Array.<ol.geom.Point>} Points. - * @api stable + * Marks this rejected Promise as unhandled. If no {@code onRejected} callback + * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY} + * expires, the reason will be passed to the unhandled rejection handler. The + * handler typically rethrows the rejection reason so that it becomes visible in + * the developer console. + * + * @param {!goog.Promise} promise The rejected Promise. + * @param {*} reason The Promise rejection reason. + * @private */ -ol.geom.MultiPoint.prototype.getPoints = function() { - var flatCoordinates = this.flatCoordinates; - var layout = this.layout; - var stride = this.stride; - /** @type {Array.<ol.geom.Point>} */ - var points = []; - var i, ii; - for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { - var point = new ol.geom.Point(null); - point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride)); - points.push(point); +goog.Promise.addUnhandledRejection_ = function(promise, reason) { + if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { + promise.unhandledRejectionId_ = goog.global.setTimeout(function() { + promise.appendLongStack_(reason); + goog.Promise.handleRejection_.call(null, reason); + }, goog.Promise.UNHANDLED_REJECTION_DELAY); + + } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { + promise.hadUnhandledRejection_ = true; + goog.async.run(function() { + if (promise.hadUnhandledRejection_) { + promise.appendLongStack_(reason); + goog.Promise.handleRejection_.call(null, reason); + } + }); } - return points; }; /** - * @inheritDoc - * @api stable + * A method that is invoked with the rejection reasons for Promises that are + * rejected but have no {@code onRejected} callbacks registered yet. + * @type {function(*)} + * @private */ -ol.geom.MultiPoint.prototype.getType = function() { - return ol.geom.GeometryType.MULTI_POINT; -}; +goog.Promise.handleRejection_ = goog.async.throwException; /** - * @inheritDoc - * @api + * Sets a handler that will be called with reasons from unhandled rejected + * Promises. If the rejected Promise (or one of its descendants) has an + * {@code onRejected} callback registered, the rejection will be considered + * handled, and the rejection handler will not be called. + * + * By default, unhandled rejections are rethrown so that the error may be + * captured by the developer console or a {@code window.onerror} handler. + * + * @param {function(*)} handler A function that will be called with reasons from + * rejected Promises. Defaults to {@code goog.async.throwException}. */ -ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) { - var flatCoordinates = this.flatCoordinates; - var stride = this.stride; - var i, ii, x, y; - for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { - x = flatCoordinates[i]; - y = flatCoordinates[i + 1]; - if (ol.extent.containsXY(extent, x, y)) { - return true; - } - } - return false; +goog.Promise.setUnhandledRejectionHandler = function(handler) { + goog.Promise.handleRejection_ = handler; }; + /** - * @param {Array.<ol.Coordinate>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * Error used as a rejection reason for canceled Promises. + * + * @param {string=} opt_message + * @constructor + * @extends {goog.debug.Error} + * @final */ -ol.geom.MultiPoint.prototype.setCoordinates = - function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null); - } else { - this.setLayout(opt_layout, coordinates, 1); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; - } - this.flatCoordinates.length = ol.geom.flat.deflate.coordinates( - this.flatCoordinates, 0, coordinates, this.stride); - this.changed(); - } +goog.Promise.CancellationError = function(opt_message) { + goog.Promise.CancellationError.base(this, 'constructor', opt_message); }; +goog.inherits(goog.Promise.CancellationError, goog.debug.Error); + + +/** @override */ +goog.Promise.CancellationError.prototype.name = 'cancel'; + /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. + * Internal implementation of the resolver interface. + * + * @param {!goog.Promise<TYPE>} promise + * @param {function((TYPE|goog.Promise<TYPE>|Thenable)=)} resolve + * @param {function(*): void} reject + * @implements {goog.promise.Resolver<TYPE>} + * @final @struct + * @constructor + * @private + * @template TYPE */ -ol.geom.MultiPoint.prototype.setFlatCoordinates = - function(layout, flatCoordinates) { - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.changed(); -}; +goog.Promise.Resolver_ = function(promise, resolve, reject) { + /** @const */ + this.promise = promise; -goog.provide('ol.geom.flat.center'); + /** @const */ + this.resolve = resolve; -goog.require('ol.extent'); + /** @const */ + this.reject = reject; +}; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {Array.<Array.<number>>} endss Endss. - * @param {number} stride Stride. - * @return {Array.<number>} Flat centers. + * @fileoverview A timer class to which other classes and objects can + * listen on. This is only an abstraction above setInterval. + * + * @see ../demos/timers.html */ -ol.geom.flat.center.linearRingss = - function(flatCoordinates, offset, endss, stride) { - var flatCenters = []; - var i, ii; - var extent = ol.extent.createEmpty(); - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - extent = ol.extent.createOrUpdateFromFlatCoordinates( - flatCoordinates, offset, ends[0], stride); - flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2); - offset = ends[ends.length - 1]; - } - return flatCenters; -}; -goog.provide('ol.geom.MultiPolygon'); +goog.provide('goog.Timer'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.Polygon'); -goog.require('ol.geom.SimpleGeometry'); -goog.require('ol.geom.flat.area'); -goog.require('ol.geom.flat.center'); -goog.require('ol.geom.flat.closest'); -goog.require('ol.geom.flat.contains'); -goog.require('ol.geom.flat.deflate'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.geom.flat.interiorpoint'); -goog.require('ol.geom.flat.intersectsextent'); -goog.require('ol.geom.flat.orient'); -goog.require('ol.geom.flat.simplify'); +goog.require('goog.Promise'); +goog.require('goog.events.EventTarget'); /** - * @classdesc - * Multi-polygon geometry. + * Class for handling timing events. * + * @param {number=} opt_interval Number of ms between ticks (Default: 1ms). + * @param {Object=} opt_timerObject An object that has setTimeout, setInterval, + * clearTimeout and clearInterval (eg Window). * @constructor - * @extends {ol.geom.SimpleGeometry} - * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @extends {goog.events.EventTarget} */ -ol.geom.MultiPolygon = function(coordinates, opt_layout) { - - goog.base(this); - - /** - * @type {Array.<Array.<number>>} - * @private - */ - this.endss_ = []; +goog.Timer = function(opt_interval, opt_timerObject) { + goog.events.EventTarget.call(this); /** - * @private + * Number of ms between ticks * @type {number} - */ - this.flatInteriorPointsRevision_ = -1; - - /** * @private - * @type {Array.<number>} */ - this.flatInteriorPoints_ = null; + this.interval_ = opt_interval || 1; /** + * An object that implements setTimeout, setInterval, clearTimeout and + * clearInterval. We default to the window object. Changing this on + * goog.Timer.prototype changes the object for all timer instances which can + * be useful if your environment has some other implementation of timers than + * the window object. + * @type {Object} * @private - * @type {number} */ - this.maxDelta_ = -1; + this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject; /** + * Cached tick_ bound to the object for later use in the timer. + * @type {Function} * @private - * @type {number} */ - this.maxDeltaRevision_ = -1; + this.boundTick_ = goog.bind(this.tick_, this); /** - * @private + * Firefox browser often fires the timer event sooner + * (sometimes MUCH sooner) than the requested timeout. So we + * compare the time to when the event was last fired, and + * reschedule if appropriate. See also goog.Timer.intervalScale * @type {number} - */ - this.orientedRevision_ = -1; - - /** * @private - * @type {Array.<number>} */ - this.orientedFlatCoordinates_ = null; + this.last_ = goog.now(); +}; +goog.inherits(goog.Timer, goog.events.EventTarget); - this.setCoordinates(coordinates, - /** @type {ol.geom.GeometryLayout|undefined} */ (opt_layout)); -}; -goog.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry); +/** + * Maximum timeout value. + * + * Timeout values too big to fit into a signed 32-bit integer may cause + * overflow in FF, Safari, and Chrome, resulting in the timeout being + * scheduled immediately. It makes more sense simply not to schedule these + * timeouts, since 24.8 days is beyond a reasonable expectation for the + * browser to stay open. + * + * @type {number} + * @private + */ +goog.Timer.MAX_TIMEOUT_ = 2147483647; /** - * @param {ol.geom.Polygon} polygon Polygon. - * @api stable + * A timer ID that cannot be returned by any known implmentation of + * Window.setTimeout. Passing this value to window.clearTimeout should + * therefore be a no-op. + * + * @const {number} + * @private */ -ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) { - goog.asserts.assert(polygon.getLayout() == this.layout); - /** @type {Array.<number>} */ - var ends; - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = polygon.getFlatCoordinates().slice(); - ends = polygon.getEnds().slice(); - this.endss_.push(); - } else { - var offset = this.flatCoordinates.length; - goog.array.extend(this.flatCoordinates, polygon.getFlatCoordinates()); - ends = polygon.getEnds().slice(); - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - ends[i] += offset; - } - } - this.endss_.push(ends); - this.changed(); -}; +goog.Timer.INVALID_TIMEOUT_ID_ = -1; /** - * Make a complete copy of the geometry. - * @return {!ol.geom.MultiPolygon} Clone. - * @api stable + * Whether this timer is enabled + * @type {boolean} */ -ol.geom.MultiPolygon.prototype.clone = function() { - var multiPolygon = new ol.geom.MultiPolygon(null); - multiPolygon.setFlatCoordinates( - this.layout, this.flatCoordinates.slice(), this.endss_.slice()); - return multiPolygon; -}; +goog.Timer.prototype.enabled = false; /** - * @inheritDoc + * An object that implements setTimout, setInterval, clearTimeout and + * clearInterval. We default to the global object. Changing + * goog.Timer.defaultTimerObject changes the object for all timer instances + * which can be useful if your environment has some other implementation of + * timers you'd like to use. + * @type {Object} */ -ol.geom.MultiPolygon.prototype.closestPointXY = - function(x, y, closestPoint, minSquaredDistance) { - if (minSquaredDistance < - ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) { - return minSquaredDistance; - } - if (this.maxDeltaRevision_ != this.getRevision()) { - this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta( - this.flatCoordinates, 0, this.endss_, this.stride, 0)); - this.maxDeltaRevision_ = this.getRevision(); - } - return ol.geom.flat.closest.getssClosestPoint( - this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, - this.maxDelta_, true, x, y, closestPoint, minSquaredDistance); -}; +goog.Timer.defaultTimerObject = goog.global; /** - * @inheritDoc + * A variable that controls the timer error correction. If the + * timer is called before the requested interval times + * intervalScale, which often happens on mozilla, the timer is + * rescheduled. See also this.last_ + * @type {number} */ -ol.geom.MultiPolygon.prototype.containsXY = function(x, y) { - return ol.geom.flat.contains.linearRingssContainsXY( - this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y); -}; +goog.Timer.intervalScale = 0.8; /** - * @return {number} Area (on projected plane). - * @api stable + * Variable for storing the result of setInterval + * @type {?number} + * @private */ -ol.geom.MultiPolygon.prototype.getArea = function() { - return ol.geom.flat.area.linearRingss( - this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride); -}; +goog.Timer.prototype.timer_ = null; /** - * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates. - * @api stable + * Gets the interval of the timer. + * @return {number} interval Number of ms between ticks. */ -ol.geom.MultiPolygon.prototype.getCoordinates = function() { - return ol.geom.flat.inflate.coordinatesss( - this.flatCoordinates, 0, this.endss_, this.stride); +goog.Timer.prototype.getInterval = function() { + return this.interval_; }; /** - * @return {Array.<Array.<number>>} Endss. + * Sets the interval of the timer. + * @param {number} interval Number of ms between ticks. */ -ol.geom.MultiPolygon.prototype.getEndss = function() { - return this.endss_; +goog.Timer.prototype.setInterval = function(interval) { + this.interval_ = interval; + if (this.timer_ && this.enabled) { + // Stop and then start the timer to reset the interval. + this.stop(); + this.start(); + } else if (this.timer_) { + this.stop(); + } }; /** - * @return {Array.<number>} Flat interior points. + * Callback for the setTimeout used by the timer + * @private */ -ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() { - if (this.flatInteriorPointsRevision_ != this.getRevision()) { - var flatCenters = ol.geom.flat.center.linearRingss( - this.flatCoordinates, 0, this.endss_, this.stride); - this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss( - this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, - flatCenters); - this.flatInteriorPointsRevision_ = this.getRevision(); +goog.Timer.prototype.tick_ = function() { + if (this.enabled) { + var elapsed = goog.now() - this.last_; + if (elapsed > 0 && + elapsed < this.interval_ * goog.Timer.intervalScale) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, + this.interval_ - elapsed); + return; + } + + // Prevents setInterval from registering a duplicate timeout when called + // in the timer event handler. + if (this.timer_) { + this.timerObject_.clearTimeout(this.timer_); + this.timer_ = null; + } + + this.dispatchTick(); + // The timer could be stopped in the timer event handler. + if (this.enabled) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, + this.interval_); + this.last_ = goog.now(); + } } - return this.flatInteriorPoints_; }; /** - * @return {ol.geom.MultiPoint} Interior points. - * @api stable + * Dispatches the TICK event. This is its own method so subclasses can override. */ -ol.geom.MultiPolygon.prototype.getInteriorPoints = function() { - var interiorPoints = new ol.geom.MultiPoint(null); - interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY, - this.getFlatInteriorPoints().slice()); - return interiorPoints; +goog.Timer.prototype.dispatchTick = function() { + this.dispatchEvent(goog.Timer.TICK); }; /** - * @return {Array.<number>} Oriented flat coordinates. + * Starts the timer. */ -ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() { - if (this.orientedRevision_ != this.getRevision()) { - var flatCoordinates = this.flatCoordinates; - if (ol.geom.flat.linearRingssAreOriented( - flatCoordinates, 0, this.endss_, this.stride)) { - this.orientedFlatCoordinates_ = flatCoordinates; - } else { - this.orientedFlatCoordinates_ = flatCoordinates.slice(); - this.orientedFlatCoordinates_.length = - ol.geom.flat.orient.orientLinearRingss( - this.orientedFlatCoordinates_, 0, this.endss_, this.stride); - } - this.orientedRevision_ = this.getRevision(); +goog.Timer.prototype.start = function() { + this.enabled = true; + + // If there is no interval already registered, start it now + if (!this.timer_) { + // IMPORTANT! + // window.setInterval in FireFox has a bug - it fires based on + // absolute time, rather than on relative time. What this means + // is that if a computer is sleeping/hibernating for 24 hours + // and the timer interval was configured to fire every 1000ms, + // then after the PC wakes up the timer will fire, in rapid + // succession, 3600*24 times. + // This bug is described here and is already fixed, but it will + // take time to propagate, so for now I am switching this over + // to setTimeout logic. + // https://bugzilla.mozilla.org/show_bug.cgi?id=376643 + // + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, + this.interval_); + this.last_ = goog.now(); } - return this.orientedFlatCoordinates_; }; /** - * @inheritDoc + * Stops the timer. */ -ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = - function(squaredTolerance) { - var simplifiedFlatCoordinates = []; - var simplifiedEndss = []; - simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess( - this.flatCoordinates, 0, this.endss_, this.stride, - Math.sqrt(squaredTolerance), - simplifiedFlatCoordinates, 0, simplifiedEndss); - var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null); - simplifiedMultiPolygon.setFlatCoordinates( - ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss); - return simplifiedMultiPolygon; +goog.Timer.prototype.stop = function() { + this.enabled = false; + if (this.timer_) { + this.timerObject_.clearTimeout(this.timer_); + this.timer_ = null; + } +}; + + +/** @override */ +goog.Timer.prototype.disposeInternal = function() { + goog.Timer.superClass_.disposeInternal.call(this); + this.stop(); + delete this.timerObject_; }; /** - * @param {number} index Index. - * @return {ol.geom.Polygon} Polygon. - * @api stable + * Constant for the timer's event type + * @type {string} */ -ol.geom.MultiPolygon.prototype.getPolygon = function(index) { - goog.asserts.assert(0 <= index && index < this.endss_.length); - if (index < 0 || this.endss_.length <= index) { - return null; - } - var offset; - if (index === 0) { - offset = 0; - } else { - var prevEnds = this.endss_[index - 1]; - offset = prevEnds[prevEnds.length - 1]; - } - var ends = this.endss_[index].slice(); - var end = ends[ends.length - 1]; - if (offset !== 0) { - var i, ii; - for (i = 0, ii = ends.length; i < ii; ++i) { - ends[i] -= offset; - } - } - var polygon = new ol.geom.Polygon(null); - polygon.setFlatCoordinates( - this.layout, this.flatCoordinates.slice(offset, end), ends); - return polygon; -}; +goog.Timer.TICK = 'tick'; /** - * @return {Array.<ol.geom.Polygon>} Polygons. - * @api stable + * Calls the given function once, after the optional pause. + * + * The function is always called asynchronously, even if the delay is 0. This + * is a common trick to schedule a function to run after a batch of browser + * event processing. + * + * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function + * or object that has a handleEvent method. + * @param {number=} opt_delay Milliseconds to wait; default is 0. + * @param {SCOPE=} opt_handler Object in whose scope to call the listener. + * @return {number} A handle to the timer ID. + * @template SCOPE */ -ol.geom.MultiPolygon.prototype.getPolygons = function() { - var layout = this.layout; - var flatCoordinates = this.flatCoordinates; - var endss = this.endss_; - var polygons = []; - var offset = 0; - var i, ii, j, jj; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i].slice(); - var end = ends[ends.length - 1]; - if (offset !== 0) { - for (j = 0, jj = ends.length; j < jj; ++j) { - ends[j] -= offset; - } - } - var polygon = new ol.geom.Polygon(null); - polygon.setFlatCoordinates( - layout, flatCoordinates.slice(offset, end), ends); - polygons.push(polygon); - offset = end; +goog.Timer.callOnce = function(listener, opt_delay, opt_handler) { + if (goog.isFunction(listener)) { + if (opt_handler) { + listener = goog.bind(listener, opt_handler); + } + } else if (listener && typeof listener.handleEvent == 'function') { + // using typeof to prevent strict js warning + listener = goog.bind(listener.handleEvent, listener); + } else { + throw Error('Invalid listener argument'); + } + + if (opt_delay > goog.Timer.MAX_TIMEOUT_) { + // Timeouts greater than MAX_INT return immediately due to integer + // overflow in many browsers. Since MAX_INT is 24.8 days, just don't + // schedule anything at all. + return goog.Timer.INVALID_TIMEOUT_ID_; + } else { + return goog.Timer.defaultTimerObject.setTimeout( + listener, opt_delay || 0); } - return polygons; }; /** - * @inheritDoc - * @api stable + * Clears a timeout initiated by callOnce + * @param {?number} timerId a timer ID. */ -ol.geom.MultiPolygon.prototype.getType = function() { - return ol.geom.GeometryType.MULTI_POLYGON; +goog.Timer.clear = function(timerId) { + goog.Timer.defaultTimerObject.clearTimeout(timerId); }; /** - * @inheritDoc - * @api + * @param {number} delay Milliseconds to wait. + * @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value + * with which the promise will be resolved. + * @return {!goog.Promise<RESULT>} A promise that will be resolved after + * the specified delay, unless it is canceled first. + * @template RESULT */ -ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) { - return ol.geom.flat.intersectsextent.linearRingss( - this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent); +goog.Timer.promise = function(delay, opt_result) { + var timerKey = null; + return new goog.Promise(function(resolve, reject) { + timerKey = goog.Timer.callOnce(function() { + resolve(opt_result); + }, delay); + if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) { + reject(new Error('Failed to schedule timer.')); + } + }).thenCatch(function(error) { + // Clear the timer. The most likely reason is "cancel" signal. + goog.Timer.clear(timerKey); + throw error; + }); }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates. - * @param {ol.geom.GeometryLayout=} opt_layout Layout. - * @api stable + * @fileoverview JSON utility functions. + * @author arv@google.com (Erik Arvidsson) */ -ol.geom.MultiPolygon.prototype.setCoordinates = - function(coordinates, opt_layout) { - if (goog.isNull(coordinates)) { - this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_); - } else { - this.setLayout(opt_layout, coordinates, 3); - if (goog.isNull(this.flatCoordinates)) { - this.flatCoordinates = []; - } - var endss = ol.geom.flat.deflate.coordinatesss( - this.flatCoordinates, 0, coordinates, this.stride, this.endss_); - if (endss.length === 0) { - this.flatCoordinates.length = 0; - } else { - var lastEnds = endss[endss.length - 1]; - this.flatCoordinates.length = lastEnds.length === 0 ? - 0 : lastEnds[lastEnds.length - 1]; - } - this.changed(); - } -}; + + +goog.provide('goog.json'); +goog.provide('goog.json.Replacer'); +goog.provide('goog.json.Reviver'); +goog.provide('goog.json.Serializer'); /** - * @param {ol.geom.GeometryLayout} layout Layout. - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {Array.<Array.<number>>} endss Endss. + * @define {boolean} If true, use the native JSON parsing API. + * NOTE(ruilopes): EXPERIMENTAL, handle with care. Setting this to true might + * break your code. The default {@code goog.json.parse} implementation is able + * to handle invalid JSON, such as JSPB. */ -ol.geom.MultiPolygon.prototype.setFlatCoordinates = - function(layout, flatCoordinates, endss) { - goog.asserts.assert(!goog.isNull(endss)); - if (goog.isNull(flatCoordinates) || flatCoordinates.length === 0) { - goog.asserts.assert(endss.length === 0); - } else { - goog.asserts.assert(endss.length > 0); - var ends = endss[endss.length - 1]; - goog.asserts.assert(flatCoordinates.length == ends[ends.length - 1]); - } - this.setFlatCoordinatesInternal(layout, flatCoordinates); - this.endss_ = endss; - this.changed(); -}; +goog.define('goog.json.USE_NATIVE_JSON', false); /** - * @param {Array.<ol.geom.Polygon>} polygons Polygons. + * Tests if a string is an invalid JSON string. This only ensures that we are + * not using any invalid characters + * @param {string} s The string to test. + * @return {boolean} True if the input is a valid JSON string. */ -ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) { - var layout = ol.geom.GeometryLayout.XY; - var flatCoordinates = []; - var endss = []; - var i, ii, ends; - for (i = 0, ii = polygons.length; i < ii; ++i) { - var polygon = polygons[i]; - if (i === 0) { - layout = polygon.getLayout(); - } else { - // FIXME better handle the case of non-matching layouts - goog.asserts.assert(polygon.getLayout() == layout); - } - var offset = flatCoordinates.length; - ends = polygon.getEnds(); - var j, jj; - for (j = 0, jj = ends.length; j < jj; ++j) { - ends[j] += offset; - } - goog.array.extend(flatCoordinates, polygon.getFlatCoordinates()); - endss.push(ends); +goog.json.isValid = function(s) { + // All empty whitespace is not valid. + if (/^\s*$/.test(s)) { + return false; } - this.setFlatCoordinates(layout, flatCoordinates, endss); + + // This is taken from http://www.json.org/json2.js which is released to the + // public domain. + // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator + // inside strings. We also treat \u2028 and \u2029 as whitespace which they + // are in the RFC but IE and Safari does not match \s to these so we need to + // include them in the reg exps in all places where whitespace is allowed. + // We allowed \x7f inside strings because some tools don't escape it, + // e.g. http://www.json.org/java/org/json/JSONObject.java + + // Parsing happens in three stages. In the first stage, we run the text + // against regular expressions that look for non-JSON patterns. We are + // especially concerned with '()' and 'new' because they can cause invocation, + // and '=' because it can cause mutation. But just to be safe, we want to + // reject all unexpected forms. + + // We split the first stage into 4 regexp operations in order to work around + // crippling inefficiencies in IE's and Safari's regexp engines. First we + // replace all backslash pairs with '@' (a non-JSON character). Second, we + // replace all simple value tokens with ']' characters. Third, we delete all + // open brackets that follow a colon or comma or that begin the text. Finally, + // we look to see that the remaining characters are only whitespace or ']' or + // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + // Don't make these static since they have the global flag. + var backslashesRe = /\\["\\\/bfnrtu]/g; + var simpleValuesRe = + /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; + var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g; + var remainderRe = /^[\],:{}\s\u2028\u2029]*$/; + + return remainderRe.test(s.replace(backslashesRe, '@'). + replace(simpleValuesRe, ']'). + replace(openBracketsRe, '')); }; -goog.provide('ol.renderer.vector'); -goog.require('goog.asserts'); -goog.require('ol.geom.Circle'); -goog.require('ol.geom.GeometryCollection'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.render.IReplayGroup'); -goog.require('ol.style.ImageState'); -goog.require('ol.style.Style'); +/** + * Parses a JSON string and returns the result. This throws an exception if + * the string is an invalid JSON string. + * + * Note that this is very slow on large strings. If you trust the source of + * the string then you should use unsafeParse instead. + * + * @param {*} s The JSON string to parse. + * @throws Error if s is invalid JSON. + * @return {Object} The object generated from the JSON string, or null. + */ +goog.json.parse = goog.json.USE_NATIVE_JSON ? + /** @type {function(*):Object} */ (goog.global['JSON']['parse']) : + function(s) { + var o = String(s); + if (goog.json.isValid(o)) { + /** @preserveTry */ + try { + return /** @type {Object} */ (eval('(' + o + ')')); + } catch (ex) { + } + } + throw Error('Invalid JSON string: ' + o); + }; /** - * @param {ol.Feature} feature1 Feature 1. - * @param {ol.Feature} feature2 Feature 2. - * @return {number} Order. + * Parses a JSON string and returns the result. This uses eval so it is open + * to security issues and it should only be used if you trust the source. + * + * @param {string} s The JSON string to parse. + * @return {Object} The object generated from the JSON string. */ -ol.renderer.vector.defaultOrder = function(feature1, feature2) { - return goog.getUid(feature1) - goog.getUid(feature2); -}; +goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ? + /** @type {function(string):Object} */ (goog.global['JSON']['parse']) : + function(s) { + return /** @type {Object} */ (eval('(' + s + ')')); + }; /** - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @return {number} Squared pixel tolerance. + * JSON replacer, as defined in Section 15.12.3 of the ES5 spec. + * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3 + * + * TODO(nicksantos): Array should also be a valid replacer. + * + * @typedef {function(this:Object, string, *): *} */ -ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) { - var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); - return tolerance * tolerance; -}; +goog.json.Replacer; /** - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @return {number} Pixel tolerance. + * JSON reviver, as defined in Section 15.12.2 of the ES5 spec. + * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3 + * + * @typedef {function(this:Object, string, *): *} */ -ol.renderer.vector.getTolerance = function(resolution, pixelRatio) { - return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio; -}; +goog.json.Reviver; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. - * @private + * Serializes an object or a value to a JSON string. + * + * @param {*} object The object to serialize. + * @param {?goog.json.Replacer=} opt_replacer A replacer function + * called for each (key, value) pair that determines how the value + * should be serialized. By defult, this just returns the value + * and allows default serialization to kick in. + * @throws Error if there are loops in the object graph. + * @return {string} A JSON string representation of the input. */ -ol.renderer.vector.renderCircleGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.Circle); - var fillStyle = style.getFill(); - var strokeStyle = style.getStroke(); - if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) { - var polygonReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.POLYGON); - polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); - polygonReplay.drawCircleGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature); - } -}; +goog.json.serialize = goog.json.USE_NATIVE_JSON ? + /** @type {function(*, ?goog.json.Replacer=):string} */ + (goog.global['JSON']['stringify']) : + function(object, opt_replacer) { + // NOTE(nicksantos): Currently, we never use JSON.stringify. + // + // The last time I evaluated this, JSON.stringify had subtle bugs and + // behavior differences on all browsers, and the performance win was not + // large enough to justify all the issues. This may change in the future + // as browser implementations get better. + // + // assertSerialize in json_test contains if branches for the cases + // that fail. + return new goog.json.Serializer(opt_replacer).serialize(object); + }; + /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.Feature} feature Feature. - * @param {ol.style.Style} style Style. - * @param {number} squaredTolerance Squared tolerance. - * @param {function(this: T, goog.events.Event)} listener Listener function. - * @param {T} thisArg Value to use as `this` when executing `listener`. - * @return {boolean} `true` if style is loading. - * @template T + * Class that is used to serialize JSON objects to a string. + * @param {?goog.json.Replacer=} opt_replacer Replacer. + * @constructor */ -ol.renderer.vector.renderFeature = function( - replayGroup, feature, style, squaredTolerance, listener, thisArg) { - var loading = false; - var imageStyle, imageState; - imageStyle = style.getImage(); - if (goog.isNull(imageStyle)) { - ol.renderer.vector.renderFeature_( - replayGroup, feature, style, squaredTolerance); - } else { - imageState = imageStyle.getImageState(); - if (imageState == ol.style.ImageState.LOADED || - imageState == ol.style.ImageState.ERROR) { - imageStyle.unlistenImageChange(listener, thisArg); - if (imageState == ol.style.ImageState.LOADED) { - ol.renderer.vector.renderFeature_( - replayGroup, feature, style, squaredTolerance); - } - } else { - if (imageState == ol.style.ImageState.IDLE) { - imageStyle.load(); - } - imageState = imageStyle.getImageState(); - goog.asserts.assert(imageState == ol.style.ImageState.LOADING); - imageStyle.listenImageChange(listener, thisArg); - loading = true; - } - } - return loading; +goog.json.Serializer = function(opt_replacer) { + /** + * @type {goog.json.Replacer|null|undefined} + * @private + */ + this.replacer_ = opt_replacer; }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.Feature} feature Feature. - * @param {ol.style.Style} style Style. - * @param {number} squaredTolerance Squared tolerance. - * @private + * Serializes an object or a value to a JSON string. + * + * @param {*} object The object to serialize. + * @throws Error if there are loops in the object graph. + * @return {string} A JSON string representation of the input. */ -ol.renderer.vector.renderFeature_ = function( - replayGroup, feature, style, squaredTolerance) { - var geometry = style.getGeometryFunction()(feature); - if (!goog.isDefAndNotNull(geometry)) { - return; - } - var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); - var geometryRenderer = - ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()]; - goog.asserts.assert(goog.isDef(geometryRenderer)); - geometryRenderer(replayGroup, simplifiedGeometry, style, feature); +goog.json.Serializer.prototype.serialize = function(object) { + var sb = []; + this.serializeInternal(object, sb); + return sb.join(''); }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. - * @private + * Serializes a generic value to a JSON string + * @protected + * @param {*} object The object to serialize. + * @param {Array<string>} sb Array used as a string builder. + * @throws Error if there are loops in the object graph. */ -ol.renderer.vector.renderGeometryCollectionGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection); - var geometries = geometry.getGeometriesArray(); - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - var geometryRenderer = - ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()]; - goog.asserts.assert(goog.isDef(geometryRenderer)); - geometryRenderer(replayGroup, geometries[i], style, feature); +goog.json.Serializer.prototype.serializeInternal = function(object, sb) { + switch (typeof object) { + case 'string': + this.serializeString_(/** @type {string} */ (object), sb); + break; + case 'number': + this.serializeNumber_(/** @type {number} */ (object), sb); + break; + case 'boolean': + sb.push(object); + break; + case 'undefined': + sb.push('null'); + break; + case 'object': + if (object == null) { + sb.push('null'); + break; + } + if (goog.isArray(object)) { + this.serializeArray(/** @type {!Array<?>} */ (object), sb); + break; + } + // should we allow new String, new Number and new Boolean to be treated + // as string, number and boolean? Most implementations do not and the + // need is not very big + this.serializeObject_(/** @type {Object} */ (object), sb); + break; + case 'function': + // Skip functions. + // TODO(user) Should we return something here? + break; + default: + throw Error('Unknown type: ' + typeof object); } }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * Character mappings used internally for goog.string.quote * @private + * @type {!Object} */ -ol.renderer.vector.renderLineStringGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - var strokeStyle = style.getStroke(); - if (!goog.isNull(strokeStyle)) { - var lineStringReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.LINE_STRING); - lineStringReplay.setFillStrokeStyle(null, strokeStyle); - lineStringReplay.drawLineStringGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature); - } +goog.json.Serializer.charToJsonCharCache_ = { + '\"': '\\"', + '\\': '\\\\', + '/': '\\/', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', + + '\x0B': '\\u000b' // '\v' is not supported in JScript }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * Regular expression used to match characters that need to be replaced. + * The S60 browser has a bug where unicode characters are not matched by + * regular expressions. The condition below detects such behaviour and + * adjusts the regular expression accordingly. * @private + * @type {!RegExp} */ -ol.renderer.vector.renderMultiLineStringGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString); - var strokeStyle = style.getStroke(); - if (!goog.isNull(strokeStyle)) { - var lineStringReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.LINE_STRING); - lineStringReplay.setFillStrokeStyle(null, strokeStyle); - lineStringReplay.drawMultiLineStringGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - var flatMidpointCoordinates = geometry.getFlatMidpoints(); - textReplay.drawText(flatMidpointCoordinates, 0, - flatMidpointCoordinates.length, 2, geometry, feature); - } -}; +goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ? + /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * Serializes a string to a JSON string * @private + * @param {string} s The string to serialize. + * @param {Array<string>} sb Array used as a string builder. */ -ol.renderer.vector.renderMultiPolygonGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon); - var fillStyle = style.getFill(); - var strokeStyle = style.getStroke(); - if (!goog.isNull(strokeStyle) || !goog.isNull(fillStyle)) { - var polygonReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.POLYGON); - polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); - polygonReplay.drawMultiPolygonGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints(); - textReplay.drawText(flatInteriorPointCoordinates, 0, - flatInteriorPointCoordinates.length, 2, geometry, feature); - } +goog.json.Serializer.prototype.serializeString_ = function(s, sb) { + // The official JSON implementation does not work with international + // characters. + sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) { + // caching the result improves performance by a factor 2-3 + if (c in goog.json.Serializer.charToJsonCharCache_) { + return goog.json.Serializer.charToJsonCharCache_[c]; + } + + var cc = c.charCodeAt(0); + var rv = '\\u'; + if (cc < 16) { + rv += '000'; + } else if (cc < 256) { + rv += '00'; + } else if (cc < 4096) { // \u1000 + rv += '0'; + } + return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16); + }), '"'); }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * Serializes a number to a JSON string * @private + * @param {number} n The number to serialize. + * @param {Array<string>} sb Array used as a string builder. */ -ol.renderer.vector.renderPointGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - var imageStyle = style.getImage(); - if (!goog.isNull(imageStyle)) { - var imageReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.IMAGE); - imageReplay.setImageStyle(imageStyle); - imageReplay.drawPointGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - textReplay.drawText(geometry.getCoordinates(), 0, 2, 2, geometry, feature); - } +goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) { + sb.push(isFinite(n) && !isNaN(n) ? n : 'null'); }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. - * @private + * Serializes an array to a JSON string + * @param {Array<string>} arr The array to serialize. + * @param {Array<string>} sb Array used as a string builder. + * @protected */ -ol.renderer.vector.renderMultiPointGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint); - var imageStyle = style.getImage(); - if (!goog.isNull(imageStyle)) { - var imageReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.IMAGE); - imageReplay.setImageStyle(imageStyle); - imageReplay.drawMultiPointGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - var flatCoordinates = geometry.getFlatCoordinates(); - textReplay.drawText(flatCoordinates, 0, flatCoordinates.length, - geometry.getStride(), geometry, feature); +goog.json.Serializer.prototype.serializeArray = function(arr, sb) { + var l = arr.length; + sb.push('['); + var sep = ''; + for (var i = 0; i < l; i++) { + sb.push(sep); + + var value = arr[i]; + this.serializeInternal( + this.replacer_ ? this.replacer_.call(arr, String(i), value) : value, + sb); + + sep = ','; } + sb.push(']'); }; /** - * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * Serializes an object to a JSON string * @private + * @param {Object} obj The object to serialize. + * @param {Array<string>} sb Array used as a string builder. */ -ol.renderer.vector.renderPolygonGeometry_ = - function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - var fillStyle = style.getFill(); - var strokeStyle = style.getStroke(); - if (!goog.isNull(fillStyle) || !goog.isNull(strokeStyle)) { - var polygonReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.POLYGON); - polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle); - polygonReplay.drawPolygonGeometry(geometry, feature); - } - var textStyle = style.getText(); - if (!goog.isNull(textStyle)) { - var textReplay = replayGroup.getReplay( - style.getZIndex(), ol.render.ReplayType.TEXT); - textReplay.setTextStyle(textStyle); - textReplay.drawText( - geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature); +goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) { + sb.push('{'); + var sep = ''; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + var value = obj[key]; + // Skip functions. + // TODO(ptucker) Should we return something for function properties? + if (typeof value != 'function') { + sb.push(sep); + this.serializeString_(key, sb); + sb.push(':'); + + this.serializeInternal( + this.replacer_ ? this.replacer_.call(obj, key, value) : value, + sb); + + sep = ','; + } + } } + sb.push('}'); }; +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @const - * @private - * @type {Object.<ol.geom.GeometryType, - * function(ol.render.IReplayGroup, ol.geom.Geometry, - * ol.style.Style, Object)>} + * @fileoverview Error codes shared between goog.net.IframeIo and + * goog.net.XhrIo. */ -ol.renderer.vector.GEOMETRY_RENDERERS_ = { - 'Point': ol.renderer.vector.renderPointGeometry_, - 'LineString': ol.renderer.vector.renderLineStringGeometry_, - 'Polygon': ol.renderer.vector.renderPolygonGeometry_, - 'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_, - 'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_, - 'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_, - 'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_, - 'Circle': ol.renderer.vector.renderCircleGeometry_ -}; - -goog.provide('ol.renderer.canvas.VectorLayer'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('ol.ViewHint'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.EventType'); -goog.require('ol.render.canvas.ReplayGroup'); -goog.require('ol.renderer.canvas.Layer'); -goog.require('ol.renderer.vector'); +goog.provide('goog.net.ErrorCode'); /** - * @constructor - * @extends {ol.renderer.canvas.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Vector} vectorLayer Vector layer. + * Error codes + * @enum {number} */ -ol.renderer.canvas.VectorLayer = function(mapRenderer, vectorLayer) { +goog.net.ErrorCode = { - goog.base(this, mapRenderer, vectorLayer); + /** + * There is no error condition. + */ + NO_ERROR: 0, /** - * @private - * @type {boolean} + * The most common error from iframeio, unfortunately, is that the browser + * responded with an error page that is classed as a different domain. The + * situations, are when a browser error page is shown -- 404, access denied, + * DNS failure, connection reset etc.) + * */ - this.dirty_ = false; + ACCESS_DENIED: 1, /** - * @private - * @type {number} + * Currently the only case where file not found will be caused is when the + * code is running on the local file system and a non-IE browser makes a + * request to a file that doesn't exist. */ - this.renderedRevision_ = -1; + FILE_NOT_FOUND: 2, /** - * @private - * @type {number} + * If Firefox shows a browser error page, such as a connection reset by + * server or access denied, then it will fail silently without the error or + * load handlers firing. */ - this.renderedResolution_ = NaN; + FF_SILENT_ERROR: 3, /** - * @private - * @type {ol.Extent} + * Custom error provided by the client through the error check hook. */ - this.renderedExtent_ = ol.extent.createEmpty(); + CUSTOM_ERROR: 4, /** - * @private - * @type {function(ol.Feature, ol.Feature): number|null} + * Exception was thrown while processing the request. */ - this.renderedRenderOrder_ = null; + EXCEPTION: 5, /** - * @private - * @type {ol.render.canvas.ReplayGroup} + * The Http response returned a non-successful http status code. */ - this.replayGroup_ = null; + HTTP_ERROR: 6, /** - * @private - * @type {CanvasRenderingContext2D} + * The request was aborted. */ - this.context_ = ol.dom.createCanvasContext2D(); + ABORT: 7, + + /** + * The request timed out. + */ + TIMEOUT: 8, + /** + * The resource is not available offline. + */ + OFFLINE: 9 }; -goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer); /** - * @inheritDoc + * Returns a friendly error message for an error code. These messages are for + * debugging and are not localized. + * @param {goog.net.ErrorCode} errorCode An error code. + * @return {string} A message for debugging. */ -ol.renderer.canvas.VectorLayer.prototype.composeFrame = - function(frameState, layerState, context) { +goog.net.ErrorCode.getDebugMessage = function(errorCode) { + switch (errorCode) { + case goog.net.ErrorCode.NO_ERROR: + return 'No Error'; - var transform = this.getTransform(frameState); + case goog.net.ErrorCode.ACCESS_DENIED: + return 'Access denied to content document'; - this.dispatchPreComposeEvent(context, frameState, transform); + case goog.net.ErrorCode.FILE_NOT_FOUND: + return 'File not found'; - var replayGroup = this.replayGroup_; - if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { - var layer = this.getLayer(); - var replayContext; - if (layer.hasListener(ol.render.EventType.RENDER)) { - // resize and clear - this.context_.canvas.width = context.canvas.width; - this.context_.canvas.height = context.canvas.height; - replayContext = this.context_; - } else { - replayContext = context; - } - // for performance reasons, context.save / context.restore is not used - // to save and restore the transformation matrix and the opacity. - // see http://jsperf.com/context-save-restore-versus-variable - var alpha = replayContext.globalAlpha; - replayContext.globalAlpha = layerState.opacity; - replayGroup.replay( - replayContext, frameState.pixelRatio, transform, - frameState.viewState.rotation, frameState.skippedFeatureUids); + case goog.net.ErrorCode.FF_SILENT_ERROR: + return 'Firefox silently errored'; - if (replayContext != context) { - this.dispatchRenderEvent(replayContext, frameState, transform); - context.drawImage(replayContext.canvas, 0, 0); - } - replayContext.globalAlpha = alpha; - } + case goog.net.ErrorCode.CUSTOM_ERROR: + return 'Application custom error'; - this.dispatchPostComposeEvent(context, frameState, transform); + case goog.net.ErrorCode.EXCEPTION: + return 'An exception occurred'; -}; + case goog.net.ErrorCode.HTTP_ERROR: + return 'Http response at 400 or 500 level'; + case goog.net.ErrorCode.ABORT: + return 'Request was aborted'; -/** - * @inheritDoc - */ -ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg) { - if (goog.isNull(this.replayGroup_)) { - return undefined; - } else { - var resolution = frameState.viewState.resolution; - var rotation = frameState.viewState.rotation; - var layer = this.getLayer(); - /** @type {Object.<string, boolean>} */ - var features = {}; - return this.replayGroup_.forEachGeometryAtPixel(resolution, - rotation, coordinate, frameState.skippedFeatureUids, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - goog.asserts.assert(goog.isDef(feature)); - var key = goog.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback.call(thisArg, feature, layer); - } - }); - } -}; + case goog.net.ErrorCode.TIMEOUT: + return 'Request timed out'; + case goog.net.ErrorCode.OFFLINE: + return 'The resource is not available offline'; -/** - * Handle changes in image style state. - * @param {goog.events.Event} event Image style change event. - * @private - */ -ol.renderer.canvas.VectorLayer.prototype.handleImageChange_ = - function(event) { - this.renderIfReadyAndVisible(); + default: + return 'Unrecognized error code'; + } }; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @inheritDoc + * @fileoverview Constants for HTTP status codes. */ -ol.renderer.canvas.VectorLayer.prototype.prepareFrame = - function(frameState, layerState) { - - var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); - goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector); - var vectorSource = vectorLayer.getSource(); - - this.updateAttributions( - frameState.attributions, vectorSource.getAttributions()); - this.updateLogos(frameState, vectorSource); - - if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] || - frameState.viewHints[ol.ViewHint.INTERACTING])) { - return true; - } - var frameStateExtent = frameState.extent; - var viewState = frameState.viewState; - var projection = viewState.projection; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var vectorLayerRevision = vectorLayer.getRevision(); - var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); - var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); - - if (!goog.isDef(vectorLayerRenderOrder)) { - vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; - } +goog.provide('goog.net.HttpStatus'); - var extent = ol.extent.buffer(frameStateExtent, - vectorLayerRenderBuffer * resolution); - if (!this.dirty_ && - this.renderedResolution_ == resolution && - this.renderedRevision_ == vectorLayerRevision && - this.renderedRenderOrder_ == vectorLayerRenderOrder && - ol.extent.containsExtent(this.renderedExtent_, extent)) { - return true; - } +/** + * HTTP Status Codes defined in RFC 2616 and RFC 6585. + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * @see http://tools.ietf.org/html/rfc6585 + * @enum {number} + */ +goog.net.HttpStatus = { + // Informational 1xx + CONTINUE: 100, + SWITCHING_PROTOCOLS: 101, - // FIXME dispose of old replayGroup in post render - goog.dispose(this.replayGroup_); - this.replayGroup_ = null; + // Successful 2xx + OK: 200, + CREATED: 201, + ACCEPTED: 202, + NON_AUTHORITATIVE_INFORMATION: 203, + NO_CONTENT: 204, + RESET_CONTENT: 205, + PARTIAL_CONTENT: 206, - this.dirty_ = false; + // Redirection 3xx + MULTIPLE_CHOICES: 300, + MOVED_PERMANENTLY: 301, + FOUND: 302, + SEE_OTHER: 303, + NOT_MODIFIED: 304, + USE_PROXY: 305, + TEMPORARY_REDIRECT: 307, - var replayGroup = - new ol.render.canvas.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); - vectorSource.loadFeatures(extent, resolution, projection); - var renderFeature = - /** - * @param {ol.Feature} feature Feature. - * @this {ol.renderer.canvas.VectorLayer} - */ - function(feature) { - var styles; - if (goog.isDef(feature.getStyleFunction())) { - styles = feature.getStyleFunction().call(feature, resolution); - } else if (goog.isDef(vectorLayer.getStyleFunction())) { - styles = vectorLayer.getStyleFunction()(feature, resolution); - } - if (goog.isDefAndNotNull(styles)) { - var dirty = this.renderFeature( - feature, resolution, pixelRatio, styles, replayGroup); - this.dirty_ = this.dirty_ || dirty; - } - }; - if (!goog.isNull(vectorLayerRenderOrder)) { - /** @type {Array.<ol.Feature>} */ - var features = []; - vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - features.push(feature); - }, this); - goog.array.sort(features, vectorLayerRenderOrder); - goog.array.forEach(features, renderFeature, this); - } else { - vectorSource.forEachFeatureInExtentAtResolution( - extent, resolution, renderFeature, this); - } - replayGroup.finish(); + // Client Error 4xx + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + PAYMENT_REQUIRED: 402, + FORBIDDEN: 403, + NOT_FOUND: 404, + METHOD_NOT_ALLOWED: 405, + NOT_ACCEPTABLE: 406, + PROXY_AUTHENTICATION_REQUIRED: 407, + REQUEST_TIMEOUT: 408, + CONFLICT: 409, + GONE: 410, + LENGTH_REQUIRED: 411, + PRECONDITION_FAILED: 412, + REQUEST_ENTITY_TOO_LARGE: 413, + REQUEST_URI_TOO_LONG: 414, + UNSUPPORTED_MEDIA_TYPE: 415, + REQUEST_RANGE_NOT_SATISFIABLE: 416, + EXPECTATION_FAILED: 417, + PRECONDITION_REQUIRED: 428, + TOO_MANY_REQUESTS: 429, + REQUEST_HEADER_FIELDS_TOO_LARGE: 431, - this.renderedResolution_ = resolution; - this.renderedRevision_ = vectorLayerRevision; - this.renderedRenderOrder_ = vectorLayerRenderOrder; - this.renderedExtent_ = extent; - this.replayGroup_ = replayGroup; + // Server Error 5xx + INTERNAL_SERVER_ERROR: 500, + NOT_IMPLEMENTED: 501, + BAD_GATEWAY: 502, + SERVICE_UNAVAILABLE: 503, + GATEWAY_TIMEOUT: 504, + HTTP_VERSION_NOT_SUPPORTED: 505, + NETWORK_AUTHENTICATION_REQUIRED: 511, - return true; + /* + * IE returns this code for 204 due to its use of URLMon, which returns this + * code for 'Operation Aborted'. The status text is 'Unknown', the response + * headers are ''. Known to occur on IE 6 on XP through IE9 on Win7. + */ + QUIRK_IE_NO_CONTENT: 1223 }; /** - * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {Array.<ol.style.Style>} styles Array of styles - * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. - * @return {boolean} `true` if an image is loading. + * Returns whether the given status should be considered successful. + * + * Successful codes are OK (200), CREATED (201), ACCEPTED (202), + * NO CONTENT (204), PARTIAL CONTENT (206), NOT MODIFIED (304), + * and IE's no content code (1223). + * + * @param {number} status The status code to test. + * @return {boolean} Whether the status code should be considered successful. */ -ol.renderer.canvas.VectorLayer.prototype.renderFeature = - function(feature, resolution, pixelRatio, styles, replayGroup) { - if (!goog.isDefAndNotNull(styles)) { - return false; - } - var i, ii, loading = false; - for (i = 0, ii = styles.length; i < ii; ++i) { - loading = ol.renderer.vector.renderFeature( - replayGroup, feature, styles[i], - ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), - this.handleImageChange_, this) || loading; +goog.net.HttpStatus.isSuccess = function(status) { + switch (status) { + case goog.net.HttpStatus.OK: + case goog.net.HttpStatus.CREATED: + case goog.net.HttpStatus.ACCEPTED: + case goog.net.HttpStatus.NO_CONTENT: + case goog.net.HttpStatus.PARTIAL_CONTENT: + case goog.net.HttpStatus.NOT_MODIFIED: + case goog.net.HttpStatus.QUIRK_IE_NO_CONTENT: + return true; + + default: + return false; } - return loading; }; -// FIXME offset panning - -goog.provide('ol.renderer.canvas.Map'); +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.style'); -goog.require('goog.vec.Mat4'); -goog.require('ol'); -goog.require('ol.RendererType'); -goog.require('ol.css'); -goog.require('ol.dom'); -goog.require('ol.layer.Image'); -goog.require('ol.layer.Layer'); -goog.require('ol.layer.Tile'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.Event'); -goog.require('ol.render.EventType'); -goog.require('ol.render.canvas.Immediate'); -goog.require('ol.render.canvas.ReplayGroup'); -goog.require('ol.renderer.Map'); -goog.require('ol.renderer.canvas.ImageLayer'); -goog.require('ol.renderer.canvas.Layer'); -goog.require('ol.renderer.canvas.TileLayer'); -goog.require('ol.renderer.canvas.VectorLayer'); -goog.require('ol.renderer.vector'); -goog.require('ol.source.State'); -goog.require('ol.vec.Mat4'); +goog.provide('goog.net.XhrLike'); /** - * @constructor - * @extends {ol.renderer.Map} - * @param {Element} container Container. - * @param {ol.Map} map Map. + * Interface for the common parts of XMLHttpRequest. + * + * Mostly copied from externs/w3c_xml.js. + * + * @interface + * @see http://www.w3.org/TR/XMLHttpRequest/ */ -ol.renderer.canvas.Map = function(container, map) { - - goog.base(this, container, map); +goog.net.XhrLike = function() {}; - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = ol.dom.createCanvasContext2D(); - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = this.context_.canvas; +/** + * Typedef that refers to either native or custom-implemented XHR objects. + * @typedef {!goog.net.XhrLike|!XMLHttpRequest} + */ +goog.net.XhrLike.OrNative; - this.canvas_.style.width = '100%'; - this.canvas_.style.height = '100%'; - this.canvas_.className = ol.css.CLASS_UNSELECTABLE; - goog.dom.insertChildAt(container, this.canvas_, 0); - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; +/** + * @type {function()|null|undefined} + * @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange + */ +goog.net.XhrLike.prototype.onreadystatechange; - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.transform_ = goog.vec.Mat4.createNumber(); -}; -goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map); +/** + * @type {string} + * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute + */ +goog.net.XhrLike.prototype.responseText; /** - * @inheritDoc + * @type {Document} + * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute */ -ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) { - if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { - return new ol.renderer.canvas.ImageLayer(this, layer); - } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { - return new ol.renderer.canvas.TileLayer(this, layer); - } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { - return new ol.renderer.canvas.VectorLayer(this, layer); - } else { - goog.asserts.fail(); - return null; - } -}; +goog.net.XhrLike.prototype.responseXML; /** - * @param {ol.render.EventType} type Event type. - * @param {olx.FrameState} frameState Frame state. - * @private + * @type {number} + * @see http://www.w3.org/TR/XMLHttpRequest/#readystate */ -ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = - function(type, frameState) { - var map = this.getMap(); - var context = this.context_; - if (map.hasListener(type)) { - var extent = frameState.extent; - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var resolution = viewState.resolution; - var rotation = viewState.rotation; +goog.net.XhrLike.prototype.readyState; - ol.vec.Mat4.makeTransform2D(this.transform_, - this.canvas_.width / 2, this.canvas_.height / 2, - pixelRatio / resolution, -pixelRatio / resolution, - -rotation, - -viewState.center[0], -viewState.center[1]); - var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); - var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, extent, - resolution); +/** + * @type {number} + * @see http://www.w3.org/TR/XMLHttpRequest/#status + */ +goog.net.XhrLike.prototype.status; - var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, - extent, this.transform_, rotation); - var composeEvent = new ol.render.Event(type, map, vectorContext, - replayGroup, frameState, context, null); - map.dispatchEvent(composeEvent); - replayGroup.finish(); - if (!replayGroup.isEmpty()) { - replayGroup.replay(context, pixelRatio, this.transform_, - rotation, {}); - } - vectorContext.flush(); - this.replayGroup = replayGroup; - } -}; +/** + * @type {string} + * @see http://www.w3.org/TR/XMLHttpRequest/#statustext + */ +goog.net.XhrLike.prototype.statusText; /** - * @param {ol.layer.Layer} layer Layer. - * @return {ol.renderer.canvas.Layer} Canvas layer renderer. + * @param {string} method + * @param {string} url + * @param {?boolean=} opt_async + * @param {?string=} opt_user + * @param {?string=} opt_password + * @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method */ -ol.renderer.canvas.Map.prototype.getCanvasLayerRenderer = function(layer) { - var layerRenderer = this.getLayerRenderer(layer); - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer); - return /** @type {ol.renderer.canvas.Layer} */ (layerRenderer); -}; +goog.net.XhrLike.prototype.open = function(method, url, opt_async, opt_user, + opt_password) {}; /** - * @inheritDoc + * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} opt_data + * @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method */ -ol.renderer.canvas.Map.prototype.getType = function() { - return ol.RendererType.CANVAS; -}; +goog.net.XhrLike.prototype.send = function(opt_data) {}; /** - * @inheritDoc + * @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method */ -ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { +goog.net.XhrLike.prototype.abort = function() {}; - if (goog.isNull(frameState)) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.canvas_, false); - this.renderedVisible_ = false; - } - return; - } - var context = this.context_; - var width = frameState.size[0] * frameState.pixelRatio; - var height = frameState.size[1] * frameState.pixelRatio; - if (this.canvas_.width != width || this.canvas_.height != height) { - this.canvas_.width = width; - this.canvas_.height = height; - } else { - context.clearRect(0, 0, this.canvas_.width, this.canvas_.height); - } +/** + * @param {string} header + * @param {string} value + * @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method + */ +goog.net.XhrLike.prototype.setRequestHeader = function(header, value) {}; - this.calculateMatrices2D(frameState); - this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); +/** + * @param {string} header + * @return {string} + * @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method + */ +goog.net.XhrLike.prototype.getResponseHeader = function(header) {}; - var layerStatesArray = frameState.layerStatesArray; - var viewResolution = frameState.viewState.resolution; - var i, ii, layer, layerRenderer, layerState; - for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { - layerState = layerStatesArray[i]; - layer = layerState.layer; - layerRenderer = this.getLayerRenderer(layer); - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer); - if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) || - layerState.sourceState != ol.source.State.READY) { - continue; - } - if (layerRenderer.prepareFrame(frameState, layerState)) { - layerRenderer.composeFrame(frameState, layerState, context); - } - } - this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); +/** + * @return {string} + * @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method + */ +goog.net.XhrLike.prototype.getAllResponseHeaders = function() {}; - if (!this.renderedVisible_) { - goog.style.setElementShown(this.canvas_, true); - this.renderedVisible_ = true; - } +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. - this.scheduleRemoveUnusedLayerRenderers(frameState); - this.scheduleExpireIconCache(frameState); -}; +/** + * @fileoverview Interface for a factory for creating XMLHttpRequest objects + * and metadata about them. + * @author dbk@google.com (David Barrett-Kahn) + */ -goog.provide('ol.renderer.dom.Layer'); +goog.provide('goog.net.XmlHttpFactory'); -goog.require('goog.dom'); -goog.require('ol.layer.Layer'); -goog.require('ol.renderer.Layer'); +/** @suppress {extraRequire} Typedef. */ +goog.require('goog.net.XhrLike'); /** + * Abstract base class for an XmlHttpRequest factory. * @constructor - * @extends {ol.renderer.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Layer} layer Layer. - * @param {!Element} target Target. */ -ol.renderer.dom.Layer = function(mapRenderer, layer, target) { - - goog.base(this, mapRenderer, layer); - - /** - * @type {!Element} - * @protected - */ - this.target = target; - +goog.net.XmlHttpFactory = function() { }; -goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer); /** - * Clear rendered elements. + * Cache of options - we only actually call internalGetOptions once. + * @type {Object} + * @private */ -ol.renderer.dom.Layer.prototype.clearFrame = goog.nullFunction; +goog.net.XmlHttpFactory.prototype.cachedOptions_ = null; /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.layer.LayerState} layerState Layer state. + * @return {!goog.net.XhrLike.OrNative} A new XhrLike instance. */ -ol.renderer.dom.Layer.prototype.composeFrame = goog.nullFunction; +goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod; /** - * @return {!Element} Target. + * @return {Object} Options describing how xhr objects obtained from this + * factory should be used. */ -ol.renderer.dom.Layer.prototype.getTarget = function() { - return this.target; +goog.net.XmlHttpFactory.prototype.getOptions = function() { + return this.cachedOptions_ || + (this.cachedOptions_ = this.internalGetOptions()); }; /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.layer.LayerState} layerState Layer state. - * @return {boolean} whether composeFrame should be called. + * Override this method in subclasses to preserve the caching offered by + * getOptions(). + * @return {Object} Options describing how xhr objects obtained from this + * factory should be used. + * @protected */ -ol.renderer.dom.Layer.prototype.prepareFrame = goog.abstractMethod; +goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod; -goog.provide('ol.renderer.dom.ImageLayer'); +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.vec.Mat4'); -goog.require('ol.ImageBase'); -goog.require('ol.ImageState'); -goog.require('ol.ViewHint'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.layer.Image'); -goog.require('ol.proj'); -goog.require('ol.renderer.dom.Layer'); -goog.require('ol.vec.Mat4'); +/** + * @fileoverview Implementation of XmlHttpFactory which allows construction from + * simple factory methods. + * @author dbk@google.com (David Barrett-Kahn) + */ + +goog.provide('goog.net.WrapperXmlHttpFactory'); + +/** @suppress {extraRequire} Typedef. */ +goog.require('goog.net.XhrLike'); +goog.require('goog.net.XmlHttpFactory'); /** + * An xhr factory subclass which can be constructed using two factory methods. + * This exists partly to allow the preservation of goog.net.XmlHttp.setFactory() + * with an unchanged signature. + * @param {function():!goog.net.XhrLike.OrNative} xhrFactory + * A function which returns a new XHR object. + * @param {function():!Object} optionsFactory A function which returns the + * options associated with xhr objects from this factory. + * @extends {goog.net.XmlHttpFactory} * @constructor - * @extends {ol.renderer.dom.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Image} imageLayer Image layer. + * @final */ -ol.renderer.dom.ImageLayer = function(mapRenderer, imageLayer) { - var target = goog.dom.createElement(goog.dom.TagName.DIV); - target.style.position = 'absolute'; - - goog.base(this, mapRenderer, imageLayer, target); +goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) { + goog.net.XmlHttpFactory.call(this); /** - * The last rendered image. + * XHR factory method. + * @type {function() : !goog.net.XhrLike.OrNative} * @private - * @type {?ol.ImageBase} */ - this.image_ = null; + this.xhrFactory_ = xhrFactory; /** + * Options factory method. + * @type {function() : !Object} * @private - * @type {goog.vec.Mat4.Number} */ - this.transform_ = goog.vec.Mat4.createNumberIdentity(); - + this.optionsFactory_ = optionsFactory; }; -goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer); +goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory); -/** - * @inheritDoc - */ -ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg) { - var layer = this.getLayer(); - var source = layer.getSource(); - var resolution = frameState.viewState.resolution; - var rotation = frameState.viewState.rotation; - var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtPixel( - resolution, rotation, coordinate, skippedFeatureUids, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - return callback.call(thisArg, feature, layer); - }); +/** @override */ +goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() { + return this.xhrFactory_(); }; -/** - * @inheritDoc - */ -ol.renderer.dom.ImageLayer.prototype.clearFrame = function() { - goog.dom.removeChildren(this.target); - this.image_ = null; +/** @override */ +goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() { + return this.optionsFactory_(); }; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /** - * @inheritDoc + * @fileoverview Low level handling of XMLHttpRequest. + * @author arv@google.com (Erik Arvidsson) + * @author dbk@google.com (David Barrett-Kahn) */ -ol.renderer.dom.ImageLayer.prototype.prepareFrame = - function(frameState, layerState) { - - var viewState = frameState.viewState; - var viewCenter = viewState.center; - var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; - - var image = this.image_; - var imageLayer = this.getLayer(); - goog.asserts.assertInstanceof(imageLayer, ol.layer.Image); - var imageSource = imageLayer.getSource(); - - var hints = frameState.viewHints; - var renderedExtent = frameState.extent; - if (goog.isDef(layerState.extent)) { - renderedExtent = ol.extent.getIntersection( - renderedExtent, layerState.extent); - } +goog.provide('goog.net.DefaultXmlHttpFactory'); +goog.provide('goog.net.XmlHttp'); +goog.provide('goog.net.XmlHttp.OptionType'); +goog.provide('goog.net.XmlHttp.ReadyState'); +goog.provide('goog.net.XmlHttpDefines'); - if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && - !ol.extent.isEmpty(renderedExtent)) { - var projection = viewState.projection; - var sourceProjection = imageSource.getProjection(); - if (!goog.isNull(sourceProjection)) { - goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection)); - projection = sourceProjection; - } - var image_ = imageSource.getImage(renderedExtent, viewResolution, - frameState.pixelRatio, projection); - if (!goog.isNull(image_)) { - var imageState = image_.getState(); - if (imageState == ol.ImageState.IDLE) { - goog.events.listenOnce(image_, goog.events.EventType.CHANGE, - this.handleImageChange, false, this); - image_.load(); - } else if (imageState == ol.ImageState.LOADED) { - image = image_; - } - } - } +goog.require('goog.asserts'); +goog.require('goog.net.WrapperXmlHttpFactory'); +goog.require('goog.net.XmlHttpFactory'); - if (!goog.isNull(image)) { - var imageExtent = image.getExtent(); - var imageResolution = image.getResolution(); - var transform = goog.vec.Mat4.createNumber(); - ol.vec.Mat4.makeTransform2D(transform, - frameState.size[0] / 2, frameState.size[1] / 2, - imageResolution / viewResolution, imageResolution / viewResolution, - viewRotation, - (imageExtent[0] - viewCenter[0]) / imageResolution, - (viewCenter[1] - imageExtent[3]) / imageResolution); - if (image != this.image_) { - var imageElement = image.getImage(this); - // Bootstrap sets the style max-width: 100% for all images, which breaks - // prevents the image from being displayed in FireFox. Workaround by - // overriding the max-width style. - imageElement.style.maxWidth = 'none'; - imageElement.style.position = 'absolute'; - goog.dom.removeChildren(this.target); - goog.dom.appendChild(this.target, imageElement); - this.image_ = image; - } - this.setTransform_(transform); - this.updateAttributions(frameState.attributions, image.getAttributions()); - this.updateLogos(frameState, imageSource); - } - return true; +/** + * Static class for creating XMLHttpRequest objects. + * @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object. + */ +goog.net.XmlHttp = function() { + return goog.net.XmlHttp.factory_.createInstance(); }; /** - * @param {goog.vec.Mat4.Number} transform Transform. - * @private + * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to + * true bypasses the ActiveX probing code. + * NOTE(ruilopes): Due to the way JSCompiler works, this define *will not* strip + * out the ActiveX probing code from binaries. To achieve this, use + * {@code goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR} instead. + * TODO(ruilopes): Collapse both defines. */ -ol.renderer.dom.ImageLayer.prototype.setTransform_ = function(transform) { - if (!ol.vec.Mat4.equals2D(transform, this.transform_)) { - ol.dom.transformElement2D(this.target, transform, 6); - goog.vec.Mat4.setFromArray(this.transform_, transform); - } -}; +goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false); -// FIXME probably need to reset TileLayerZ if offsets get too large -// FIXME when zooming out, preserve higher Z divs to avoid white flash -goog.provide('ol.renderer.dom.TileLayer'); +/** @const */ +goog.net.XmlHttpDefines = {}; -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.object'); -goog.require('goog.style'); -goog.require('goog.vec.Mat4'); -goog.require('ol'); -goog.require('ol.Coordinate'); -goog.require('ol.TileCoord'); -goog.require('ol.TileRange'); -goog.require('ol.TileState'); -goog.require('ol.ViewHint'); -goog.require('ol.dom'); -goog.require('ol.dom.BrowserFeature'); -goog.require('ol.extent'); -goog.require('ol.layer.Tile'); -goog.require('ol.renderer.dom.Layer'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); -goog.require('ol.vec.Mat4'); +/** + * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to + * true eliminates the ActiveX probing code. + */ +goog.define('goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR', false); /** - * @constructor - * @extends {ol.renderer.dom.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Tile} tileLayer Tile layer. + * Gets the options to use with the XMLHttpRequest objects obtained using + * the static methods. + * @return {Object} The options. */ -ol.renderer.dom.TileLayer = function(mapRenderer, tileLayer) { +goog.net.XmlHttp.getOptions = function() { + return goog.net.XmlHttp.factory_.getOptions(); +}; - var target = goog.dom.createElement(goog.dom.TagName.DIV); - target.style.position = 'absolute'; - // Needed for IE7-8 to render a transformed element correctly - if (ol.dom.BrowserFeature.USE_MS_MATRIX_TRANSFORM) { - target.style.width = '100%'; - target.style.height = '100%'; - } +/** + * Type of options that an XmlHttp object can have. + * @enum {number} + */ +goog.net.XmlHttp.OptionType = { + /** + * Whether a goog.nullFunction should be used to clear the onreadystatechange + * handler instead of null. + */ + USE_NULL_FUNCTION: 0, + + /** + * NOTE(user): In IE if send() errors on a *local* request the readystate + * is still changed to COMPLETE. We need to ignore it and allow the + * try/catch around send() to pick up the error. + */ + LOCAL_REQUEST_ERROR: 1 +}; - goog.base(this, mapRenderer, tileLayer, target); +/** + * Status constants for XMLHTTP, matches: + * http://msdn.microsoft.com/library/default.asp?url=/library/ + * en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp + * @enum {number} + */ +goog.net.XmlHttp.ReadyState = { /** - * @private - * @type {boolean} + * Constant for when xmlhttprequest.readyState is uninitialized */ - this.renderedVisible_ = true; + UNINITIALIZED: 0, /** - * @private - * @type {number} + * Constant for when xmlhttprequest.readyState is loading. */ - this.renderedOpacity_ = 1; + LOADING: 1, /** - * @private - * @type {number} + * Constant for when xmlhttprequest.readyState is loaded. */ - this.renderedRevision_ = 0; + LOADED: 2, /** - * @private - * @type {Object.<number, ol.renderer.dom.TileLayerZ_>} + * Constant for when xmlhttprequest.readyState is in an interactive state. */ - this.tileLayerZs_ = {}; + INTERACTIVE: 3, + /** + * Constant for when xmlhttprequest.readyState is completed + */ + COMPLETE: 4 }; -goog.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer); /** - * @inheritDoc + * The global factory instance for creating XMLHttpRequest objects. + * @type {goog.net.XmlHttpFactory} + * @private */ -ol.renderer.dom.TileLayer.prototype.clearFrame = function() { - goog.dom.removeChildren(this.target); - this.renderedRevision_ = 0; +goog.net.XmlHttp.factory_; + + +/** + * Sets the factories for creating XMLHttpRequest objects and their options. + * @param {Function} factory The factory for XMLHttpRequest objects. + * @param {Function} optionsFactory The factory for options. + * @deprecated Use setGlobalFactory instead. + */ +goog.net.XmlHttp.setFactory = function(factory, optionsFactory) { + goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory( + goog.asserts.assert(factory), + goog.asserts.assert(optionsFactory))); }; /** - * @inheritDoc + * Sets the global factory object. + * @param {!goog.net.XmlHttpFactory} factory New global factory object. */ -ol.renderer.dom.TileLayer.prototype.prepareFrame = - function(frameState, layerState) { +goog.net.XmlHttp.setGlobalFactory = function(factory) { + goog.net.XmlHttp.factory_ = factory; +}; - if (!layerState.visible) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.target, false); - this.renderedVisible_ = false; - } - return true; - } - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var projection = viewState.projection; - var tileLayer = this.getLayer(); - goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile); - var tileSource = tileLayer.getSource(); - var tileGrid = tileSource.getTileGridForProjection(projection); - var tileGutter = tileSource.getGutter(); - var z = tileGrid.getZForResolution(viewState.resolution); - var tileResolution = tileGrid.getResolution(z); - var center = viewState.center; - var extent; - if (tileResolution == viewState.resolution) { - center = this.snapCenterToPixel(center, tileResolution, frameState.size); - extent = ol.extent.getForViewAndSize( - center, tileResolution, viewState.rotation, frameState.size); +/** + * Default factory to use when creating xhr objects. You probably shouldn't be + * instantiating this directly, but rather using it via goog.net.XmlHttp. + * @extends {goog.net.XmlHttpFactory} + * @constructor + */ +goog.net.DefaultXmlHttpFactory = function() { + goog.net.XmlHttpFactory.call(this); +}; +goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory); + + +/** @override */ +goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() { + var progId = this.getProgId_(); + if (progId) { + return new ActiveXObject(progId); } else { - extent = frameState.extent; + return new XMLHttpRequest(); } +}; - if (goog.isDef(layerState.extent)) { - extent = ol.extent.getIntersection(extent, layerState.extent); + +/** @override */ +goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() { + var progId = this.getProgId_(); + var options = {}; + if (progId) { + options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true; + options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true; } + return options; +}; - var tileRange = tileGrid.getTileRangeForExtentAndResolution( - extent, tileResolution); - /** @type {Object.<number, Object.<string, ol.Tile>>} */ - var tilesToDrawByZ = {}; - tilesToDrawByZ[z] = {}; +/** + * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized. + * @type {string|undefined} + * @private + */ +goog.net.DefaultXmlHttpFactory.prototype.ieProgId_; - var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) { - return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED; - }, tileSource, pixelRatio, projection); - var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource, - tilesToDrawByZ, getTileIfLoaded); - var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); - if (!goog.isDef(useInterimTilesOnError)) { - useInterimTilesOnError = true; +/** + * Initialize the private state used by other functions. + * @return {string} The ActiveX PROG ID string to use to create xhr's in IE. + * @private + */ +goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() { + if (goog.net.XmlHttp.ASSUME_NATIVE_XHR || + goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) { + return ''; } - var tmpExtent = ol.extent.createEmpty(); - var tmpTileRange = new ol.TileRange(0, 0, 0, 0); - var childTileRange, fullyLoaded, tile, tileState, x, y; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - tileState = tile.getState(); - if (tileState == ol.TileState.LOADED) { - tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile; - continue; - } else if (tileState == ol.TileState.EMPTY || - (tileState == ol.TileState.ERROR && - !useInterimTilesOnError)) { - continue; - } - - fullyLoaded = tileGrid.forEachTileCoordParentTileRange( - tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); - if (!fullyLoaded) { - childTileRange = tileGrid.getTileCoordChildTileRange( - tile.tileCoord, tmpTileRange, tmpExtent); - if (!goog.isNull(childTileRange)) { - findLoadedTiles(z + 1, childTileRange); - } + // The following blog post describes what PROG IDs to use to create the + // XMLHTTP object in Internet Explorer: + // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx + // However we do not (yet) fully trust that this will be OK for old versions + // of IE on Win9x so we therefore keep the last 2. + if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' && + typeof ActiveXObject != 'undefined') { + // Candidate Active X types. + var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', + 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; + for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) { + var candidate = ACTIVE_X_IDENTS[i]; + /** @preserveTry */ + try { + new ActiveXObject(candidate); + // NOTE(user): cannot assign progid and return candidate in one line + // because JSCompiler complaings: BUG 658126 + this.ieProgId_ = candidate; + return candidate; + } catch (e) { + // do nothing; try next choice } - } + // couldn't find any matches + throw Error('Could not create ActiveXObject. ActiveX might be disabled,' + + ' or MSXML might not be installed'); } - // If the tile source revision changes, we destroy the existing DOM structure - // so that a new one will be created. It would be more efficient to modify - // the existing structure. - var tileLayerZ, tileLayerZKey; - if (this.renderedRevision_ != tileSource.getRevision()) { - for (tileLayerZKey in this.tileLayerZs_) { - tileLayerZ = this.tileLayerZs_[+tileLayerZKey]; - goog.dom.removeNode(tileLayerZ.target); - } - this.tileLayerZs_ = {}; - this.renderedRevision_ = tileSource.getRevision(); - } + return /** @type {string} */ (this.ieProgId_); +}; - /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); - goog.array.sort(zs); - /** @type {Object.<number, boolean>} */ - var newTileLayerZKeys = {}; +//Set the global factory to an instance of the default factory. +goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory()); - var iz, iziz, tileCoordKey, tileCoordOrigin, tilesToDraw; - for (iz = 0, iziz = zs.length; iz < iziz; ++iz) { - tileLayerZKey = zs[iz]; - if (tileLayerZKey in this.tileLayerZs_) { - tileLayerZ = this.tileLayerZs_[tileLayerZKey]; - } else { - tileCoordOrigin = - tileGrid.getTileCoordForCoordAndZ(center, tileLayerZKey); - tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin); - newTileLayerZKeys[tileLayerZKey] = true; - this.tileLayerZs_[tileLayerZKey] = tileLayerZ; - } - tilesToDraw = tilesToDrawByZ[tileLayerZKey]; - for (tileCoordKey in tilesToDraw) { - tileLayerZ.addTile(tilesToDraw[tileCoordKey], tileGutter); - } - tileLayerZ.finalizeAddTiles(); +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple utilities for dealing with URI strings. + * + * This is intended to be a lightweight alternative to constructing goog.Uri + * objects. Whereas goog.Uri adds several kilobytes to the binary regardless + * of how much of its functionality you use, this is designed to be a set of + * mostly-independent utilities so that the compiler includes only what is + * necessary for the task. Estimated savings of porting is 5k pre-gzip and + * 1.5k post-gzip. To ensure the savings remain, future developers should + * avoid adding new functionality to existing functions, but instead create + * new ones and factor out shared code. + * + * Many of these utilities have limited functionality, tailored to common + * cases. The query parameter utilities assume that the parameter keys are + * already encoded, since most keys are compile-time alphanumeric strings. The + * query parameter mutation utilities also do not tolerate fragment identifiers. + * + * By design, these functions can be slower than goog.Uri equivalents. + * Repeated calls to some of functions may be quadratic in behavior for IE, + * although the effect is somewhat limited given the 2kb limit. + * + * One advantage of the limited functionality here is that this approach is + * less sensitive to differences in URI encodings than goog.Uri, since these + * functions modify the strings in place, rather than decoding and + * re-encoding. + * + * Uses features of RFC 3986 for parsing/formatting URIs: + * http://www.ietf.org/rfc/rfc3986.txt + * + * @author gboyer@google.com (Garrett Boyer) - The "lightened" design. + */ + +goog.provide('goog.uri.utils'); +goog.provide('goog.uri.utils.ComponentIndex'); +goog.provide('goog.uri.utils.QueryArray'); +goog.provide('goog.uri.utils.QueryValue'); +goog.provide('goog.uri.utils.StandardQueryParam'); + +goog.require('goog.asserts'); +goog.require('goog.string'); +goog.require('goog.userAgent'); + + +/** + * Character codes inlined to avoid object allocations due to charCode. + * @enum {number} + * @private + */ +goog.uri.utils.CharCode_ = { + AMPERSAND: 38, + EQUAL: 61, + HASH: 35, + QUESTION: 63 +}; + + +/** + * Builds a URI string from already-encoded parts. + * + * No encoding is performed. Any component may be omitted as either null or + * undefined. + * + * @param {?string=} opt_scheme The scheme such as 'http'. + * @param {?string=} opt_userInfo The user name before the '@'. + * @param {?string=} opt_domain The domain such as 'www.google.com', already + * URI-encoded. + * @param {(string|number|null)=} opt_port The port number. + * @param {?string=} opt_path The path, already URI-encoded. If it is not + * empty, it must begin with a slash. + * @param {?string=} opt_queryData The URI-encoded query data. + * @param {?string=} opt_fragment The URI-encoded fragment identifier. + * @return {string} The fully combined URI. + */ +goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo, + opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) { + var out = ''; + + if (opt_scheme) { + out += opt_scheme + ':'; } - /** @type {Array.<number>} */ - var tileLayerZKeys = - goog.array.map(goog.object.getKeys(this.tileLayerZs_), Number); - goog.array.sort(tileLayerZKeys); + if (opt_domain) { + out += '//'; - var i, ii, j, origin, resolution; - var transform = goog.vec.Mat4.createNumber(); - for (i = 0, ii = tileLayerZKeys.length; i < ii; ++i) { - tileLayerZKey = tileLayerZKeys[i]; - tileLayerZ = this.tileLayerZs_[tileLayerZKey]; - if (!(tileLayerZKey in tilesToDrawByZ)) { - goog.dom.removeNode(tileLayerZ.target); - delete this.tileLayerZs_[tileLayerZKey]; - continue; + if (opt_userInfo) { + out += opt_userInfo + '@'; } - resolution = tileLayerZ.getResolution(); - origin = tileLayerZ.getOrigin(); - ol.vec.Mat4.makeTransform2D(transform, - frameState.size[0] / 2, frameState.size[1] / 2, - resolution / viewState.resolution, - resolution / viewState.resolution, - viewState.rotation, - (origin[0] - center[0]) / resolution, - (center[1] - origin[1]) / resolution); - tileLayerZ.setTransform(transform); - if (tileLayerZKey in newTileLayerZKeys) { - for (j = tileLayerZKey - 1; j >= 0; --j) { - if (j in this.tileLayerZs_) { - goog.dom.insertSiblingAfter( - tileLayerZ.target, this.tileLayerZs_[j].target); - break; - } - } - if (j < 0) { - goog.dom.insertChildAt(this.target, tileLayerZ.target, 0); - } - } else { - if (!frameState.viewHints[ol.ViewHint.ANIMATING] && - !frameState.viewHints[ol.ViewHint.INTERACTING]) { - tileLayerZ.removeTilesOutsideExtent(extent, tmpTileRange); - } + + out += opt_domain; + + if (opt_port) { + out += ':' + opt_port; } } - if (layerState.opacity != this.renderedOpacity_) { - ol.dom.setOpacity(this.target, layerState.opacity); - this.renderedOpacity_ = layerState.opacity; - } + if (opt_path) { + out += opt_path; + } + + if (opt_queryData) { + out += '?' + opt_queryData; + } + + if (opt_fragment) { + out += '#' + opt_fragment; + } + + return out; +}; + + +/** + * A regular expression for breaking a URI into its component parts. + * + * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B + * As the "first-match-wins" algorithm is identical to the "greedy" + * disambiguation method used by POSIX regular expressions, it is natural and + * commonplace to use a regular expression for parsing the potential five + * components of a URI reference. + * + * The following line is the regular expression for breaking-down a + * well-formed URI reference into its components. + * + * <pre> + * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + * 12 3 4 5 6 7 8 9 + * </pre> + * + * The numbers in the second line above are only to assist readability; they + * indicate the reference points for each subexpression (i.e., each paired + * parenthesis). We refer to the value matched for subexpression <n> as $<n>. + * For example, matching the above expression to + * <pre> + * http://www.ics.uci.edu/pub/ietf/uri/#Related + * </pre> + * results in the following subexpression matches: + * <pre> + * $1 = http: + * $2 = http + * $3 = //www.ics.uci.edu + * $4 = www.ics.uci.edu + * $5 = /pub/ietf/uri/ + * $6 = <undefined> + * $7 = <undefined> + * $8 = #Related + * $9 = Related + * </pre> + * where <undefined> indicates that the component is not present, as is the + * case for the query component in the above example. Therefore, we can + * determine the value of the five components as + * <pre> + * scheme = $2 + * authority = $4 + * path = $5 + * query = $7 + * fragment = $9 + * </pre> + * + * The regular expression has been modified slightly to expose the + * userInfo, domain, and port separately from the authority. + * The modified version yields + * <pre> + * $1 = http scheme + * $2 = <undefined> userInfo -\ + * $3 = www.ics.uci.edu domain | authority + * $4 = <undefined> port -/ + * $5 = /pub/ietf/uri/ path + * $6 = <undefined> query without ? + * $7 = Related fragment without # + * </pre> + * @type {!RegExp} + * @private + */ +goog.uri.utils.splitRe_ = new RegExp( + '^' + + '(?:' + + '([^:/?#.]+)' + // scheme - ignore special characters + // used by other URL parts such as :, + // ?, /, #, and . + ':)?' + + '(?://' + + '(?:([^/?#]*)@)?' + // userInfo + '([^/#?]*?)' + // domain + '(?::([0-9]+))?' + // port + '(?=[/#?]|$)' + // authority-terminating character + ')?' + + '([^?#]+)?' + // path + '(?:\\?([^#]*))?' + // query + '(?:#(.*))?' + // fragment + '$'); + - if (layerState.visible && !this.renderedVisible_) { - goog.style.setElementShown(this.target, true); - this.renderedVisible_ = true; - } +/** + * The index of each URI component in the return value of goog.uri.utils.split. + * @enum {number} + */ +goog.uri.utils.ComponentIndex = { + SCHEME: 1, + USER_INFO: 2, + DOMAIN: 3, + PORT: 4, + PATH: 5, + QUERY_DATA: 6, + FRAGMENT: 7 +}; - this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, - projection, extent, z, tileLayer.getPreload()); - this.scheduleExpireCache(frameState, tileSource); - this.updateLogos(frameState, tileSource); - return true; -}; +/** + * Splits a URI into its component parts. + * + * Each component can be accessed via the component indices; for example: + * <pre> + * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA]; + * </pre> + * + * @param {string} uri The URI string to examine. + * @return {!Array<string|undefined>} Each component still URI-encoded. + * Each component that is present will contain the encoded value, whereas + * components that are not present will be undefined or empty, depending + * on the browser's regular expression implementation. Never null, since + * arbitrary strings may still look like path names. + */ +goog.uri.utils.split = function(uri) { + goog.uri.utils.phishingProtection_(); + // See @return comment -- never null. + return /** @type {!Array<string|undefined>} */ ( + uri.match(goog.uri.utils.splitRe_)); +}; /** - * @constructor + * Safari has a nasty bug where if you have an http URL with a username, e.g., + * http://evil.com%2F@google.com/ + * Safari will report that window.location.href is + * http://evil.com/google.com/ + * so that anyone who tries to parse the domain of that URL will get + * the wrong domain. We've seen exploits where people use this to trick + * Safari into loading resources from evil domains. + * + * To work around this, we run a little "Safari phishing check", and throw + * an exception if we see this happening. + * + * There is no convenient place to put this check. We apply it to + * anyone doing URI parsing on Webkit. We're not happy about this, but + * it fixes the problem. + * + * This should be removed once Safari fixes their bug. + * + * Exploit reported by Masato Kinugawa. + * + * @type {boolean} * @private - * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. - * @param {ol.TileCoord} tileCoordOrigin Tile coord origin. */ -ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) { +goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT; - /** - * @type {!Element} - */ - this.target = goog.dom.createElement(goog.dom.TagName.DIV); - this.target.style.position = 'absolute'; - this.target.style.width = '100%'; - this.target.style.height = '100%'; - if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { - /** - * Needed due to issues with IE7-8 clipping of transformed elements - * Solution is to translate separately from the scaled/rotated elements - * @private - * @type {!Element} - */ - this.translateTarget_ = goog.dom.createElement(goog.dom.TagName.DIV); - this.translateTarget_.style.position = 'absolute'; - this.translateTarget_.style.width = '100%'; - this.translateTarget_.style.height = '100%'; +/** + * Check to see if the user is being phished. + * @private + */ +goog.uri.utils.phishingProtection_ = function() { + if (goog.uri.utils.needsPhishingProtection_) { + // Turn protection off, so that we don't recurse. + goog.uri.utils.needsPhishingProtection_ = false; - goog.dom.appendChild(this.target, this.translateTarget_); + // Use quoted access, just in case the user isn't using location externs. + var location = goog.global['location']; + if (location) { + var href = location['href']; + if (href) { + var domain = goog.uri.utils.getDomain(href); + if (domain && domain != location['hostname']) { + // Phishing attack + goog.uri.utils.needsPhishingProtection_ = true; + throw Error(); + } + } + } } +}; - /** - * @private - * @type {ol.tilegrid.TileGrid} - */ - this.tileGrid_ = tileGrid; - - /** - * @private - * @type {ol.TileCoord} - */ - this.tileCoordOrigin_ = tileCoordOrigin; - /** - * @private - * @type {ol.Coordinate} - */ - this.origin_ = - ol.extent.getTopLeft(tileGrid.getTileCoordExtent(tileCoordOrigin)); +/** + * @param {?string} uri A possibly null string. + * @param {boolean=} opt_preserveReserved If true, percent-encoding of RFC-3986 + * reserved characters will not be removed. + * @return {?string} The string URI-decoded, or null if uri is null. + * @private + */ +goog.uri.utils.decodeIfPossible_ = function(uri, opt_preserveReserved) { + if (!uri) { + return uri; + } - /** - * @private - * @type {number} - */ - this.resolution_ = tileGrid.getResolution(tileCoordOrigin[0]); + return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri); +}; - /** - * @private - * @type {Object.<string, ol.Tile>} - */ - this.tiles_ = {}; - /** - * @private - * @type {DocumentFragment} - */ - this.documentFragment_ = null; +/** + * Gets a URI component by index. + * + * It is preferred to use the getPathEncoded() variety of functions ahead, + * since they are more readable. + * + * @param {goog.uri.utils.ComponentIndex} componentIndex The component index. + * @param {string} uri The URI to examine. + * @return {?string} The still-encoded component, or null if the component + * is not present. + * @private + */ +goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) { + // Convert undefined, null, and empty string into null. + return goog.uri.utils.split(uri)[componentIndex] || null; +}; - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.transform_ = goog.vec.Mat4.createNumberIdentity(); +/** + * @param {string} uri The URI to examine. + * @return {?string} The protocol or scheme, or null if none. Does not + * include trailing colons or slashes. + */ +goog.uri.utils.getScheme = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.SCHEME, uri); }; /** - * @param {ol.Tile} tile Tile. - * @param {number} tileGutter Tile gutter. + * Gets the effective scheme for the URL. If the URL is relative then the + * scheme is derived from the page's location. + * @param {string} uri The URI to examine. + * @return {string} The protocol or scheme, always lower case. */ -ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) { - var tileCoord = tile.tileCoord; - var tileCoordZ = tileCoord[0]; - var tileCoordX = tileCoord[1]; - var tileCoordY = tileCoord[2]; - goog.asserts.assert(tileCoordZ == this.tileCoordOrigin_[0]); - var tileCoordKey = ol.tilecoord.toString(tileCoord); - if (tileCoordKey in this.tiles_) { - return; - } - var tileSize = this.tileGrid_.getTileSize(tileCoordZ); - var image = tile.getImage(this); - var imageStyle = image.style; - // Bootstrap sets the style max-width: 100% for all images, which - // prevents the tile from being displayed in FireFox. Workaround - // by overriding the max-width style. - imageStyle.maxWidth = 'none'; - var tileElement; - var tileElementStyle; - if (tileGutter > 0) { - tileElement = goog.dom.createElement(goog.dom.TagName.DIV); - tileElementStyle = tileElement.style; - tileElementStyle.overflow = 'hidden'; - tileElementStyle.width = tileSize + 'px'; - tileElementStyle.height = tileSize + 'px'; - imageStyle.position = 'absolute'; - imageStyle.left = -tileGutter + 'px'; - imageStyle.top = -tileGutter + 'px'; - imageStyle.width = (tileSize + 2 * tileGutter) + 'px'; - imageStyle.height = (tileSize + 2 * tileGutter) + 'px'; - goog.dom.appendChild(tileElement, image); - } else { - imageStyle.width = tileSize + 'px'; - imageStyle.height = tileSize + 'px'; - tileElement = image; - tileElementStyle = imageStyle; - } - tileElementStyle.position = 'absolute'; - tileElementStyle.left = - ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize) + 'px'; - tileElementStyle.top = - ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize) + 'px'; - if (goog.isNull(this.documentFragment_)) { - this.documentFragment_ = document.createDocumentFragment(); +goog.uri.utils.getEffectiveScheme = function(uri) { + var scheme = goog.uri.utils.getScheme(uri); + if (!scheme && self.location) { + var protocol = self.location.protocol; + scheme = protocol.substr(0, protocol.length - 1); } - goog.dom.appendChild(this.documentFragment_, tileElement); - this.tiles_[tileCoordKey] = tile; + // NOTE: When called from a web worker in Firefox 3.5, location maybe null. + // All other browsers with web workers support self.location from the worker. + return scheme ? scheme.toLowerCase() : ''; }; /** - * FIXME empty description for jsdoc + * @param {string} uri The URI to examine. + * @return {?string} The user name still encoded, or null if none. */ -ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() { - if (!goog.isNull(this.documentFragment_)) { - if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { - goog.dom.appendChild(this.translateTarget_, this.documentFragment_); - } else { - goog.dom.appendChild(this.target, this.documentFragment_); - } - this.documentFragment_ = null; - } +goog.uri.utils.getUserInfoEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.USER_INFO, uri); }; /** - * @return {ol.Coordinate} Origin. + * @param {string} uri The URI to examine. + * @return {?string} The decoded user info, or null if none. */ -ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() { - return this.origin_; +goog.uri.utils.getUserInfo = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getUserInfoEncoded(uri)); }; /** - * @return {number} Resolution. + * @param {string} uri The URI to examine. + * @return {?string} The domain name still encoded, or null if none. */ -ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() { - return this.resolution_; +goog.uri.utils.getDomainEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.DOMAIN, uri); }; /** - * @param {ol.Extent} extent Extent. - * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. + * @param {string} uri The URI to examine. + * @return {?string} The decoded domain, or null if none. */ -ol.renderer.dom.TileLayerZ_.prototype.removeTilesOutsideExtent = - function(extent, opt_tileRange) { - var tileRange = this.tileGrid_.getTileRangeForExtentAndZ( - extent, this.tileCoordOrigin_[0], opt_tileRange); - /** @type {Array.<ol.Tile>} */ - var tilesToRemove = []; - var tile, tileCoordKey; - for (tileCoordKey in this.tiles_) { - tile = this.tiles_[tileCoordKey]; - if (!tileRange.contains(tile.tileCoord)) { - tilesToRemove.push(tile); - } - } - var i, ii; - for (i = 0, ii = tilesToRemove.length; i < ii; ++i) { - tile = tilesToRemove[i]; - tileCoordKey = ol.tilecoord.toString(tile.tileCoord); - goog.dom.removeNode(tile.getImage(this)); - delete this.tiles_[tileCoordKey]; - } +goog.uri.utils.getDomain = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */); }; /** - * @param {goog.vec.Mat4.Number} transform Transform. + * @param {string} uri The URI to examine. + * @return {?number} The port number, or null if none. */ -ol.renderer.dom.TileLayerZ_.prototype.setTransform = function(transform) { - if (!ol.vec.Mat4.equals2D(transform, this.transform_)) { - if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { - ol.dom.transformElement2D(this.target, transform, 6, - this.translateTarget_); - } else { - ol.dom.transformElement2D(this.target, transform, 6); - } - goog.vec.Mat4.setFromArray(this.transform_, transform); - } +goog.uri.utils.getPort = function(uri) { + // Coerce to a number. If the result of getComponentByIndex_ is null or + // non-numeric, the number coersion yields NaN. This will then return + // null for all non-numeric cases (though also zero, which isn't a relevant + // port number). + return Number(goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.PORT, uri)) || null; }; -goog.provide('ol.renderer.dom.VectorLayer'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.vec.Mat4'); -goog.require('ol.ViewHint'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.Event'); -goog.require('ol.render.EventType'); -goog.require('ol.render.canvas.Immediate'); -goog.require('ol.render.canvas.ReplayGroup'); -goog.require('ol.renderer.dom.Layer'); -goog.require('ol.renderer.vector'); -goog.require('ol.vec.Mat4'); +/** + * @param {string} uri The URI to examine. + * @return {?string} The path still encoded, or null if none. Includes the + * leading slash, if any. + */ +goog.uri.utils.getPathEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.PATH, uri); +}; /** - * @constructor - * @extends {ol.renderer.dom.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Vector} vectorLayer Vector layer. + * @param {string} uri The URI to examine. + * @return {?string} The decoded path, or null if none. Includes the leading + * slash, if any. */ -ol.renderer.dom.VectorLayer = function(mapRenderer, vectorLayer) { - - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = ol.dom.createCanvasContext2D(); +goog.uri.utils.getPath = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getPathEncoded(uri), true /* opt_preserveReserved */); +}; - var target = this.context_.canvas; - // Bootstrap sets the style max-width: 100% for all images, which breaks - // prevents the image from being displayed in FireFox. Workaround by - // overriding the max-width style. - target.style.maxWidth = 'none'; - target.style.position = 'absolute'; - goog.base(this, mapRenderer, vectorLayer, target); +/** + * @param {string} uri The URI to examine. + * @return {?string} The query data still encoded, or null if none. Does not + * include the question mark itself. + */ +goog.uri.utils.getQueryData = function(uri) { + return goog.uri.utils.getComponentByIndex_( + goog.uri.utils.ComponentIndex.QUERY_DATA, uri); +}; - /** - * @private - * @type {boolean} - */ - this.dirty_ = false; - /** - * @private - * @type {number} - */ - this.renderedRevision_ = -1; +/** + * @param {string} uri The URI to examine. + * @return {?string} The fragment identifier, or null if none. Does not + * include the hash mark itself. + */ +goog.uri.utils.getFragmentEncoded = function(uri) { + // The hash mark may not appear in any other part of the URL. + var hashIndex = uri.indexOf('#'); + return hashIndex < 0 ? null : uri.substr(hashIndex + 1); +}; - /** - * @private - * @type {number} - */ - this.renderedResolution_ = NaN; - /** - * @private - * @type {ol.Extent} - */ - this.renderedExtent_ = ol.extent.createEmpty(); +/** + * @param {string} uri The URI to examine. + * @param {?string} fragment The encoded fragment identifier, or null if none. + * Does not include the hash mark itself. + * @return {string} The URI with the fragment set. + */ +goog.uri.utils.setFragmentEncoded = function(uri, fragment) { + return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : ''); +}; - /** - * @private - * @type {function(ol.Feature, ol.Feature): number|null} - */ - this.renderedRenderOrder_ = null; - /** - * @private - * @type {ol.render.canvas.ReplayGroup} - */ - this.replayGroup_ = null; +/** + * @param {string} uri The URI to examine. + * @return {?string} The decoded fragment identifier, or null if none. Does + * not include the hash mark. + */ +goog.uri.utils.getFragment = function(uri) { + return goog.uri.utils.decodeIfPossible_( + goog.uri.utils.getFragmentEncoded(uri)); +}; - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.transform_ = goog.vec.Mat4.createNumber(); +/** + * Extracts everything up to the port of the URI. + * @param {string} uri The URI string. + * @return {string} Everything up to and including the port. + */ +goog.uri.utils.getHost = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + pieces[goog.uri.utils.ComponentIndex.SCHEME], + pieces[goog.uri.utils.ComponentIndex.USER_INFO], + pieces[goog.uri.utils.ComponentIndex.DOMAIN], + pieces[goog.uri.utils.ComponentIndex.PORT]); }; -goog.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer); /** - * @inheritDoc + * Extracts the path of the URL and everything after. + * @param {string} uri The URI string. + * @return {string} The URI, starting at the path and including the query + * parameters and fragment identifier. */ -ol.renderer.dom.VectorLayer.prototype.composeFrame = - function(frameState, layerState) { - - var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); - goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector); +goog.uri.utils.getPathAndAfter = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts(null, null, null, null, + pieces[goog.uri.utils.ComponentIndex.PATH], + pieces[goog.uri.utils.ComponentIndex.QUERY_DATA], + pieces[goog.uri.utils.ComponentIndex.FRAGMENT]); +}; - var viewState = frameState.viewState; - var viewRotation = viewState.rotation; - var pixelRatio = frameState.pixelRatio; - var transform = ol.vec.Mat4.makeTransform2D(this.transform_, - pixelRatio * frameState.size[0] / 2, - pixelRatio * frameState.size[1] / 2, - pixelRatio / viewState.resolution, - -pixelRatio / viewState.resolution, - -viewState.rotation, - -viewState.center[0], -viewState.center[1]); +/** + * Gets the URI with the fragment identifier removed. + * @param {string} uri The URI to examine. + * @return {string} Everything preceding the hash mark. + */ +goog.uri.utils.removeFragment = function(uri) { + // The hash mark may not appear in any other part of the URL. + var hashIndex = uri.indexOf('#'); + return hashIndex < 0 ? uri : uri.substr(0, hashIndex); +}; - var context = this.context_; - // Clear the canvas and set the correct size - context.canvas.width = frameState.size[0]; - context.canvas.height = frameState.size[1]; +/** + * Ensures that two URI's have the exact same domain, scheme, and port. + * + * Unlike the version in goog.Uri, this checks protocol, and therefore is + * suitable for checking against the browser's same-origin policy. + * + * @param {string} uri1 The first URI. + * @param {string} uri2 The second URI. + * @return {boolean} Whether they have the same scheme, domain and port. + */ +goog.uri.utils.haveSameDomain = function(uri1, uri2) { + var pieces1 = goog.uri.utils.split(uri1); + var pieces2 = goog.uri.utils.split(uri2); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == + pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && + pieces1[goog.uri.utils.ComponentIndex.SCHEME] == + pieces2[goog.uri.utils.ComponentIndex.SCHEME] && + pieces1[goog.uri.utils.ComponentIndex.PORT] == + pieces2[goog.uri.utils.ComponentIndex.PORT]; +}; - this.dispatchEvent_(ol.render.EventType.PRECOMPOSE, frameState, transform); - var replayGroup = this.replayGroup_; +/** + * Asserts that there are no fragment or query identifiers, only in uncompiled + * mode. + * @param {string} uri The URI to examine. + * @private + */ +goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) { + // NOTE: would use goog.asserts here, but jscompiler doesn't know that + // indexOf has no side effects. + if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) { + throw Error('goog.uri.utils: Fragment or query identifiers are not ' + + 'supported: [' + uri + ']'); + } +}; - if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { - context.globalAlpha = layerState.opacity; - replayGroup.replay(context, pixelRatio, transform, viewRotation, - frameState.skippedFeatureUids); +/** + * Supported query parameter values by the parameter serializing utilities. + * + * If a value is null or undefined, the key-value pair is skipped, as an easy + * way to omit parameters conditionally. Non-array parameters are converted + * to a string and URI encoded. Array values are expanded into multiple + * &key=value pairs, with each element stringized and URI-encoded. + * + * @typedef {*} + */ +goog.uri.utils.QueryValue; - this.dispatchEvent_(ol.render.EventType.RENDER, frameState, transform); - } - this.dispatchEvent_(ol.render.EventType.POSTCOMPOSE, frameState, transform); -}; +/** + * An array representing a set of query parameters with alternating keys + * and values. + * + * Keys are assumed to be URI encoded already and live at even indices. See + * goog.uri.utils.QueryValue for details on how parameter values are encoded. + * + * Example: + * <pre> + * var data = [ + * // Simple param: ?name=BobBarker + * 'name', 'BobBarker', + * // Conditional param -- may be omitted entirely. + * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null, + * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null + * 'house', ['LosAngeles', 'NewYork', null] + * ]; + * </pre> + * + * @typedef {!Array<string|goog.uri.utils.QueryValue>} + */ +goog.uri.utils.QueryArray; /** - * @param {ol.render.EventType} type Event type. - * @param {olx.FrameState} frameState Frame state. - * @param {goog.vec.Mat4.Number} transform Transform. - * @private + * Parses encoded query parameters and calls callback function for every + * parameter found in the string. + * + * Missing value of parameter (e.g. “…&key&…”) is treated as if the value was an + * empty string. Keys may be empty strings (e.g. “…&=value&…”) which also means + * that “…&=&…” and “…&&…” will result in an empty key and value. + * + * @param {string} encodedQuery Encoded query string excluding question mark at + * the beginning. + * @param {function(string, string)} callback Function called for every + * parameter found in query string. The first argument (name) will not be + * urldecoded (so the function is consistent with buildQueryData), but the + * second will. If the parameter has no value (i.e. “=” was not present) + * the second argument (value) will be an empty string. */ -ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ = - function(type, frameState, transform) { - var context = this.context_; - var layer = this.getLayer(); - if (layer.hasListener(type)) { - var render = new ol.render.canvas.Immediate( - context, frameState.pixelRatio, frameState.extent, transform, - frameState.viewState.rotation); - var event = new ol.render.Event(type, layer, render, null, - frameState, context, null); - layer.dispatchEvent(event); - render.flush(); +goog.uri.utils.parseQueryData = function(encodedQuery, callback) { + var pairs = encodedQuery.split('&'); + for (var i = 0; i < pairs.length; i++) { + var indexOfEquals = pairs[i].indexOf('='); + var name = null; + var value = null; + if (indexOfEquals >= 0) { + name = pairs[i].substring(0, indexOfEquals); + value = pairs[i].substring(indexOfEquals + 1); + } else { + name = pairs[i]; + } + callback(name, value ? goog.string.urlDecode(value) : ''); } }; /** - * @inheritDoc + * Appends a URI and query data in a string buffer with special preconditions. + * + * Internal implementation utility, performing very few object allocations. + * + * @param {!Array<string|undefined>} buffer A string buffer. The first element + * must be the base URI, and may have a fragment identifier. If the array + * contains more than one element, the second element must be an ampersand, + * and may be overwritten, depending on the base URI. Undefined elements + * are treated as empty-string. + * @return {string} The concatenated URI and query data. + * @private */ -ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg) { - if (goog.isNull(this.replayGroup_)) { - return undefined; - } else { - var resolution = frameState.viewState.resolution; - var rotation = frameState.viewState.rotation; - var layer = this.getLayer(); - /** @type {Object.<string, boolean>} */ - var features = {}; - return this.replayGroup_.forEachGeometryAtPixel(resolution, - rotation, coordinate, frameState.skippedFeatureUids, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - goog.asserts.assert(goog.isDef(feature)); - var key = goog.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback.call(thisArg, feature, layer); - } - }); +goog.uri.utils.appendQueryData_ = function(buffer) { + if (buffer[1]) { + // At least one query parameter was added. We need to check the + // punctuation mark, which is currently an ampersand, and also make sure + // there aren't any interfering fragment identifiers. + var baseUri = /** @type {string} */ (buffer[0]); + var hashIndex = baseUri.indexOf('#'); + if (hashIndex >= 0) { + // Move the fragment off the base part of the URI into the end. + buffer.push(baseUri.substr(hashIndex)); + buffer[0] = baseUri = baseUri.substr(0, hashIndex); + } + var questionIndex = baseUri.indexOf('?'); + if (questionIndex < 0) { + // No question mark, so we need a question mark instead of an ampersand. + buffer[1] = '?'; + } else if (questionIndex == baseUri.length - 1) { + // Question mark is the very last character of the existing URI, so don't + // append an additional delimiter. + buffer[1] = undefined; + } } + + return buffer.join(''); }; /** - * Handle changes in image style state. - * @param {goog.events.Event} event Image style change event. + * Appends key=value pairs to an array, supporting multi-valued objects. + * @param {string} key The key prefix. + * @param {goog.uri.utils.QueryValue} value The value to serialize. + * @param {!Array<string>} pairs The array to which the 'key=value' strings + * should be appended. * @private */ -ol.renderer.dom.VectorLayer.prototype.handleImageChange_ = - function(event) { - this.renderIfReadyAndVisible(); +goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) { + if (goog.isArray(value)) { + // Convince the compiler it's an array. + goog.asserts.assertArray(value); + for (var j = 0; j < value.length; j++) { + // Convert to string explicitly, to short circuit the null and array + // logic in this function -- this ensures that null and undefined get + // written as literal 'null' and 'undefined', and arrays don't get + // expanded out but instead encoded in the default way. + goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs); + } + } else if (value != null) { + // Skip a top-level null or undefined entirely. + pairs.push('&', key, + // Check for empty string. Zero gets encoded into the url as literal + // strings. For empty string, skip the equal sign, to be consistent + // with UriBuilder.java. + value === '' ? '' : '=', + goog.string.urlEncode(value)); + } }; /** - * @inheritDoc + * Builds a buffer of query data from a sequence of alternating keys and values. + * + * @param {!Array<string|undefined>} buffer A string buffer to append to. The + * first element appended will be an '&', and may be replaced by the caller. + * @param {!goog.uri.utils.QueryArray|!Arguments} keysAndValues An array with + * alternating keys and values -- see the typedef. + * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. + * @return {!Array<string|undefined>} The buffer argument. + * @private */ -ol.renderer.dom.VectorLayer.prototype.prepareFrame = - function(frameState, layerState) { - - var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); - goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector); - var vectorSource = vectorLayer.getSource(); - - this.updateAttributions( - frameState.attributions, vectorSource.getAttributions()); - this.updateLogos(frameState, vectorSource); - - if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] || - frameState.viewHints[ol.ViewHint.INTERACTING])) { - return true; - } - - var frameStateExtent = frameState.extent; - var viewState = frameState.viewState; - var projection = viewState.projection; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var vectorLayerRevision = vectorLayer.getRevision(); - var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); - var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); +goog.uri.utils.buildQueryDataBuffer_ = function( + buffer, keysAndValues, opt_startIndex) { + goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0), + 0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.'); - if (!goog.isDef(vectorLayerRenderOrder)) { - vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) { + goog.uri.utils.appendKeyValuePairs_( + keysAndValues[i], keysAndValues[i + 1], buffer); } - var extent = ol.extent.buffer(frameStateExtent, - vectorLayerRenderBuffer * resolution); + return buffer; +}; - if (!this.dirty_ && - this.renderedResolution_ == resolution && - this.renderedRevision_ == vectorLayerRevision && - this.renderedRenderOrder_ == vectorLayerRenderOrder && - ol.extent.containsExtent(this.renderedExtent_, extent)) { - return true; - } - // FIXME dispose of old replayGroup in post render - goog.dispose(this.replayGroup_); - this.replayGroup_ = null; +/** + * Builds a query data string from a sequence of alternating keys and values. + * Currently generates "&key&" for empty args. + * + * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and + * values. See the typedef. + * @param {number=} opt_startIndex A start offset into the arary, defaults to 0. + * @return {string} The encoded query string, in the form 'a=1&b=2'. + */ +goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) { + var buffer = goog.uri.utils.buildQueryDataBuffer_( + [], keysAndValues, opt_startIndex); + buffer[0] = ''; // Remove the leading ampersand. + return buffer.join(''); +}; - this.dirty_ = false; - var replayGroup = - new ol.render.canvas.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); - vectorSource.loadFeatures(extent, resolution, projection); - var renderFeature = - /** - * @param {ol.Feature} feature Feature. - * @this {ol.renderer.dom.VectorLayer} - */ - function(feature) { - var styles; - if (goog.isDef(feature.getStyleFunction())) { - styles = feature.getStyleFunction().call(feature, resolution); - } else if (goog.isDef(vectorLayer.getStyleFunction())) { - styles = vectorLayer.getStyleFunction()(feature, resolution); - } - if (goog.isDefAndNotNull(styles)) { - var dirty = this.renderFeature( - feature, resolution, pixelRatio, styles, replayGroup); - this.dirty_ = this.dirty_ || dirty; - } - }; - if (!goog.isNull(vectorLayerRenderOrder)) { - /** @type {Array.<ol.Feature>} */ - var features = []; - vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - features.push(feature); - }, this); - goog.array.sort(features, vectorLayerRenderOrder); - goog.array.forEach(features, renderFeature, this); - } else { - vectorSource.forEachFeatureInExtentAtResolution( - extent, resolution, renderFeature, this); +/** + * Builds a buffer of query data from a map. + * + * @param {!Array<string|undefined>} buffer A string buffer to append to. The + * first element appended will be an '&', and may be replaced by the caller. + * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys + * are URI-encoded parameter keys, and the values conform to the contract + * specified in the goog.uri.utils.QueryValue typedef. + * @return {!Array<string|undefined>} The buffer argument. + * @private + */ +goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) { + for (var key in map) { + goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer); } - replayGroup.finish(); - - this.renderedResolution_ = resolution; - this.renderedRevision_ = vectorLayerRevision; - this.renderedRenderOrder_ = vectorLayerRenderOrder; - this.renderedExtent_ = extent; - this.replayGroup_ = replayGroup; - return true; + return buffer; }; /** - * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {Array.<ol.style.Style>} styles Array of styles - * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. - * @return {boolean} `true` if an image is loading. + * Builds a query data string from a map. + * Currently generates "&key&" for empty args. + * + * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys + * are URI-encoded parameter keys, and the values are arbitrary types + * or arrays. Keys with a null value are dropped. + * @return {string} The encoded query string, in the form 'a=1&b=2'. */ -ol.renderer.dom.VectorLayer.prototype.renderFeature = - function(feature, resolution, pixelRatio, styles, replayGroup) { - if (!goog.isDefAndNotNull(styles)) { - return false; - } - var i, ii, loading = false; - for (i = 0, ii = styles.length; i < ii; ++i) { - loading = ol.renderer.vector.renderFeature( - replayGroup, feature, styles[i], - ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), - this.handleImageChange_, this) || loading; - } - return loading; +goog.uri.utils.buildQueryDataFromMap = function(map) { + var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map); + buffer[0] = ''; + return buffer.join(''); }; -goog.provide('ol.renderer.dom.Map'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventType'); -goog.require('goog.functions'); -goog.require('goog.style'); -goog.require('goog.vec.Mat4'); -goog.require('ol'); -goog.require('ol.RendererType'); -goog.require('ol.css'); -goog.require('ol.dom'); -goog.require('ol.layer.Image'); -goog.require('ol.layer.Tile'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.Event'); -goog.require('ol.render.EventType'); -goog.require('ol.render.canvas.Immediate'); -goog.require('ol.render.canvas.ReplayGroup'); -goog.require('ol.renderer.Map'); -goog.require('ol.renderer.dom.ImageLayer'); -goog.require('ol.renderer.dom.Layer'); -goog.require('ol.renderer.dom.TileLayer'); -goog.require('ol.renderer.dom.VectorLayer'); -goog.require('ol.renderer.vector'); -goog.require('ol.source.State'); -goog.require('ol.vec.Mat4'); +/** + * Appends URI parameters to an existing URI. + * + * The variable arguments may contain alternating keys and values. Keys are + * assumed to be already URI encoded. The values should not be URI-encoded, + * and will instead be encoded by this function. + * <pre> + * appendParams('http://www.foo.com?existing=true', + * 'key1', 'value1', + * 'key2', 'value?willBeEncoded', + * 'key3', ['valueA', 'valueB', 'valueC'], + * 'key4', null); + * result: 'http://www.foo.com?existing=true&' + + * 'key1=value1&' + + * 'key2=value%3FwillBeEncoded&' + + * 'key3=valueA&key3=valueB&key3=valueC' + * </pre> + * + * A single call to this function will not exhibit quadratic behavior in IE, + * whereas multiple repeated calls may, although the effect is limited by + * fact that URL's generally can't exceed 2kb. + * + * @param {string} uri The original URI, which may already have query data. + * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args + * An array or argument list conforming to goog.uri.utils.QueryArray. + * @return {string} The URI with all query parameters added. + */ +goog.uri.utils.appendParams = function(uri, var_args) { + return goog.uri.utils.appendQueryData_( + arguments.length == 2 ? + goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) : + goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1)); +}; /** - * @constructor - * @extends {ol.renderer.Map} - * @param {Element} container Container. - * @param {ol.Map} map Map. + * Appends query parameters from a map. + * + * @param {string} uri The original URI, which may already have query data. + * @param {!Object<goog.uri.utils.QueryValue>} map An object where keys are + * URI-encoded parameter keys, and the values are arbitrary types or arrays. + * Keys with a null value are dropped. + * @return {string} The new parameters. */ -ol.renderer.dom.Map = function(container, map) { +goog.uri.utils.appendParamsFromMap = function(uri, map) { + return goog.uri.utils.appendQueryData_( + goog.uri.utils.buildQueryDataBufferFromMap_([uri], map)); +}; - goog.base(this, container, map); - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = null; - if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE)) { - this.context_ = ol.dom.createCanvasContext2D(); - var canvas = this.context_.canvas; - canvas.style.position = 'absolute'; - canvas.style.width = '100%'; - canvas.style.height = '100%'; - canvas.className = ol.css.CLASS_UNSELECTABLE; - goog.dom.insertChildAt(container, canvas, 0); +/** + * Appends a single URI parameter. + * + * Repeated calls to this can exhibit quadratic behavior in IE6 due to the + * way string append works, though it should be limited given the 2kb limit. + * + * @param {string} uri The original URI, which may already have query data. + * @param {string} key The key, which must already be URI encoded. + * @param {*=} opt_value The value, which will be stringized and encoded + * (assumed not already to be encoded). If omitted, undefined, or null, the + * key will be added as a valueless parameter. + * @return {string} The URI with the query parameter added. + */ +goog.uri.utils.appendParam = function(uri, key, opt_value) { + var paramArr = [uri, '&', key]; + if (goog.isDefAndNotNull(opt_value)) { + paramArr.push('=', goog.string.urlEncode(opt_value)); } + return goog.uri.utils.appendQueryData_(paramArr); +}; - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.transform_ = goog.vec.Mat4.createNumber(); - /** - * @type {!Element} - * @private - */ - this.layersPane_ = goog.dom.createElement(goog.dom.TagName.DIV); - this.layersPane_.className = ol.css.CLASS_UNSELECTABLE; - var style = this.layersPane_.style; - style.position = 'absolute'; - style.width = '100%'; - style.height = '100%'; +/** + * Finds the next instance of a query parameter with the specified name. + * + * Does not instantiate any objects. + * + * @param {string} uri The URI to search. May contain a fragment identifier + * if opt_hashIndex is specified. + * @param {number} startIndex The index to begin searching for the key at. A + * match may be found even if this is one character after the ampersand. + * @param {string} keyEncoded The URI-encoded key. + * @param {number} hashOrEndIndex Index to stop looking at. If a hash + * mark is present, it should be its index, otherwise it should be the + * length of the string. + * @return {number} The position of the first character in the key's name, + * immediately after either a question mark or a dot. + * @private + */ +goog.uri.utils.findParam_ = function( + uri, startIndex, keyEncoded, hashOrEndIndex) { + var index = startIndex; + var keyLength = keyEncoded.length; - // in IE < 9, we need to return false from ondragstart to cancel the default - // behavior of dragging images, which is interfering with the custom handler - // in the Drag interaction subclasses - if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { - this.layersPane_.ondragstart = goog.functions.FALSE; - this.layersPane_.onselectstart = goog.functions.FALSE; + // Search for the key itself and post-filter for surronuding punctuation, + // rather than expensively building a regexp. + while ((index = uri.indexOf(keyEncoded, index)) >= 0 && + index < hashOrEndIndex) { + var precedingChar = uri.charCodeAt(index - 1); + // Ensure that the preceding character is '&' or '?'. + if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND || + precedingChar == goog.uri.utils.CharCode_.QUESTION) { + // Ensure the following character is '&', '=', '#', or NaN + // (end of string). + var followingChar = uri.charCodeAt(index + keyLength); + if (!followingChar || + followingChar == goog.uri.utils.CharCode_.EQUAL || + followingChar == goog.uri.utils.CharCode_.AMPERSAND || + followingChar == goog.uri.utils.CharCode_.HASH) { + return index; + } + } + index += keyLength + 1; } - // prevent the img context menu on mobile devices - goog.events.listen(this.layersPane_, goog.events.EventType.TOUCHSTART, - goog.events.Event.preventDefault); - - goog.dom.insertChildAt(container, this.layersPane_, 0); + return -1; +}; - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; -}; -goog.inherits(ol.renderer.dom.Map, ol.renderer.Map); +/** + * Regular expression for finding a hash mark or end of string. + * @type {RegExp} + * @private + */ +goog.uri.utils.hashOrEndRe_ = /#|$/; /** - * @inheritDoc + * Determines if the URI contains a specific key. + * + * Performs no object instantiations. + * + * @param {string} uri The URI to process. May contain a fragment + * identifier. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {boolean} Whether the key is present. */ -ol.renderer.dom.Map.prototype.disposeInternal = function() { - goog.dom.removeNode(this.layersPane_); - goog.base(this, 'disposeInternal'); +goog.uri.utils.hasParam = function(uri, keyEncoded) { + return goog.uri.utils.findParam_(uri, 0, keyEncoded, + uri.search(goog.uri.utils.hashOrEndRe_)) >= 0; }; /** - * @inheritDoc + * Gets the first value of a query parameter. + * @param {string} uri The URI to process. May contain a fragment. + * @param {string} keyEncoded The URI-encoded key. Case-sensitive. + * @return {?string} The first value of the parameter (URI-decoded), or null + * if the parameter is not found. */ -ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { - var layerRenderer; - if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { - layerRenderer = new ol.renderer.dom.ImageLayer(this, layer); - } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { - layerRenderer = new ol.renderer.dom.TileLayer(this, layer); - } else if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && - ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { - layerRenderer = new ol.renderer.dom.VectorLayer(this, layer); - } else { - goog.asserts.fail(); +goog.uri.utils.getParamValue = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var foundIndex = goog.uri.utils.findParam_( + uri, 0, keyEncoded, hashOrEndIndex); + + if (foundIndex < 0) { return null; + } else { + var endPosition = uri.indexOf('&', foundIndex); + if (endPosition < 0 || endPosition > hashOrEndIndex) { + endPosition = hashOrEndIndex; + } + // Progress forth to the end of the "key=" or "key&" substring. + foundIndex += keyEncoded.length + 1; + // Use substr, because it (unlike substring) will return empty string + // if foundIndex > endPosition. + return goog.string.urlDecode( + uri.substr(foundIndex, endPosition - foundIndex)); } - return layerRenderer; }; /** - * @param {ol.render.EventType} type Event type. - * @param {olx.FrameState} frameState Frame state. - * @private + * Gets all values of a query parameter. + * @param {string} uri The URI to process. May contain a framgnet. + * @param {string} keyEncoded The URI-encoded key. Case-snsitive. + * @return {!Array<string>} All URI-decoded values with the given key. + * If the key is not found, this will have length 0, but never be null. */ -ol.renderer.dom.Map.prototype.dispatchComposeEvent_ = - function(type, frameState) { - var map = this.getMap(); - if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && map.hasListener(type)) { - var extent = frameState.extent; - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var resolution = viewState.resolution; - var rotation = viewState.rotation; - var context = this.context_; - var canvas = context.canvas; +goog.uri.utils.getParamValues = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var position = 0; + var foundIndex; + var result = []; - ol.vec.Mat4.makeTransform2D(this.transform_, - canvas.width / 2, - canvas.height / 2, - pixelRatio / viewState.resolution, - -pixelRatio / viewState.resolution, - -viewState.rotation, - -viewState.center[0], -viewState.center[1]); - var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, - extent, this.transform_, rotation); - var replayGroup = new ol.render.canvas.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); - var composeEvent = new ol.render.Event(type, map, vectorContext, - replayGroup, frameState, context, null); - map.dispatchEvent(composeEvent); - replayGroup.finish(); - if (!replayGroup.isEmpty()) { - replayGroup.replay(context, pixelRatio, this.transform_, rotation, {}); + while ((foundIndex = goog.uri.utils.findParam_( + uri, position, keyEncoded, hashOrEndIndex)) >= 0) { + // Find where this parameter ends, either the '&' or the end of the + // query parameters. + position = uri.indexOf('&', foundIndex); + if (position < 0 || position > hashOrEndIndex) { + position = hashOrEndIndex; } - vectorContext.flush(); - this.replayGroup = replayGroup; + + // Progress forth to the end of the "key=" or "key&" substring. + foundIndex += keyEncoded.length + 1; + // Use substr, because it (unlike substring) will return empty string + // if foundIndex > position. + result.push(goog.string.urlDecode(uri.substr( + foundIndex, position - foundIndex))); } + + return result; }; /** - * @inheritDoc + * Regexp to find trailing question marks and ampersands. + * @type {RegExp} + * @private */ -ol.renderer.dom.Map.prototype.getType = function() { - return ol.RendererType.DOM; -}; +goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/; /** - * @inheritDoc + * Removes all instances of a query parameter. + * @param {string} uri The URI to process. Must not contain a fragment. + * @param {string} keyEncoded The URI-encoded key. + * @return {string} The URI with all instances of the parameter removed. */ -ol.renderer.dom.Map.prototype.renderFrame = function(frameState) { +goog.uri.utils.removeParam = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_); + var position = 0; + var foundIndex; + var buffer = []; - if (goog.isNull(frameState)) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.layersPane_, false); - this.renderedVisible_ = false; - } - return; + // Look for a query parameter. + while ((foundIndex = goog.uri.utils.findParam_( + uri, position, keyEncoded, hashOrEndIndex)) >= 0) { + // Get the portion of the query string up to, but not including, the ? + // or & starting the parameter. + buffer.push(uri.substring(position, foundIndex)); + // Progress to immediately after the '&'. If not found, go to the end. + // Avoid including the hash mark. + position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex, + hashOrEndIndex); } - /** - * @this {ol.renderer.dom.Map} - * @param {Element} elem - * @param {number} i - */ - var addChild; + // Append everything that is remaining. + buffer.push(uri.substr(position)); - // appendChild is actually more performant than insertBefore - // in IE 7 and 8. http://jsperf.com/reattaching-dom-nodes - if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { - addChild = - /** - * @this {ol.renderer.dom.Map} - * @param {Element} elem - */ ( - function(elem) { - goog.dom.appendChild(this.layersPane_, elem); - }); - } else { - addChild = - /** - * @this {ol.renderer.dom.Map} - * @param {Element} elem - * @param {number} i - */ ( - function(elem, i) { - goog.dom.insertChildAt(this.layersPane_, elem, i); - }); - } + // Join the buffer, and remove trailing punctuation that remains. + return buffer.join('').replace( + goog.uri.utils.trailingQueryPunctuationRe_, '$1'); +}; - var map = this.getMap(); - if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && - (map.hasListener(ol.render.EventType.PRECOMPOSE) || - map.hasListener(ol.render.EventType.POSTCOMPOSE))) { - var canvas = this.context_.canvas; - canvas.width = frameState.size[0]; - canvas.height = frameState.size[1]; - } - this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); +/** + * Replaces all existing definitions of a parameter with a single definition. + * + * Repeated calls to this can exhibit quadratic behavior due to the need to + * find existing instances and reconstruct the string, though it should be + * limited given the 2kb limit. Consider using appendParams to append multiple + * parameters in bulk. + * + * @param {string} uri The original URI, which may already have query data. + * @param {string} keyEncoded The key, which must already be URI encoded. + * @param {*} value The value, which will be stringized and encoded (assumed + * not already to be encoded). + * @return {string} The URI with the query parameter added. + */ +goog.uri.utils.setParam = function(uri, keyEncoded, value) { + return goog.uri.utils.appendParam( + goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value); +}; - var layerStatesArray = frameState.layerStatesArray; - var i, ii, layer, layerRenderer, layerState; - for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { - layerState = layerStatesArray[i]; - layer = layerState.layer; - layerRenderer = /** @type {ol.renderer.dom.Layer} */ ( - this.getLayerRenderer(layer)); - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer); - addChild.call(this, layerRenderer.getTarget(), i); - if (layerState.sourceState == ol.source.State.READY) { - if (layerRenderer.prepareFrame(frameState, layerState)) { - layerRenderer.composeFrame(frameState, layerState); - } - } else { - layerRenderer.clearFrame(); - } - } - var layerStates = frameState.layerStates; - var layerKey; - for (layerKey in this.getLayerRenderers()) { - if (!(layerKey in layerStates)) { - layerRenderer = this.getLayerRendererByKey(layerKey); - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer); - goog.dom.removeNode(layerRenderer.getTarget()); - } +/** + * Generates a URI path using a given URI and a path with checks to + * prevent consecutive "//". The baseUri passed in must not contain + * query or fragment identifiers. The path to append may not contain query or + * fragment identifiers. + * + * @param {string} baseUri URI to use as the base. + * @param {string} path Path to append. + * @return {string} Updated URI. + */ +goog.uri.utils.appendPath = function(baseUri, path) { + goog.uri.utils.assertNoFragmentsOrQueries_(baseUri); + + // Remove any trailing '/' + if (goog.string.endsWith(baseUri, '/')) { + baseUri = baseUri.substr(0, baseUri.length - 1); + } + // Remove any leading '/' + if (goog.string.startsWith(path, '/')) { + path = path.substr(1); } + return goog.string.buildString(baseUri, '/', path); +}; - if (!this.renderedVisible_) { - goog.style.setElementShown(this.layersPane_, true); - this.renderedVisible_ = true; + +/** + * Replaces the path. + * @param {string} uri URI to use as the base. + * @param {string} path New path. + * @return {string} Updated URI. + */ +goog.uri.utils.setPath = function(uri, path) { + // Add any missing '/'. + if (!goog.string.startsWith(path, '/')) { + path = '/' + path; } + var parts = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts( + parts[goog.uri.utils.ComponentIndex.SCHEME], + parts[goog.uri.utils.ComponentIndex.USER_INFO], + parts[goog.uri.utils.ComponentIndex.DOMAIN], + parts[goog.uri.utils.ComponentIndex.PORT], + path, + parts[goog.uri.utils.ComponentIndex.QUERY_DATA], + parts[goog.uri.utils.ComponentIndex.FRAGMENT]); +}; - this.calculateMatrices2D(frameState); - this.scheduleRemoveUnusedLayerRenderers(frameState); - this.scheduleExpireIconCache(frameState); - this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); +/** + * Standard supported query parameters. + * @enum {string} + */ +goog.uri.utils.StandardQueryParam = { + + /** Unused parameter for unique-ifying. */ + RANDOM: 'zx' }; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. + +/** + * Sets the zx parameter of a URI to a random value. + * @param {string} uri Any URI. + * @return {string} That URI with the "zx" parameter added or replaced to + * contain a random string. + */ +goog.uri.utils.makeUnique = function(uri) { + return goog.uri.utils.setParam(uri, + goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString()); +}; + +// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -67401,15286 +68136,16962 @@ ol.renderer.dom.Map.prototype.renderFrame = function(frameState) { // See the License for the specific language governing permissions and // limitations under the License. - /** - * @fileoverview Constants used by the WebGL rendering, including all of the - * constants used from the WebGL context. For example, instead of using - * context.ARRAY_BUFFER, your code can use - * goog.webgl.ARRAY_BUFFER. The benefits for doing this include allowing - * the compiler to optimize your code so that the compiled code does not have to - * contain large strings to reference these properties, and reducing runtime - * property access. + * @fileoverview Wrapper class for handling XmlHttpRequests. + * + * One off requests can be sent through goog.net.XhrIo.send() or an + * instance can be created to send multiple requests. Each request uses its + * own XmlHttpRequest object and handles clearing of the event callback to + * ensure no leaks. + * + * XhrIo is event based, it dispatches events when a request finishes, fails or + * succeeds or when the ready-state changes. The ready-state or timeout event + * fires first, followed by a generic completed event. Then the abort, error, + * or success event is fired as appropriate. Lastly, the ready event will fire + * to indicate that the object may be used to make another request. + * + * The error event may also be called before completed and + * ready-state-change if the XmlHttpRequest.open() or .send() methods throw. + * + * This class does not support multiple requests, queuing, or prioritization. + * + * Tested = IE6, FF1.5, Safari, Opera 8.5 + * + * TODO(user): Error cases aren't playing nicely in Safari. * - * Values are taken from the WebGL Spec: - * https://www.khronos.org/registry/webgl/specs/1.0/#WEBGLRENDERINGCONTEXT */ -goog.provide('goog.webgl'); + +goog.provide('goog.net.XhrIo'); +goog.provide('goog.net.XhrIo.ResponseType'); + +goog.require('goog.Timer'); +goog.require('goog.array'); +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.events.EventTarget'); +goog.require('goog.json'); +goog.require('goog.log'); +goog.require('goog.net.ErrorCode'); +goog.require('goog.net.EventType'); +goog.require('goog.net.HttpStatus'); +goog.require('goog.net.XmlHttp'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.structs'); +goog.require('goog.structs.Map'); +goog.require('goog.uri.utils'); +goog.require('goog.userAgent'); + +goog.forwardDeclare('goog.Uri'); + + + +/** + * Basic class for handling XMLHttpRequests. + * @param {goog.net.XmlHttpFactory=} opt_xmlHttpFactory Factory to use when + * creating XMLHttpRequest objects. + * @constructor + * @extends {goog.events.EventTarget} + */ +goog.net.XhrIo = function(opt_xmlHttpFactory) { + goog.net.XhrIo.base(this, 'constructor'); + + /** + * Map of default headers to add to every request, use: + * XhrIo.headers.set(name, value) + * @type {!goog.structs.Map} + */ + this.headers = new goog.structs.Map(); + + /** + * Optional XmlHttpFactory + * @private {goog.net.XmlHttpFactory} + */ + this.xmlHttpFactory_ = opt_xmlHttpFactory || null; + + /** + * Whether XMLHttpRequest is active. A request is active from the time send() + * is called until onReadyStateChange() is complete, or error() or abort() + * is called. + * @private {boolean} + */ + this.active_ = false; + + /** + * The XMLHttpRequest object that is being used for the transfer. + * @private {?goog.net.XhrLike.OrNative} + */ + this.xhr_ = null; + + /** + * The options to use with the current XMLHttpRequest object. + * @private {Object} + */ + this.xhrOptions_ = null; + + /** + * Last URL that was requested. + * @private {string|goog.Uri} + */ + this.lastUri_ = ''; + + /** + * Method for the last request. + * @private {string} + */ + this.lastMethod_ = ''; + + /** + * Last error code. + * @private {!goog.net.ErrorCode} + */ + this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR; + + /** + * Last error message. + * @private {Error|string} + */ + this.lastError_ = ''; + + /** + * Used to ensure that we don't dispatch an multiple ERROR events. This can + * happen in IE when it does a synchronous load and one error is handled in + * the ready statte change and one is handled due to send() throwing an + * exception. + * @private {boolean} + */ + this.errorDispatched_ = false; + + /** + * Used to make sure we don't fire the complete event from inside a send call. + * @private {boolean} + */ + this.inSend_ = false; + + /** + * Used in determining if a call to {@link #onReadyStateChange_} is from + * within a call to this.xhr_.open. + * @private {boolean} + */ + this.inOpen_ = false; + + /** + * Used in determining if a call to {@link #onReadyStateChange_} is from + * within a call to this.xhr_.abort. + * @private {boolean} + */ + this.inAbort_ = false; + + /** + * Number of milliseconds after which an incomplete request will be aborted + * and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no timeout + * is set. + * @private {number} + */ + this.timeoutInterval_ = 0; + + /** + * Timer to track request timeout. + * @private {?number} + */ + this.timeoutId_ = null; + + /** + * The requested type for the response. The empty string means use the default + * XHR behavior. + * @private {goog.net.XhrIo.ResponseType} + */ + this.responseType_ = goog.net.XhrIo.ResponseType.DEFAULT; + + /** + * Whether a "credentialed" request is to be sent (one that is aware of + * cookies and authentication). This is applicable only for cross-domain + * requests and more recent browsers that support this part of the HTTP Access + * Control standard. + * + * @see http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute + * + * @private {boolean} + */ + this.withCredentials_ = false; + + /** + * True if we can use XMLHttpRequest's timeout directly. + * @private {boolean} + */ + this.useXhr2Timeout_ = false; +}; +goog.inherits(goog.net.XhrIo, goog.events.EventTarget); + + +/** + * Response types that may be requested for XMLHttpRequests. + * @enum {string} + * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute + */ +goog.net.XhrIo.ResponseType = { + DEFAULT: '', + TEXT: 'text', + DOCUMENT: 'document', + // Not supported as of Chrome 10.0.612.1 dev + BLOB: 'blob', + ARRAY_BUFFER: 'arraybuffer' +}; /** + * A reference to the XhrIo logger + * @private {goog.debug.Logger} * @const - * @type {number} */ -goog.webgl.DEPTH_BUFFER_BIT = 0x00000100; +goog.net.XhrIo.prototype.logger_ = + goog.log.getLogger('goog.net.XhrIo'); + + +/** + * The Content-Type HTTP header name + * @type {string} + */ +goog.net.XhrIo.CONTENT_TYPE_HEADER = 'Content-Type'; /** - * @const - * @type {number} + * The pattern matching the 'http' and 'https' URI schemes + * @type {!RegExp} */ -goog.webgl.STENCIL_BUFFER_BIT = 0x00000400; +goog.net.XhrIo.HTTP_SCHEME_PATTERN = /^https?$/i; /** - * @const - * @type {number} + * The methods that typically come along with form data. We set different + * headers depending on whether the HTTP action is one of these. */ -goog.webgl.COLOR_BUFFER_BIT = 0x00004000; +goog.net.XhrIo.METHODS_WITH_FORM_DATA = ['POST', 'PUT']; /** - * @const - * @type {number} + * The Content-Type HTTP header value for a url-encoded form + * @type {string} */ -goog.webgl.POINTS = 0x0000; +goog.net.XhrIo.FORM_CONTENT_TYPE = + 'application/x-www-form-urlencoded;charset=utf-8'; /** + * The XMLHttpRequest Level two timeout delay ms property name. + * + * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute + * + * @private {string} * @const - * @type {number} */ -goog.webgl.LINES = 0x0001; +goog.net.XhrIo.XHR2_TIMEOUT_ = 'timeout'; /** + * The XMLHttpRequest Level two ontimeout handler property name. + * + * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute + * + * @private {string} * @const - * @type {number} */ -goog.webgl.LINE_LOOP = 0x0002; +goog.net.XhrIo.XHR2_ON_TIMEOUT_ = 'ontimeout'; /** - * @const - * @type {number} + * All non-disposed instances of goog.net.XhrIo created + * by {@link goog.net.XhrIo.send} are in this Array. + * @see goog.net.XhrIo.cleanup + * @private {!Array<!goog.net.XhrIo>} */ -goog.webgl.LINE_STRIP = 0x0003; +goog.net.XhrIo.sendInstances_ = []; /** - * @const - * @type {number} + * Static send that creates a short lived instance of XhrIo to send the + * request. + * @see goog.net.XhrIo.cleanup + * @param {string|goog.Uri} url Uri to make request to. + * @param {?function(this:goog.net.XhrIo, ?)=} opt_callback Callback function + * for when request is complete. + * @param {string=} opt_method Send method, default: GET. + * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} + * opt_content Body data. + * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the + * request. + * @param {number=} opt_timeoutInterval Number of milliseconds after which an + * incomplete request will be aborted; 0 means no timeout is set. + * @param {boolean=} opt_withCredentials Whether to send credentials with the + * request. Default to false. See {@link goog.net.XhrIo#setWithCredentials}. + * @return {!goog.net.XhrIo} The sent XhrIo. */ -goog.webgl.TRIANGLES = 0x0004; +goog.net.XhrIo.send = function(url, opt_callback, opt_method, opt_content, + opt_headers, opt_timeoutInterval, + opt_withCredentials) { + var x = new goog.net.XhrIo(); + goog.net.XhrIo.sendInstances_.push(x); + if (opt_callback) { + x.listen(goog.net.EventType.COMPLETE, opt_callback); + } + x.listenOnce(goog.net.EventType.READY, x.cleanupSend_); + if (opt_timeoutInterval) { + x.setTimeoutInterval(opt_timeoutInterval); + } + if (opt_withCredentials) { + x.setWithCredentials(opt_withCredentials); + } + x.send(url, opt_method, opt_content, opt_headers); + return x; +}; /** - * @const - * @type {number} + * Disposes all non-disposed instances of goog.net.XhrIo created by + * {@link goog.net.XhrIo.send}. + * {@link goog.net.XhrIo.send} cleans up the goog.net.XhrIo instance + * it creates when the request completes or fails. However, if + * the request never completes, then the goog.net.XhrIo is not disposed. + * This can occur if the window is unloaded before the request completes. + * We could have {@link goog.net.XhrIo.send} return the goog.net.XhrIo + * it creates and make the client of {@link goog.net.XhrIo.send} be + * responsible for disposing it in this case. However, this makes things + * significantly more complicated for the client, and the whole point + * of {@link goog.net.XhrIo.send} is that it's simple and easy to use. + * Clients of {@link goog.net.XhrIo.send} should call + * {@link goog.net.XhrIo.cleanup} when doing final + * cleanup on window unload. */ -goog.webgl.TRIANGLE_STRIP = 0x0005; +goog.net.XhrIo.cleanup = function() { + var instances = goog.net.XhrIo.sendInstances_; + while (instances.length) { + instances.pop().dispose(); + } +}; /** - * @const - * @type {number} + * Installs exception protection for all entry point introduced by + * goog.net.XhrIo instances which are not protected by + * {@link goog.debug.ErrorHandler#protectWindowSetTimeout}, + * {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or + * {@link goog.events.protectBrowserEventEntryPoint}. + * + * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to + * protect the entry point(s). */ -goog.webgl.TRIANGLE_FAN = 0x0006; +goog.net.XhrIo.protectEntryPoints = function(errorHandler) { + goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = + errorHandler.protectEntryPoint( + goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_); +}; /** - * @const - * @type {number} + * Disposes of the specified goog.net.XhrIo created by + * {@link goog.net.XhrIo.send} and removes it from + * {@link goog.net.XhrIo.pendingStaticSendInstances_}. + * @private */ -goog.webgl.ZERO = 0; +goog.net.XhrIo.prototype.cleanupSend_ = function() { + this.dispose(); + goog.array.remove(goog.net.XhrIo.sendInstances_, this); +}; /** - * @const - * @type {number} + * Returns the number of milliseconds after which an incomplete request will be + * aborted, or 0 if no timeout is set. + * @return {number} Timeout interval in milliseconds. */ -goog.webgl.ONE = 1; +goog.net.XhrIo.prototype.getTimeoutInterval = function() { + return this.timeoutInterval_; +}; /** - * @const - * @type {number} + * Sets the number of milliseconds after which an incomplete request will be + * aborted and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no + * timeout is set. + * @param {number} ms Timeout interval in milliseconds; 0 means none. */ -goog.webgl.SRC_COLOR = 0x0300; +goog.net.XhrIo.prototype.setTimeoutInterval = function(ms) { + this.timeoutInterval_ = Math.max(0, ms); +}; /** - * @const - * @type {number} + * Sets the desired type for the response. At time of writing, this is only + * supported in very recent versions of WebKit (10.0.612.1 dev and later). + * + * If this is used, the response may only be accessed via {@link #getResponse}. + * + * @param {goog.net.XhrIo.ResponseType} type The desired type for the response. */ -goog.webgl.ONE_MINUS_SRC_COLOR = 0x0301; +goog.net.XhrIo.prototype.setResponseType = function(type) { + this.responseType_ = type; +}; /** - * @const - * @type {number} + * Gets the desired type for the response. + * @return {goog.net.XhrIo.ResponseType} The desired type for the response. */ -goog.webgl.SRC_ALPHA = 0x0302; +goog.net.XhrIo.prototype.getResponseType = function() { + return this.responseType_; +}; /** - * @const - * @type {number} + * Sets whether a "credentialed" request that is aware of cookie and + * authentication information should be made. This option is only supported by + * browsers that support HTTP Access Control. As of this writing, this option + * is not supported in IE. + * + * @param {boolean} withCredentials Whether this should be a "credentialed" + * request. */ -goog.webgl.ONE_MINUS_SRC_ALPHA = 0x0303; +goog.net.XhrIo.prototype.setWithCredentials = function(withCredentials) { + this.withCredentials_ = withCredentials; +}; /** - * @const - * @type {number} + * Gets whether a "credentialed" request is to be sent. + * @return {boolean} The desired type for the response. */ -goog.webgl.DST_ALPHA = 0x0304; +goog.net.XhrIo.prototype.getWithCredentials = function() { + return this.withCredentials_; +}; /** - * @const - * @type {number} + * Instance send that actually uses XMLHttpRequest to make a server call. + * @param {string|goog.Uri} url Uri to make request to. + * @param {string=} opt_method Send method, default: GET. + * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} + * opt_content Body data. + * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the + * request. */ -goog.webgl.ONE_MINUS_DST_ALPHA = 0x0305; +goog.net.XhrIo.prototype.send = function(url, opt_method, opt_content, + opt_headers) { + if (this.xhr_) { + throw Error('[goog.net.XhrIo] Object is active with another request=' + + this.lastUri_ + '; newUri=' + url); + } + var method = opt_method ? opt_method.toUpperCase() : 'GET'; -/** - * @const - * @type {number} - */ -goog.webgl.DST_COLOR = 0x0306; + this.lastUri_ = url; + this.lastError_ = ''; + this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR; + this.lastMethod_ = method; + this.errorDispatched_ = false; + this.active_ = true; + + // Use the factory to create the XHR object and options + this.xhr_ = this.createXhr(); + this.xhrOptions_ = this.xmlHttpFactory_ ? + this.xmlHttpFactory_.getOptions() : goog.net.XmlHttp.getOptions(); + + // Set up the onreadystatechange callback + this.xhr_.onreadystatechange = goog.bind(this.onReadyStateChange_, this); + + /** + * Try to open the XMLHttpRequest (always async), if an error occurs here it + * is generally permission denied + * @preserveTry + */ + try { + goog.log.fine(this.logger_, this.formatMsg_('Opening Xhr')); + this.inOpen_ = true; + this.xhr_.open(method, String(url), true); // Always async! + this.inOpen_ = false; + } catch (err) { + goog.log.fine(this.logger_, + this.formatMsg_('Error opening Xhr: ' + err.message)); + this.error_(goog.net.ErrorCode.EXCEPTION, err); + return; + } + + // We can't use null since this won't allow requests with form data to have a + // content length specified which will cause some proxies to return a 411 + // error. + var content = opt_content || ''; + + var headers = this.headers.clone(); + + // Add headers specific to this request + if (opt_headers) { + goog.structs.forEach(opt_headers, function(value, key) { + headers.set(key, value); + }); + } + + // Find whether a content type header is set, ignoring case. + // HTTP header names are case-insensitive. See: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + var contentTypeKey = goog.array.find(headers.getKeys(), + goog.net.XhrIo.isContentTypeHeader_); + + var contentIsFormData = (goog.global['FormData'] && + (content instanceof goog.global['FormData'])); + if (goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) && + !contentTypeKey && !contentIsFormData) { + // For requests typically with form data, default to the url-encoded form + // content type unless this is a FormData request. For FormData, + // the browser will automatically add a multipart/form-data content type + // with an appropriate multipart boundary. + headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER, + goog.net.XhrIo.FORM_CONTENT_TYPE); + } + + // Add the headers to the Xhr object + headers.forEach(function(value, key) { + this.xhr_.setRequestHeader(key, value); + }, this); + + if (this.responseType_) { + this.xhr_.responseType = this.responseType_; + } + + if (goog.object.containsKey(this.xhr_, 'withCredentials')) { + this.xhr_.withCredentials = this.withCredentials_; + } + + /** + * Try to send the request, or other wise report an error (404 not found). + * @preserveTry + */ + try { + this.cleanUpTimeoutTimer_(); // Paranoid, should never be running. + if (this.timeoutInterval_ > 0) { + this.useXhr2Timeout_ = goog.net.XhrIo.shouldUseXhr2Timeout_(this.xhr_); + goog.log.fine(this.logger_, this.formatMsg_('Will abort after ' + + this.timeoutInterval_ + 'ms if incomplete, xhr2 ' + + this.useXhr2Timeout_)); + if (this.useXhr2Timeout_) { + this.xhr_[goog.net.XhrIo.XHR2_TIMEOUT_] = this.timeoutInterval_; + this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = + goog.bind(this.timeout_, this); + } else { + this.timeoutId_ = goog.Timer.callOnce(this.timeout_, + this.timeoutInterval_, this); + } + } + goog.log.fine(this.logger_, this.formatMsg_('Sending request')); + this.inSend_ = true; + this.xhr_.send(content); + this.inSend_ = false; + + } catch (err) { + goog.log.fine(this.logger_, this.formatMsg_('Send error: ' + err.message)); + this.error_(goog.net.ErrorCode.EXCEPTION, err); + } +}; /** - * @const - * @type {number} + * Determines if the argument is an XMLHttpRequest that supports the level 2 + * timeout value and event. + * + * Currently, FF 21.0 OS X has the fields but won't actually call the timeout + * handler. Perhaps the confusion in the bug referenced below hasn't + * entirely been resolved. + * + * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816 + * + * @param {!goog.net.XhrLike.OrNative} xhr The request. + * @return {boolean} True if the request supports level 2 timeout. + * @private */ -goog.webgl.ONE_MINUS_DST_COLOR = 0x0307; +goog.net.XhrIo.shouldUseXhr2Timeout_ = function(xhr) { + return goog.userAgent.IE && + goog.userAgent.isVersionOrHigher(9) && + goog.isNumber(xhr[goog.net.XhrIo.XHR2_TIMEOUT_]) && + goog.isDef(xhr[goog.net.XhrIo.XHR2_ON_TIMEOUT_]); +}; /** - * @const - * @type {number} + * @param {string} header An HTTP header key. + * @return {boolean} Whether the key is a content type header (ignoring + * case. + * @private */ -goog.webgl.SRC_ALPHA_SATURATE = 0x0308; +goog.net.XhrIo.isContentTypeHeader_ = function(header) { + return goog.string.caseInsensitiveEquals( + goog.net.XhrIo.CONTENT_TYPE_HEADER, header); +}; /** - * @const - * @type {number} + * Creates a new XHR object. + * @return {!goog.net.XhrLike.OrNative} The newly created XHR object. + * @protected */ -goog.webgl.FUNC_ADD = 0x8006; +goog.net.XhrIo.prototype.createXhr = function() { + return this.xmlHttpFactory_ ? + this.xmlHttpFactory_.createInstance() : goog.net.XmlHttp(); +}; /** - * @const - * @type {number} + * The request didn't complete after {@link goog.net.XhrIo#timeoutInterval_} + * milliseconds; raises a {@link goog.net.EventType.TIMEOUT} event and aborts + * the request. + * @private */ -goog.webgl.BLEND_EQUATION = 0x8009; +goog.net.XhrIo.prototype.timeout_ = function() { + if (typeof goog == 'undefined') { + // If goog is undefined then the callback has occurred as the application + // is unloading and will error. Thus we let it silently fail. + } else if (this.xhr_) { + this.lastError_ = 'Timed out after ' + this.timeoutInterval_ + + 'ms, aborting'; + this.lastErrorCode_ = goog.net.ErrorCode.TIMEOUT; + goog.log.fine(this.logger_, this.formatMsg_(this.lastError_)); + this.dispatchEvent(goog.net.EventType.TIMEOUT); + this.abort(goog.net.ErrorCode.TIMEOUT); + } +}; /** - * Same as BLEND_EQUATION - * @const - * @type {number} + * Something errorred, so inactivate, fire error callback and clean up + * @param {goog.net.ErrorCode} errorCode The error code. + * @param {Error} err The error object. + * @private */ -goog.webgl.BLEND_EQUATION_RGB = 0x8009; +goog.net.XhrIo.prototype.error_ = function(errorCode, err) { + this.active_ = false; + if (this.xhr_) { + this.inAbort_ = true; + this.xhr_.abort(); // Ensures XHR isn't hung (FF) + this.inAbort_ = false; + } + this.lastError_ = err; + this.lastErrorCode_ = errorCode; + this.dispatchErrors_(); + this.cleanUpXhr_(); +}; /** - * @const - * @type {number} + * Dispatches COMPLETE and ERROR in case of an error. This ensures that we do + * not dispatch multiple error events. + * @private */ -goog.webgl.BLEND_EQUATION_ALPHA = 0x883D; +goog.net.XhrIo.prototype.dispatchErrors_ = function() { + if (!this.errorDispatched_) { + this.errorDispatched_ = true; + this.dispatchEvent(goog.net.EventType.COMPLETE); + this.dispatchEvent(goog.net.EventType.ERROR); + } +}; /** - * @const - * @type {number} + * Abort the current XMLHttpRequest + * @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use - + * defaults to ABORT. */ -goog.webgl.FUNC_SUBTRACT = 0x800A; +goog.net.XhrIo.prototype.abort = function(opt_failureCode) { + if (this.xhr_ && this.active_) { + goog.log.fine(this.logger_, this.formatMsg_('Aborting')); + this.active_ = false; + this.inAbort_ = true; + this.xhr_.abort(); + this.inAbort_ = false; + this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT; + this.dispatchEvent(goog.net.EventType.COMPLETE); + this.dispatchEvent(goog.net.EventType.ABORT); + this.cleanUpXhr_(); + } +}; /** - * @const - * @type {number} + * Nullifies all callbacks to reduce risks of leaks. + * @override + * @protected */ -goog.webgl.FUNC_REVERSE_SUBTRACT = 0x800B; +goog.net.XhrIo.prototype.disposeInternal = function() { + if (this.xhr_) { + // We explicitly do not call xhr_.abort() unless active_ is still true. + // This is to avoid unnecessarily aborting a successful request when + // dispose() is called in a callback triggered by a complete response, but + // in which browser cleanup has not yet finished. + // (See http://b/issue?id=1684217.) + if (this.active_) { + this.active_ = false; + this.inAbort_ = true; + this.xhr_.abort(); + this.inAbort_ = false; + } + this.cleanUpXhr_(true); + } + + goog.net.XhrIo.base(this, 'disposeInternal'); +}; /** - * @const - * @type {number} + * Internal handler for the XHR object's readystatechange event. This method + * checks the status and the readystate and fires the correct callbacks. + * If the request has ended, the handlers are cleaned up and the XHR object is + * nullified. + * @private */ -goog.webgl.BLEND_DST_RGB = 0x80C8; +goog.net.XhrIo.prototype.onReadyStateChange_ = function() { + if (this.isDisposed()) { + // This method is the target of an untracked goog.Timer.callOnce(). + return; + } + if (!this.inOpen_ && !this.inSend_ && !this.inAbort_) { + // Were not being called from within a call to this.xhr_.send + // this.xhr_.abort, or this.xhr_.open, so this is an entry point + this.onReadyStateChangeEntryPoint_(); + } else { + this.onReadyStateChangeHelper_(); + } +}; /** - * @const - * @type {number} + * Used to protect the onreadystatechange handler entry point. Necessary + * as {#onReadyStateChange_} maybe called from within send or abort, this + * method is only called when {#onReadyStateChange_} is called as an + * entry point. + * {@see #protectEntryPoints} + * @private */ -goog.webgl.BLEND_SRC_RGB = 0x80C9; +goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = function() { + this.onReadyStateChangeHelper_(); +}; /** - * @const - * @type {number} + * Helper for {@link #onReadyStateChange_}. This is used so that + * entry point calls to {@link #onReadyStateChange_} can be routed through + * {@link #onReadyStateChangeEntryPoint_}. + * @private */ -goog.webgl.BLEND_DST_ALPHA = 0x80CA; +goog.net.XhrIo.prototype.onReadyStateChangeHelper_ = function() { + if (!this.active_) { + // can get called inside abort call + return; + } + + if (typeof goog == 'undefined') { + // NOTE(user): If goog is undefined then the callback has occurred as the + // application is unloading and will error. Thus we let it silently fail. + + } else if ( + this.xhrOptions_[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] && + this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE && + this.getStatus() == 2) { + // NOTE(user): In IE if send() errors on a *local* request the readystate + // is still changed to COMPLETE. We need to ignore it and allow the + // try/catch around send() to pick up the error. + goog.log.fine(this.logger_, this.formatMsg_( + 'Local request error detected and ignored')); + } else { -/** - * @const - * @type {number} - */ -goog.webgl.BLEND_SRC_ALPHA = 0x80CB; + // In IE when the response has been cached we sometimes get the callback + // from inside the send call and this usually breaks code that assumes that + // XhrIo is asynchronous. If that is the case we delay the callback + // using a timer. + if (this.inSend_ && + this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE) { + goog.Timer.callOnce(this.onReadyStateChange_, 0, this); + return; + } + this.dispatchEvent(goog.net.EventType.READY_STATE_CHANGE); -/** - * @const - * @type {number} - */ -goog.webgl.CONSTANT_COLOR = 0x8001; + // readyState indicates the transfer has finished + if (this.isComplete()) { + goog.log.fine(this.logger_, this.formatMsg_('Request complete')); + this.active_ = false; -/** - * @const - * @type {number} - */ -goog.webgl.ONE_MINUS_CONSTANT_COLOR = 0x8002; + try { + // Call the specific callbacks for success or failure. Only call the + // success if the status is 200 (HTTP_OK) or 304 (HTTP_CACHED) + if (this.isSuccess()) { + this.dispatchEvent(goog.net.EventType.COMPLETE); + this.dispatchEvent(goog.net.EventType.SUCCESS); + } else { + this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR; + this.lastError_ = + this.getStatusText() + ' [' + this.getStatus() + ']'; + this.dispatchErrors_(); + } + } finally { + this.cleanUpXhr_(); + } + } + } +}; /** - * @const - * @type {number} + * Remove the listener to protect against leaks, and nullify the XMLHttpRequest + * object. + * @param {boolean=} opt_fromDispose If this is from the dispose (don't want to + * fire any events). + * @private */ -goog.webgl.CONSTANT_ALPHA = 0x8003; - +goog.net.XhrIo.prototype.cleanUpXhr_ = function(opt_fromDispose) { + if (this.xhr_) { + // Cancel any pending timeout event handler. + this.cleanUpTimeoutTimer_(); -/** - * @const - * @type {number} - */ -goog.webgl.ONE_MINUS_CONSTANT_ALPHA = 0x8004; + // Save reference so we can mark it as closed after the READY event. The + // READY event may trigger another request, thus we must nullify this.xhr_ + var xhr = this.xhr_; + var clearedOnReadyStateChange = + this.xhrOptions_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] ? + goog.nullFunction : null; + this.xhr_ = null; + this.xhrOptions_ = null; + if (!opt_fromDispose) { + this.dispatchEvent(goog.net.EventType.READY); + } -/** - * @const - * @type {number} - */ -goog.webgl.BLEND_COLOR = 0x8005; + try { + // NOTE(user): Not nullifying in FireFox can still leak if the callbacks + // are defined in the same scope as the instance of XhrIo. But, IE doesn't + // allow you to set the onreadystatechange to NULL so nullFunction is + // used. + xhr.onreadystatechange = clearedOnReadyStateChange; + } catch (e) { + // This seems to occur with a Gears HTTP request. Delayed the setting of + // this onreadystatechange until after READY is sent out and catching the + // error to see if we can track down the problem. + goog.log.error(this.logger_, + 'Problem encountered resetting onreadystatechange: ' + e.message); + } + } +}; /** - * @const - * @type {number} + * Make sure the timeout timer isn't running. + * @private */ -goog.webgl.ARRAY_BUFFER = 0x8892; +goog.net.XhrIo.prototype.cleanUpTimeoutTimer_ = function() { + if (this.xhr_ && this.useXhr2Timeout_) { + this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = null; + } + if (goog.isNumber(this.timeoutId_)) { + goog.Timer.clear(this.timeoutId_); + this.timeoutId_ = null; + } +}; /** - * @const - * @type {number} + * @return {boolean} Whether there is an active request. */ -goog.webgl.ELEMENT_ARRAY_BUFFER = 0x8893; +goog.net.XhrIo.prototype.isActive = function() { + return !!this.xhr_; +}; /** - * @const - * @type {number} + * @return {boolean} Whether the request has completed. */ -goog.webgl.ARRAY_BUFFER_BINDING = 0x8894; +goog.net.XhrIo.prototype.isComplete = function() { + return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE; +}; /** - * @const - * @type {number} + * @return {boolean} Whether the request completed with a success. */ -goog.webgl.ELEMENT_ARRAY_BUFFER_BINDING = 0x8895; +goog.net.XhrIo.prototype.isSuccess = function() { + var status = this.getStatus(); + // A zero status code is considered successful for local files. + return goog.net.HttpStatus.isSuccess(status) || + status === 0 && !this.isLastUriEffectiveSchemeHttp_(); +}; /** - * @const - * @type {number} + * @return {boolean} whether the effective scheme of the last URI that was + * fetched was 'http' or 'https'. + * @private */ -goog.webgl.STREAM_DRAW = 0x88E0; +goog.net.XhrIo.prototype.isLastUriEffectiveSchemeHttp_ = function() { + var scheme = goog.uri.utils.getEffectiveScheme(String(this.lastUri_)); + return goog.net.XhrIo.HTTP_SCHEME_PATTERN.test(scheme); +}; /** - * @const - * @type {number} + * Get the readystate from the Xhr object + * Will only return correct result when called from the context of a callback + * @return {goog.net.XmlHttp.ReadyState} goog.net.XmlHttp.ReadyState.*. */ -goog.webgl.STATIC_DRAW = 0x88E4; +goog.net.XhrIo.prototype.getReadyState = function() { + return this.xhr_ ? + /** @type {goog.net.XmlHttp.ReadyState} */ (this.xhr_.readyState) : + goog.net.XmlHttp.ReadyState.UNINITIALIZED; +}; /** - * @const - * @type {number} + * Get the status from the Xhr object + * Will only return correct result when called from the context of a callback + * @return {number} Http status. */ -goog.webgl.DYNAMIC_DRAW = 0x88E8; +goog.net.XhrIo.prototype.getStatus = function() { + /** + * IE doesn't like you checking status until the readystate is greater than 2 + * (i.e. it is receiving or complete). The try/catch is used for when the + * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_. + * @preserveTry + */ + try { + return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? + this.xhr_.status : -1; + } catch (e) { + return -1; + } +}; /** - * @const - * @type {number} + * Get the status text from the Xhr object + * Will only return correct result when called from the context of a callback + * @return {string} Status text. */ -goog.webgl.BUFFER_SIZE = 0x8764; +goog.net.XhrIo.prototype.getStatusText = function() { + /** + * IE doesn't like you checking status until the readystate is greater than 2 + * (i.e. it is recieving or complete). The try/catch is used for when the + * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_. + * @preserveTry + */ + try { + return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? + this.xhr_.statusText : ''; + } catch (e) { + goog.log.fine(this.logger_, 'Can not get status: ' + e.message); + return ''; + } +}; /** - * @const - * @type {number} + * Get the last Uri that was requested + * @return {string} Last Uri. */ -goog.webgl.BUFFER_USAGE = 0x8765; +goog.net.XhrIo.prototype.getLastUri = function() { + return String(this.lastUri_); +}; /** - * @const - * @type {number} + * Get the response text from the Xhr object + * Will only return correct result when called from the context of a callback. + * @return {string} Result from the server, or '' if no result available. */ -goog.webgl.CURRENT_VERTEX_ATTRIB = 0x8626; +goog.net.XhrIo.prototype.getResponseText = function() { + /** @preserveTry */ + try { + return this.xhr_ ? this.xhr_.responseText : ''; + } catch (e) { + // http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute + // states that responseText should return '' (and responseXML null) + // when the state is not LOADING or DONE. Instead, IE can + // throw unexpected exceptions, for example when a request is aborted + // or no data is available yet. + goog.log.fine(this.logger_, 'Can not get responseText: ' + e.message); + return ''; + } +}; /** - * @const - * @type {number} + * Get the response body from the Xhr object. This property is only available + * in IE since version 7 according to MSDN: + * http://msdn.microsoft.com/en-us/library/ie/ms534368(v=vs.85).aspx + * Will only return correct result when called from the context of a callback. + * + * One option is to construct a VBArray from the returned object and convert + * it to a JavaScript array using the toArray method: + * {@code (new window['VBArray'](xhrIo.getResponseBody())).toArray()} + * This will result in an array of numbers in the range of [0..255] + * + * Another option is to use the VBScript CStr method to convert it into a + * string as outlined in http://stackoverflow.com/questions/1919972 + * + * @return {Object} Binary result from the server or null if not available. */ -goog.webgl.FRONT = 0x0404; +goog.net.XhrIo.prototype.getResponseBody = function() { + /** @preserveTry */ + try { + if (this.xhr_ && 'responseBody' in this.xhr_) { + return this.xhr_['responseBody']; + } + } catch (e) { + // IE can throw unexpected exceptions, for example when a request is aborted + // or no data is yet available. + goog.log.fine(this.logger_, 'Can not get responseBody: ' + e.message); + } + return null; +}; /** - * @const - * @type {number} + * Get the response XML from the Xhr object + * Will only return correct result when called from the context of a callback. + * @return {Document} The DOM Document representing the XML file, or null + * if no result available. */ -goog.webgl.BACK = 0x0405; +goog.net.XhrIo.prototype.getResponseXml = function() { + /** @preserveTry */ + try { + return this.xhr_ ? this.xhr_.responseXML : null; + } catch (e) { + goog.log.fine(this.logger_, 'Can not get responseXML: ' + e.message); + return null; + } +}; /** - * @const - * @type {number} + * Get the response and evaluates it as JSON from the Xhr object + * Will only return correct result when called from the context of a callback + * @param {string=} opt_xssiPrefix Optional XSSI prefix string to use for + * stripping of the response before parsing. This needs to be set only if + * your backend server prepends the same prefix string to the JSON response. + * @return {Object|undefined} JavaScript object. */ -goog.webgl.FRONT_AND_BACK = 0x0408; +goog.net.XhrIo.prototype.getResponseJson = function(opt_xssiPrefix) { + if (!this.xhr_) { + return undefined; + } + var responseText = this.xhr_.responseText; + if (opt_xssiPrefix && responseText.indexOf(opt_xssiPrefix) == 0) { + responseText = responseText.substring(opt_xssiPrefix.length); + } -/** - * @const - * @type {number} - */ -goog.webgl.CULL_FACE = 0x0B44; + return goog.json.parse(responseText); +}; /** - * @const - * @type {number} + * Get the response as the type specificed by {@link #setResponseType}. At time + * of writing, this is only directly supported in very recent versions of WebKit + * (10.0.612.1 dev and later). If the field is not supported directly, we will + * try to emulate it. + * + * Emulating the response means following the rules laid out at + * http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute + * + * On browsers with no support for this (Chrome < 10, Firefox < 4, etc), only + * response types of DEFAULT or TEXT may be used, and the response returned will + * be the text response. + * + * On browsers with Mozilla's draft support for array buffers (Firefox 4, 5), + * only response types of DEFAULT, TEXT, and ARRAY_BUFFER may be used, and the + * response returned will be either the text response or the Mozilla + * implementation of the array buffer response. + * + * On browsers will full support, any valid response type supported by the + * browser may be used, and the response provided by the browser will be + * returned. + * + * @return {*} The response. */ -goog.webgl.BLEND = 0x0BE2; +goog.net.XhrIo.prototype.getResponse = function() { + /** @preserveTry */ + try { + if (!this.xhr_) { + return null; + } + if ('response' in this.xhr_) { + return this.xhr_.response; + } + switch (this.responseType_) { + case goog.net.XhrIo.ResponseType.DEFAULT: + case goog.net.XhrIo.ResponseType.TEXT: + return this.xhr_.responseText; + // DOCUMENT and BLOB don't need to be handled here because they are + // introduced in the same spec that adds the .response field, and would + // have been caught above. + // ARRAY_BUFFER needs an implementation for Firefox 4, where it was + // implemented using a draft spec rather than the final spec. + case goog.net.XhrIo.ResponseType.ARRAY_BUFFER: + if ('mozResponseArrayBuffer' in this.xhr_) { + return this.xhr_.mozResponseArrayBuffer; + } + } + // Fell through to a response type that is not supported on this browser. + goog.log.error(this.logger_, + 'Response type ' + this.responseType_ + ' is not ' + + 'supported on this browser'); + return null; + } catch (e) { + goog.log.fine(this.logger_, 'Can not get response: ' + e.message); + return null; + } +}; /** - * @const - * @type {number} + * Get the value of the response-header with the given name from the Xhr object + * Will only return correct result when called from the context of a callback + * and the request has completed + * @param {string} key The name of the response-header to retrieve. + * @return {string|undefined} The value of the response-header named key. */ -goog.webgl.DITHER = 0x0BD0; +goog.net.XhrIo.prototype.getResponseHeader = function(key) { + return this.xhr_ && this.isComplete() ? + this.xhr_.getResponseHeader(key) : undefined; +}; /** - * @const - * @type {number} + * Gets the text of all the headers in the response. + * Will only return correct result when called from the context of a callback + * and the request has completed. + * @return {string} The value of the response headers or empty string. */ -goog.webgl.STENCIL_TEST = 0x0B90; +goog.net.XhrIo.prototype.getAllResponseHeaders = function() { + return this.xhr_ && this.isComplete() ? + this.xhr_.getAllResponseHeaders() : ''; +}; /** - * @const - * @type {number} + * Returns all response headers as a key-value map. + * Multiple values for the same header key can be combined into one, + * separated by a comma and a space. + * Note that the native getResponseHeader method for retrieving a single header + * does a case insensitive match on the header name. This method does not + * include any case normalization logic, it will just return a key-value + * representation of the headers. + * See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method + * @return {!Object<string, string>} An object with the header keys as keys + * and header values as values. */ -goog.webgl.DEPTH_TEST = 0x0B71; +goog.net.XhrIo.prototype.getResponseHeaders = function() { + var headersObject = {}; + var headersArray = this.getAllResponseHeaders().split('\r\n'); + for (var i = 0; i < headersArray.length; i++) { + if (goog.string.isEmptyOrWhitespace(headersArray[i])) { + continue; + } + var keyValue = goog.string.splitLimit(headersArray[i], ': ', 2); + if (headersObject[keyValue[0]]) { + headersObject[keyValue[0]] += ', ' + keyValue[1]; + } else { + headersObject[keyValue[0]] = keyValue[1]; + } + } + return headersObject; +}; /** - * @const - * @type {number} + * Get the last error message + * @return {goog.net.ErrorCode} Last error code. */ -goog.webgl.SCISSOR_TEST = 0x0C11; +goog.net.XhrIo.prototype.getLastErrorCode = function() { + return this.lastErrorCode_; +}; /** - * @const - * @type {number} + * Get the last error message + * @return {string} Last error message. */ -goog.webgl.POLYGON_OFFSET_FILL = 0x8037; +goog.net.XhrIo.prototype.getLastError = function() { + return goog.isString(this.lastError_) ? this.lastError_ : + String(this.lastError_); +}; /** - * @const - * @type {number} + * Adds the last method, status and URI to the message. This is used to add + * this information to the logging calls. + * @param {string} msg The message text that we want to add the extra text to. + * @return {string} The message with the extra text appended. + * @private */ -goog.webgl.SAMPLE_ALPHA_TO_COVERAGE = 0x809E; - +goog.net.XhrIo.prototype.formatMsg_ = function(msg) { + return msg + ' [' + this.lastMethod_ + ' ' + this.lastUri_ + ' ' + + this.getStatus() + ']'; +}; -/** - * @const - * @type {number} - */ -goog.webgl.SAMPLE_COVERAGE = 0x80A0; +// Register the xhr handler as an entry point, so that +// it can be monitored for exception handling, etc. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = + transformer(goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_); + }); -/** - * @const - * @type {number} - */ -goog.webgl.NO_ERROR = 0; +goog.provide('ol.format.FormatType'); /** - * @const - * @type {number} + * @enum {string} */ -goog.webgl.INVALID_ENUM = 0x0500; +ol.format.FormatType = { + JSON: 'json', + TEXT: 'text', + XML: 'xml' +}; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @const - * @type {number} + * @fileoverview + * XML utilities. + * */ -goog.webgl.INVALID_VALUE = 0x0501; +goog.provide('goog.dom.xml'); -/** - * @const - * @type {number} - */ -goog.webgl.INVALID_OPERATION = 0x0502; +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); /** - * @const + * Max XML size for MSXML2. Used to prevent potential DoS attacks. * @type {number} */ -goog.webgl.OUT_OF_MEMORY = 0x0505; +goog.dom.xml.MAX_XML_SIZE_KB = 2 * 1024; // In kB /** - * @const + * Max XML size for MSXML2. Used to prevent potential DoS attacks. * @type {number} */ -goog.webgl.CW = 0x0900; +goog.dom.xml.MAX_ELEMENT_DEPTH = 256; // Same default as MSXML6. /** - * @const - * @type {number} + * Creates an XML document appropriate for the current JS runtime + * @param {string=} opt_rootTagName The root tag name. + * @param {string=} opt_namespaceUri Namespace URI of the document element. + * @return {Document} The new document. */ -goog.webgl.CCW = 0x0901; +goog.dom.xml.createDocument = function(opt_rootTagName, opt_namespaceUri) { + if (opt_namespaceUri && !opt_rootTagName) { + throw Error("Can't create document with namespace and no root tag"); + } + if (document.implementation && document.implementation.createDocument) { + return document.implementation.createDocument(opt_namespaceUri || '', + opt_rootTagName || '', + null); + } else if (typeof ActiveXObject != 'undefined') { + var doc = goog.dom.xml.createMsXmlDocument_(); + if (doc) { + if (opt_rootTagName) { + doc.appendChild(doc.createNode(goog.dom.NodeType.ELEMENT, + opt_rootTagName, + opt_namespaceUri || '')); + } + return doc; + } + } + throw Error('Your browser does not support creating new documents'); +}; /** - * @const - * @type {number} + * Creates an XML document from a string + * @param {string} xml The text. + * @return {Document} XML document from the text. */ -goog.webgl.LINE_WIDTH = 0x0B21; +goog.dom.xml.loadXml = function(xml) { + if (typeof DOMParser != 'undefined') { + return new DOMParser().parseFromString(xml, 'application/xml'); + } else if (typeof ActiveXObject != 'undefined') { + var doc = goog.dom.xml.createMsXmlDocument_(); + doc.loadXML(xml); + return doc; + } + throw Error('Your browser does not support loading xml documents'); +}; /** - * @const - * @type {number} + * Serializes an XML document or subtree to string. + * @param {Document|Element} xml The document or the root node of the subtree. + * @return {string} The serialized XML. */ -goog.webgl.ALIASED_POINT_SIZE_RANGE = 0x846D; +goog.dom.xml.serialize = function(xml) { + // Compatible with Firefox, Opera and WebKit. + if (typeof XMLSerializer != 'undefined') { + return new XMLSerializer().serializeToString(xml); + } + // Compatible with Internet Explorer. + var text = xml.xml; + if (text) { + return text; + } + throw Error('Your browser does not support serializing XML documents'); +}; /** - * @const - * @type {number} + * Selects a single node using an Xpath expression and a root node + * @param {Node} node The root node. + * @param {string} path Xpath selector. + * @return {Node} The selected node, or null if no matching node. */ -goog.webgl.ALIASED_LINE_WIDTH_RANGE = 0x846E; +goog.dom.xml.selectSingleNode = function(node, path) { + if (typeof node.selectSingleNode != 'undefined') { + var doc = goog.dom.getOwnerDocument(node); + if (typeof doc.setProperty != 'undefined') { + doc.setProperty('SelectionLanguage', 'XPath'); + } + return node.selectSingleNode(path); + } else if (document.implementation.hasFeature('XPath', '3.0')) { + var doc = goog.dom.getOwnerDocument(node); + var resolver = doc.createNSResolver(doc.documentElement); + var result = doc.evaluate(path, node, resolver, + XPathResult.FIRST_ORDERED_NODE_TYPE, null); + return result.singleNodeValue; + } + return null; +}; /** - * @const - * @type {number} + * Selects multiple nodes using an Xpath expression and a root node + * @param {Node} node The root node. + * @param {string} path Xpath selector. + * @return {(NodeList|Array<Node>)} The selected nodes, or empty array if no + * matching nodes. */ -goog.webgl.CULL_FACE_MODE = 0x0B45; +goog.dom.xml.selectNodes = function(node, path) { + if (typeof node.selectNodes != 'undefined') { + var doc = goog.dom.getOwnerDocument(node); + if (typeof doc.setProperty != 'undefined') { + doc.setProperty('SelectionLanguage', 'XPath'); + } + return node.selectNodes(path); + } else if (document.implementation.hasFeature('XPath', '3.0')) { + var doc = goog.dom.getOwnerDocument(node); + var resolver = doc.createNSResolver(doc.documentElement); + var nodes = doc.evaluate(path, node, resolver, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + var results = []; + var count = nodes.snapshotLength; + for (var i = 0; i < count; i++) { + results.push(nodes.snapshotItem(i)); + } + return results; + } else { + return []; + } +}; /** - * @const - * @type {number} + * Sets multiple attributes on an element. Differs from goog.dom.setProperties + * in that it exclusively uses the element's setAttributes method. Use this + * when you need to ensure that the exact property is available as an attribute + * and can be read later by the native getAttribute method. + * @param {!Element} element XML or DOM element to set attributes on. + * @param {!Object<string, string>} attributes Map of property:value pairs. */ -goog.webgl.FRONT_FACE = 0x0B46; +goog.dom.xml.setAttributes = function(element, attributes) { + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + element.setAttribute(key, attributes[key]); + } + } +}; /** - * @const - * @type {number} + * Creates an instance of the MSXML2.DOMDocument. + * @return {Document} The new document. + * @private */ -goog.webgl.DEPTH_RANGE = 0x0B70; +goog.dom.xml.createMsXmlDocument_ = function() { + var doc = new ActiveXObject('MSXML2.DOMDocument'); + if (doc) { + // Prevent potential vulnerabilities exposed by MSXML2, see + // http://b/1707300 and http://wiki/Main/ISETeamXMLAttacks for details. + doc.resolveExternals = false; + doc.validateOnParse = false; + // Add a try catch block because accessing these properties will throw an + // error on unsupported MSXML versions. This affects Windows machines + // running IE6 or IE7 that are on XP SP2 or earlier without MSXML updates. + // See http://msdn.microsoft.com/en-us/library/ms766391(VS.85).aspx for + // specific details on which MSXML versions support these properties. + try { + doc.setProperty('ProhibitDTD', true); + doc.setProperty('MaxXMLSize', goog.dom.xml.MAX_XML_SIZE_KB); + doc.setProperty('MaxElementDepth', goog.dom.xml.MAX_ELEMENT_DEPTH); + } catch (e) { + // No-op. + } + } + return doc; +}; +// FIXME Remove ol.xml.makeParsersNS, and use ol.xml.makeStructureNS instead. -/** - * @const - * @type {number} - */ -goog.webgl.DEPTH_WRITEMASK = 0x0B72; +goog.provide('ol.xml'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.xml'); +goog.require('goog.object'); +goog.require('goog.userAgent'); /** - * @const - * @type {number} + * When using {@link ol.xml.makeChildAppender} or + * {@link ol.xml.makeSimpleNodeFactory}, the top `objectStack` item needs to + * have this structure. + * @typedef {{node:Node}} */ -goog.webgl.DEPTH_CLEAR_VALUE = 0x0B73; +ol.xml.NodeStackItem; /** - * @const - * @type {number} + * @typedef {function(Node, Array.<*>)} */ -goog.webgl.DEPTH_FUNC = 0x0B74; +ol.xml.Parser; /** - * @const - * @type {number} + * @typedef {function(Node, *, Array.<*>)} */ -goog.webgl.STENCIL_CLEAR_VALUE = 0x0B91; +ol.xml.Serializer; /** + * This document should be used when creating nodes for XML serializations. This + * document is also used by {@link ol.xml.createElementNS} and + * {@link ol.xml.setAttributeNS} * @const - * @type {number} + * @type {Document} */ -goog.webgl.STENCIL_FUNC = 0x0B92; +ol.xml.DOCUMENT = goog.dom.xml.createDocument(); /** - * @const - * @type {number} + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + * @private */ -goog.webgl.STENCIL_FAIL = 0x0B94; +ol.xml.createElementNS_ = function(namespaceURI, qualifiedName) { + return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName); +}; /** - * @const - * @type {number} + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. + * @private */ -goog.webgl.STENCIL_PASS_DEPTH_FAIL = 0x0B95; +ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) { + if (goog.isNull(namespaceURI)) { + namespaceURI = ''; + } + return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI); +}; /** - * @const - * @type {number} + * @param {string} namespaceURI Namespace URI. + * @param {string} qualifiedName Qualified name. + * @return {Node} Node. */ -goog.webgl.STENCIL_PASS_DEPTH_PASS = 0x0B96; +ol.xml.createElementNS = + (document.implementation && document.implementation.createDocument) ? + ol.xml.createElementNS_ : ol.xml.createElementNSActiveX_; /** - * @const - * @type {number} + * Recursively grab all text content of child nodes into a single string. + * @param {Node} node Node. + * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line + * breaks. + * @return {string} All text content. + * @api */ -goog.webgl.STENCIL_REF = 0x0B97; +ol.xml.getAllTextContent = function(node, normalizeWhitespace) { + return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join(''); +}; /** - * @const - * @type {number} + * Recursively grab all text content of child nodes into a single string. + * @param {Node} node Node. + * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line + * breaks. + * @param {Array.<String|string>} accumulator Accumulator. + * @private + * @return {Array.<String|string>} Accumulator. */ -goog.webgl.STENCIL_VALUE_MASK = 0x0B93; +ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) { + if (node.nodeType == goog.dom.NodeType.CDATA_SECTION || + node.nodeType == goog.dom.NodeType.TEXT) { + if (normalizeWhitespace) { + // FIXME understand why goog.dom.getTextContent_ uses String here + accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); + } else { + accumulator.push(node.nodeValue); + } + } else { + var n; + for (n = node.firstChild; !goog.isNull(n); n = n.nextSibling) { + ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator); + } + } + return accumulator; +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @private + * @return {string} Local name. */ -goog.webgl.STENCIL_WRITEMASK = 0x0B98; +ol.xml.getLocalName_ = function(node) { + return node.localName; +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @private + * @return {string} Local name. */ -goog.webgl.STENCIL_BACK_FUNC = 0x8800; +ol.xml.getLocalNameIE_ = function(node) { + var localName = node.localName; + if (goog.isDef(localName)) { + return localName; + } + var baseName = node.baseName; + goog.asserts.assert(goog.isDefAndNotNull(baseName), + 'Failed to get localName/baseName of node %s', node); + return baseName; +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @return {string} Local name. */ -goog.webgl.STENCIL_BACK_FAIL = 0x8801; +ol.xml.getLocalName = goog.userAgent.IE ? + ol.xml.getLocalNameIE_ : ol.xml.getLocalName_; /** - * @const - * @type {number} + * @param {?} value Value. + * @private + * @return {boolean} Is document. */ -goog.webgl.STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802; +ol.xml.isDocument_ = function(value) { + return value instanceof Document; +}; /** - * @const - * @type {number} + * @param {?} value Value. + * @private + * @return {boolean} Is document. */ -goog.webgl.STENCIL_BACK_PASS_DEPTH_PASS = 0x8803; +ol.xml.isDocumentIE_ = function(value) { + return goog.isObject(value) && value.nodeType == goog.dom.NodeType.DOCUMENT; +}; /** - * @const - * @type {number} + * @param {?} value Value. + * @return {boolean} Is document. */ -goog.webgl.STENCIL_BACK_REF = 0x8CA3; +ol.xml.isDocument = goog.userAgent.IE ? + ol.xml.isDocumentIE_ : ol.xml.isDocument_; /** - * @const - * @type {number} + * @param {?} value Value. + * @private + * @return {boolean} Is node. */ -goog.webgl.STENCIL_BACK_VALUE_MASK = 0x8CA4; +ol.xml.isNode_ = function(value) { + return value instanceof Node; +}; /** - * @const - * @type {number} + * @param {?} value Value. + * @private + * @return {boolean} Is node. */ -goog.webgl.STENCIL_BACK_WRITEMASK = 0x8CA5; +ol.xml.isNodeIE_ = function(value) { + return goog.isObject(value) && goog.isDef(value.nodeType); +}; /** - * @const - * @type {number} + * @param {?} value Value. + * @return {boolean} Is node. */ -goog.webgl.VIEWPORT = 0x0BA2; +ol.xml.isNode = goog.userAgent.IE ? ol.xml.isNodeIE_ : ol.xml.isNode_; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {string} Value + * @private */ -goog.webgl.SCISSOR_BOX = 0x0C10; +ol.xml.getAttributeNS_ = function(node, namespaceURI, name) { + return node.getAttributeNS(namespaceURI, name) || ''; +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {string} Value + * @private */ -goog.webgl.COLOR_CLEAR_VALUE = 0x0C22; +ol.xml.getAttributeNSActiveX_ = function(node, namespaceURI, name) { + var attributeValue = ''; + var attributeNode = ol.xml.getAttributeNodeNS(node, namespaceURI, name); + if (goog.isDef(attributeNode)) { + attributeValue = attributeNode.nodeValue; + } + return attributeValue; +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {string} Value */ -goog.webgl.COLOR_WRITEMASK = 0x0C23; +ol.xml.getAttributeNS = + (document.implementation && document.implementation.createDocument) ? + ol.xml.getAttributeNS_ : ol.xml.getAttributeNSActiveX_; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {?Node} Attribute node or null if none found. + * @private */ -goog.webgl.UNPACK_ALIGNMENT = 0x0CF5; +ol.xml.getAttributeNodeNS_ = function(node, namespaceURI, name) { + return node.getAttributeNodeNS(namespaceURI, name); +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {?Node} Attribute node or null if none found. + * @private */ -goog.webgl.PACK_ALIGNMENT = 0x0D05; +ol.xml.getAttributeNodeNSActiveX_ = function(node, namespaceURI, name) { + var attributeNode = null; + var attributes = node.attributes; + var potentialNode, fullName; + for (var i = 0, len = attributes.length; i < len; ++i) { + potentialNode = attributes[i]; + if (potentialNode.namespaceURI == namespaceURI) { + fullName = (potentialNode.prefix) ? + (potentialNode.prefix + ':' + name) : name; + if (fullName == potentialNode.nodeName) { + attributeNode = potentialNode; + break; + } + } + } + return attributeNode; +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @return {?Node} Attribute node or null if none found. */ -goog.webgl.MAX_TEXTURE_SIZE = 0x0D33; +ol.xml.getAttributeNodeNS = + (document.implementation && document.implementation.createDocument) ? + ol.xml.getAttributeNodeNS_ : ol.xml.getAttributeNodeNSActiveX_; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + * @private */ -goog.webgl.MAX_VIEWPORT_DIMS = 0x0D3A; +ol.xml.setAttributeNS_ = function(node, namespaceURI, name, value) { + node.setAttributeNS(namespaceURI, name, value); +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. + * @private */ -goog.webgl.SUBPIXEL_BITS = 0x0D50; +ol.xml.setAttributeNSActiveX_ = function(node, namespaceURI, name, value) { + if (!goog.isNull(namespaceURI)) { + var attribute = node.ownerDocument.createNode(2, name, namespaceURI); + attribute.nodeValue = value; + node.setAttributeNode(attribute); + } else { + node.setAttribute(name, value); + } +}; /** - * @const - * @type {number} + * @param {Node} node Node. + * @param {?string} namespaceURI Namespace URI. + * @param {string} name Attribute name. + * @param {string|number} value Value. */ -goog.webgl.RED_BITS = 0x0D52; +ol.xml.setAttributeNS = + (document.implementation && document.implementation.createDocument) ? + ol.xml.setAttributeNS_ : ol.xml.setAttributeNSActiveX_; /** - * @const - * @type {number} + * Parse an XML string to an XML Document. + * @param {string} xml XML. + * @return {Document} Document. + * @api */ -goog.webgl.GREEN_BITS = 0x0D53; +ol.xml.parse = function(xml) { + return new DOMParser().parseFromString(xml, 'application/xml'); +}; /** - * @const - * @type {number} + * Make an array extender function for extending the array at the top of the + * object stack. + * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)} + * valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.xml.Parser} Parser. + * @template T */ -goog.webgl.BLUE_BITS = 0x0D54; +ol.xml.makeArrayExtender = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(opt_this, node, objectStack); + if (goog.isDef(value)) { + goog.asserts.assert(goog.isArray(value), + 'valueReader function is expected to return an array of values'); + var array = /** @type {Array.<*>} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isArray(array), + 'objectStack is supposed to be an array of arrays'); + goog.array.extend(array, value); + } + }); +}; /** - * @const - * @type {number} + * Make an array pusher function for pushing to the array at the top of the + * object stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.xml.Parser} Parser. + * @template T */ -goog.webgl.ALPHA_BITS = 0x0D55; +ol.xml.makeArrayPusher = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, + node, objectStack); + if (goog.isDef(value)) { + var array = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isArray(array), + 'objectStack is supposed to be an array of arrays'); + array.push(value); + } + }); +}; /** - * @const - * @type {number} + * Make an object stack replacer function for replacing the object at the + * top of the stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.xml.Parser} Parser. + * @template T */ -goog.webgl.DEPTH_BITS = 0x0D56; +ol.xml.makeReplacer = function(valueReader, opt_this) { + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, + node, objectStack); + if (goog.isDef(value)) { + objectStack[objectStack.length - 1] = value; + } + }); +}; /** - * @const - * @type {number} + * Make an object property pusher function for adding a property to the + * object at the top of the stack. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {string=} opt_property Property. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.xml.Parser} Parser. + * @template T */ -goog.webgl.STENCIL_BITS = 0x0D57; +ol.xml.makeObjectPropertyPusher = + function(valueReader, opt_property, opt_this) { + goog.asserts.assert(goog.isDef(valueReader), + 'undefined valueReader, expected function(this: T, Node, Array.<*>)'); + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, + node, objectStack); + if (goog.isDef(value)) { + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + var property = goog.isDef(opt_property) ? + opt_property : node.localName; + goog.asserts.assert(goog.isObject(object), + 'entity from stack was not an object'); + var array = goog.object.setIfUndefined(object, property, []); + array.push(value); + } + }); +}; /** - * @const - * @type {number} + * Make an object property setter function. + * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. + * @param {string=} opt_property Property. + * @param {T=} opt_this The object to use as `this` in `valueReader`. + * @return {ol.xml.Parser} Parser. + * @template T */ -goog.webgl.POLYGON_OFFSET_UNITS = 0x2A00; +ol.xml.makeObjectPropertySetter = + function(valueReader, opt_property, opt_this) { + goog.asserts.assert(goog.isDef(valueReader), + 'undefined valueReader, expected function(this: T, Node, Array.<*>)'); + return ( + /** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + */ + function(node, objectStack) { + var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, + node, objectStack); + if (goog.isDef(value)) { + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + var property = goog.isDef(opt_property) ? + opt_property : node.localName; + goog.asserts.assert(goog.isObject(object), + 'entity from stack was not an object'); + object[property] = value; + } + }); +}; /** - * @const - * @type {number} + * Make a parserNS hash. + * @param {Array.<string>} namespaceURIs Namespace URIs. + * @param {Object.<string, ol.xml.Parser>} parsers Parsers. + * @param {Object.<string, Object.<string, ol.xml.Parser>>=} opt_parsersNS + * ParsersNS. + * @return {Object.<string, Object.<string, ol.xml.Parser>>} Parsers NS. */ -goog.webgl.POLYGON_OFFSET_FACTOR = 0x8038; +ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) { + return /** @type {Object.<string, Object.<string, ol.xml.Parser>>} */ ( + ol.xml.makeStructureNS(namespaceURIs, parsers, opt_parsersNS)); +}; /** - * @const - * @type {number} + * Create a serializer that appends nodes written by its `nodeWriter` to its + * designated parent. The parent is the `node` of the + * {@link ol.xml.NodeStackItem} at the top of the `objectStack`. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.xml.Serializer} Serializer. + * @template T, V */ -goog.webgl.TEXTURE_BINDING_2D = 0x8069; +ol.xml.makeChildAppender = function(nodeWriter, opt_this) { + return function(node, value, objectStack) { + nodeWriter.call(goog.isDef(opt_this) ? opt_this : this, + node, value, objectStack); + var parent = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(parent), + 'entity from stack was not an object'); + var parentNode = parent.node; + goog.asserts.assert(ol.xml.isNode(parentNode) || + ol.xml.isDocument(parentNode), + 'expected parentNode %s to be a Node or a Document', parentNode); + parentNode.appendChild(node); + }; +}; /** - * @const - * @type {number} + * Create a serializer that calls the provided `nodeWriter` from + * {@link ol.xml.serialize}. This can be used by the parent writer to have the + * 'nodeWriter' called with an array of values when the `nodeWriter` was + * designed to serialize a single item. An example would be a LineString + * geometry writer, which could be reused for writing MultiLineString + * geometries. + * @param {function(this: T, Node, V, Array.<*>)} + * nodeWriter Node writer. + * @param {T=} opt_this The object to use as `this` in `nodeWriter`. + * @return {ol.xml.Serializer} Serializer. + * @template T, V */ -goog.webgl.SAMPLE_BUFFERS = 0x80A8; +ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { + var serializersNS, nodeFactory; + return function(node, value, objectStack) { + if (!goog.isDef(serializersNS)) { + serializersNS = {}; + var serializers = {}; + serializers[node.localName] = nodeWriter; + serializersNS[node.namespaceURI] = serializers; + nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName); + } + ol.xml.serialize(serializersNS, nodeFactory, value, objectStack); + }; +}; /** - * @const - * @type {number} + * Create a node factory which can use the `opt_keys` passed to + * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names, + * or a fixed node name. The namespace of the created nodes can either be fixed, + * or the parent namespace will be used. + * @param {string=} opt_nodeName Fixed node name which will be used for all + * created nodes. If not provided, the 3rd argument to the resulting node + * factory needs to be provided and will be the nodeName. + * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for + * all created nodes. If not provided, the namespace of the parent node will + * be used. + * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory. */ -goog.webgl.SAMPLES = 0x80A9; +ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { + var fixedNodeName = opt_nodeName; + return ( + /** + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. + */ + function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var node = context.node; + goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node), + 'expected node %s to be a Node or a Document', node); + var nodeName = fixedNodeName; + if (!goog.isDef(nodeName)) { + nodeName = opt_nodeName; + } + var namespaceURI = opt_namespaceURI; + if (!goog.isDef(opt_namespaceURI)) { + namespaceURI = node.namespaceURI; + } + goog.asserts.assert(goog.isDef(nodeName), 'nodeName was undefined'); + return ol.xml.createElementNS(namespaceURI, nodeName); + } + ); +}; /** + * A node factory that creates a node using the parent's `namespaceURI` and the + * `nodeName` passed by {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop} to the node factory. * @const - * @type {number} + * @type {function(*, Array.<*>, string=): (Node|undefined)} */ -goog.webgl.SAMPLE_COVERAGE_VALUE = 0x80AA; +ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); /** - * @const - * @type {number} + * Create an array of `values` to be used with {@link ol.xml.serialize} or + * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as + * `opt_key` argument. + * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can + * be a subset of the `orderedKeys`. + * @param {Array.<string>} orderedKeys Keys in the order of the sequence. + * @return {Array.<V>} Values in the order of the sequence. The resulting array + * has the same length as the `orderedKeys` array. Values that are not + * present in `object` will be `undefined` in the resulting array. + * @template V */ -goog.webgl.SAMPLE_COVERAGE_INVERT = 0x80AB; +ol.xml.makeSequence = function(object, orderedKeys) { + var length = orderedKeys.length; + var sequence = new Array(length); + for (var i = 0; i < length; ++i) { + sequence[i] = object[orderedKeys[i]]; + } + return sequence; +}; /** - * @const - * @type {number} + * Create a namespaced structure, using the same values for each namespace. + * This can be used as a starting point for versioned parsers, when only a few + * values are version specific. + * @param {Array.<string>} namespaceURIs Namespace URIs. + * @param {T} structure Structure. + * @param {Object.<string, T>=} opt_structureNS Namespaced structure to add to. + * @return {Object.<string, T>} Namespaced structure. + * @template T */ -goog.webgl.COMPRESSED_TEXTURE_FORMATS = 0x86A3; +ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) { + /** + * @type {Object.<string, *>} + */ + var structureNS = goog.isDef(opt_structureNS) ? opt_structureNS : {}; + var i, ii; + for (i = 0, ii = namespaceURIs.length; i < ii; ++i) { + structureNS[namespaceURIs[i]] = structure; + } + return structureNS; +}; /** - * @const - * @type {number} + * Parse a node using the parsers and object stack. + * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS + * Parsers by namespace. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @param {*=} opt_this The object to use as `this`. */ -goog.webgl.DONT_CARE = 0x1100; +ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) { + var n; + for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { + var parsers = parsersNS[n.namespaceURI]; + if (goog.isDef(parsers)) { + var parser = parsers[n.localName]; + if (goog.isDef(parser)) { + parser.call(opt_this, n, objectStack); + } + } + } +}; /** - * @const - * @type {number} + * Push an object on top of the stack, parse and return the popped object. + * @param {T} object Object. + * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS + * Parsers by namespace. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @param {*=} opt_this The object to use as `this`. + * @return {T} Object. + * @template T */ -goog.webgl.FASTEST = 0x1101; +ol.xml.pushParseAndPop = function( + object, parsersNS, node, objectStack, opt_this) { + objectStack.push(object); + ol.xml.parseNode(parsersNS, node, objectStack, opt_this); + return objectStack.pop(); +}; /** - * @const - * @type {number} + * Walk through an array of `values` and call a serializer for each value. + * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @template T */ -goog.webgl.NICEST = 0x1102; +ol.xml.serialize = function( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + var length = (goog.isDef(opt_keys) ? opt_keys : values).length; + var value, node; + for (var i = 0; i < length; ++i) { + value = values[i]; + if (goog.isDef(value)) { + node = nodeFactory.call(opt_this, value, objectStack, + goog.isDef(opt_keys) ? opt_keys[i] : undefined); + if (goog.isDef(node)) { + serializersNS[node.namespaceURI][node.localName] + .call(opt_this, node, value, objectStack); + } + } + } +}; /** - * @const - * @type {number} + * @param {O} object Object. + * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS + * Namespaced serializers. + * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory + * Node factory. The `nodeFactory` creates the node whose namespace and name + * will be used to choose a node writer from `serializersNS`. This + * separation allows us to decide what kind of node to create, depending on + * the value we want to serialize. An example for this would be different + * geometry writers based on the geometry type. + * @param {Array.<*>} values Values to serialize. An example would be an array + * of {@link ol.Feature} instances. + * @param {Array.<*>} objectStack Node stack. + * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the + * `nodeFactory`. This is used for serializing object literals where the + * node name relates to the property key. The array length of `opt_keys` has + * to match the length of `values`. For serializing a sequence, `opt_keys` + * determines the order of the sequence. + * @param {T=} opt_this The object to use as `this` for the node factory and + * serializers. + * @return {O|undefined} Object. + * @template O, T */ -goog.webgl.GENERATE_MIPMAP_HINT = 0x8192; +ol.xml.pushSerializeAndPop = function(object, + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { + objectStack.push(object); + ol.xml.serialize( + serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this); + return objectStack.pop(); +}; +goog.provide('ol.FeatureLoader'); +goog.provide('ol.featureloader'); -/** - * @const - * @type {number} - */ -goog.webgl.BYTE = 0x1400; +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.net.EventType'); +goog.require('goog.net.XhrIo'); +goog.require('goog.net.XhrIo.ResponseType'); +goog.require('ol.format.FormatType'); +goog.require('ol.xml'); /** - * @const - * @type {number} + * @api + * @typedef {function(this:ol.source.Vector, ol.Extent, number, + * ol.proj.Projection)} */ -goog.webgl.UNSIGNED_BYTE = 0x1401; +ol.FeatureLoader; /** - * @const - * @type {number} + * @param {string} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @param {function(this:ol.source.Vector, Array.<ol.Feature>)} success + * Function called with the loaded features. Called with the vector + * source as `this`. + * @return {ol.FeatureLoader} The feature loader. */ -goog.webgl.SHORT = 0x1402; +ol.featureloader.loadFeaturesXhr = function(url, format, success) { + return ( + /** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.proj.Projection} projection Projection. + * @this {ol.source.Vector} + */ + function(extent, resolution, projection) { + var xhrIo = new goog.net.XhrIo(); + xhrIo.setResponseType(goog.net.XhrIo.ResponseType.TEXT); + goog.events.listen(xhrIo, goog.net.EventType.COMPLETE, + /** + * @param {Event} event Event. + * @private + * @this {ol.source.Vector} + */ + function(event) { + var xhrIo = event.target; + goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo, + 'event.target/xhrIo is an instance of goog.net.XhrIo'); + if (xhrIo.isSuccess()) { + var type = format.getType(); + /** @type {Document|Node|Object|string|undefined} */ + var source; + if (type == ol.format.FormatType.JSON) { + source = xhrIo.getResponseText(); + } else if (type == ol.format.FormatType.TEXT) { + source = xhrIo.getResponseText(); + } else if (type == ol.format.FormatType.XML) { + if (!goog.userAgent.IE) { + source = xhrIo.getResponseXml(); + } + if (!goog.isDefAndNotNull(source)) { + source = ol.xml.parse(xhrIo.getResponseText()); + } + } else { + goog.asserts.fail('unexpected format type'); + } + if (goog.isDefAndNotNull(source)) { + var features = format.readFeatures(source, + {featureProjection: projection}); + success.call(this, features); + } else { + goog.asserts.fail('undefined or null source'); + } + } else { + // FIXME + } + goog.dispose(xhrIo); + }, false, this); + xhrIo.send(url); + }); +}; /** - * @const - * @type {number} + * Create an XHR feature loader for a `url` and `format`. The feature loader + * loads features (with XHR), parses the features, and adds them to the + * vector source. + * @param {string} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @return {ol.FeatureLoader} The feature loader. + * @api */ -goog.webgl.UNSIGNED_SHORT = 0x1403; +ol.featureloader.xhr = function(url, format) { + return ol.featureloader.loadFeaturesXhr(url, format, + /** + * @param {Array.<ol.Feature>} features The loaded features. + * @this {ol.source.Vector} + */ + function(features) { + this.addFeatures(features); + }); +}; +goog.provide('ol.LoadingStrategy'); +goog.provide('ol.loadingstrategy'); -/** - * @const - * @type {number} - */ -goog.webgl.INT = 0x1404; +goog.require('ol.TileCoord'); /** - * @const - * @type {number} + * @typedef {function(ol.Extent, number): Array.<ol.Extent>} + * @api */ -goog.webgl.UNSIGNED_INT = 0x1405; +ol.LoadingStrategy; /** - * @const - * @type {number} + * Strategy function for loading all features with a single request. + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + * @api */ -goog.webgl.FLOAT = 0x1406; +ol.loadingstrategy.all = function(extent, resolution) { + return [[-Infinity, -Infinity, Infinity, Infinity]]; +}; /** - * @const - * @type {number} + * Strategy function for loading features based on the view's extent and + * resolution. + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + * @api */ -goog.webgl.DEPTH_COMPONENT = 0x1902; +ol.loadingstrategy.bbox = function(extent, resolution) { + return [extent]; +}; /** - * @const - * @type {number} + * Creates a strategy function for loading features based on a tile grid. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @return {function(ol.Extent, number): Array.<ol.Extent>} Loading strategy. + * @api */ -goog.webgl.ALPHA = 0x1906; - +ol.loadingstrategy.tile = function(tileGrid) { + return ( + /** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {Array.<ol.Extent>} Extents. + */ + function(extent, resolution) { + var z = tileGrid.getZForResolution(resolution); + var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + /** @type {Array.<ol.Extent>} */ + var extents = []; + /** @type {ol.TileCoord} */ + var tileCoord = [z, 0, 0]; + for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX; + ++tileCoord[1]) { + for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY; + ++tileCoord[2]) { + extents.push(tileGrid.getTileCoordExtent(tileCoord)); + } + } + return extents; + }); +}; +goog.provide('ol.ext.rbush'); +/** @typedef {function(*)} */ +ol.ext.rbush; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; /** - * @const - * @type {number} + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} */ -goog.webgl.RGB = 0x1907; - +/* + (c) 2013, Vladimir Agafonkin + RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles. + https://github.com/mourner/rbush +*/ -/** - * @const - * @type {number} - */ -goog.webgl.RGBA = 0x1908; +(function () { 'use strict'; +function rbush(maxEntries, format) { -/** - * @const - * @type {number} - */ -goog.webgl.LUMINANCE = 0x1909; + // jshint newcap: false, validthis: true + if (!(this instanceof rbush)) return new rbush(maxEntries, format); + // max entries in a node is 9 by default; min node fill is 40% for best performance + this._maxEntries = Math.max(4, maxEntries || 9); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); -/** - * @const - * @type {number} - */ -goog.webgl.LUMINANCE_ALPHA = 0x190A; + if (format) { + this._initFormat(format); + } + this.clear(); +} -/** - * @const - * @type {number} - */ -goog.webgl.UNSIGNED_SHORT_4_4_4_4 = 0x8033; +rbush.prototype = { + all: function () { + return this._all(this.data, []); + }, -/** - * @const - * @type {number} - */ -goog.webgl.UNSIGNED_SHORT_5_5_5_1 = 0x8034; + search: function (bbox) { + var node = this.data, + result = [], + toBBox = this.toBBox; -/** - * @const - * @type {number} - */ -goog.webgl.UNSIGNED_SHORT_5_6_5 = 0x8363; + if (!intersects(bbox, node.bbox)) return result; + var nodesToSearch = [], + i, len, child, childBBox; -/** - * @const - * @type {number} - */ -goog.webgl.FRAGMENT_SHADER = 0x8B30; + while (node) { + for (i = 0, len = node.children.length; i < len; i++) { + child = node.children[i]; + childBBox = node.leaf ? toBBox(child) : child.bbox; -/** - * @const - * @type {number} - */ -goog.webgl.VERTEX_SHADER = 0x8B31; + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); + } + } + node = nodesToSearch.pop(); + } + return result; + }, -/** - * @const - * @type {number} - */ -goog.webgl.MAX_VERTEX_ATTRIBS = 0x8869; + load: function (data) { + if (!(data && data.length)) return this; + if (data.length < this._minEntries) { + for (var i = 0, len = data.length; i < len; i++) { + this.insert(data[i]); + } + return this; + } -/** - * @const - * @type {number} - */ -goog.webgl.MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; + // recursively build the tree with the given data from stratch using OMT algorithm + var node = this._build(data.slice(), 0, data.length - 1, 0); + if (!this.data.children.length) { + // save as is if tree is empty + this.data = node; -/** - * @const - * @type {number} - */ -goog.webgl.MAX_VARYING_VECTORS = 0x8DFC; + } else if (this.data.height === node.height) { + // split root if trees have the same height + this._splitRoot(this.data, node); + } else { + if (this.data.height < node.height) { + // swap trees if inserted one is bigger + var tmpNode = this.data; + this.data = node; + node = tmpNode; + } -/** - * @const - * @type {number} - */ -goog.webgl.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D; + // insert the small tree into the large tree at appropriate level + this._insert(node, this.data.height - node.height - 1, true); + } + return this; + }, -/** - * @const - * @type {number} - */ -goog.webgl.MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; + insert: function (item) { + if (item) this._insert(item, this.data.height - 1); + return this; + }, + clear: function () { + this.data = { + children: [], + height: 1, + bbox: empty(), + leaf: true + }; + return this; + }, -/** - * @const - * @type {number} - */ -goog.webgl.MAX_TEXTURE_IMAGE_UNITS = 0x8872; + remove: function (item) { + if (!item) return this; + var node = this.data, + bbox = this.toBBox(item), + path = [], + indexes = [], + i, parent, index, goingUp; -/** - * @const - * @type {number} - */ -goog.webgl.MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD; + // depth-first iterative tree traversal + while (node || path.length) { + if (!node) { // go up + node = path.pop(); + parent = path[path.length - 1]; + i = indexes.pop(); + goingUp = true; + } -/** - * @const - * @type {number} - */ -goog.webgl.SHADER_TYPE = 0x8B4F; + if (node.leaf) { // check current node + index = node.children.indexOf(item); + if (index !== -1) { + // item found, remove the item and condense tree upwards + node.children.splice(index, 1); + path.push(node); + this._condense(path); + return this; + } + } -/** - * @const - * @type {number} - */ -goog.webgl.DELETE_STATUS = 0x8B80; + if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down + path.push(node); + indexes.push(i); + i = 0; + parent = node; + node = node.children[0]; + } else if (parent) { // go right + i++; + node = parent.children[i]; + goingUp = false; -/** - * @const - * @type {number} - */ -goog.webgl.LINK_STATUS = 0x8B82; + } else node = null; // nothing found + } + return this; + }, -/** - * @const - * @type {number} - */ -goog.webgl.VALIDATE_STATUS = 0x8B83; + toBBox: function (item) { return item; }, + compareMinX: function (a, b) { return a[0] - b[0]; }, + compareMinY: function (a, b) { return a[1] - b[1]; }, -/** - * @const - * @type {number} - */ -goog.webgl.ATTACHED_SHADERS = 0x8B85; + toJSON: function () { return this.data; }, + fromJSON: function (data) { + this.data = data; + return this; + }, -/** - * @const - * @type {number} - */ -goog.webgl.ACTIVE_UNIFORMS = 0x8B86; + _all: function (node, result) { + var nodesToSearch = []; + while (node) { + if (node.leaf) result.push.apply(result, node.children); + else nodesToSearch.push.apply(nodesToSearch, node.children); + node = nodesToSearch.pop(); + } + return result; + }, -/** - * @const - * @type {number} - */ -goog.webgl.ACTIVE_ATTRIBUTES = 0x8B89; + _build: function (items, left, right, height) { + var N = right - left + 1, + M = this._maxEntries, + node; -/** - * @const - * @type {number} - */ -goog.webgl.SHADING_LANGUAGE_VERSION = 0x8B8C; + if (N <= M) { + // reached leaf level; return leaf + node = { + children: items.slice(left, right + 1), + height: 1, + bbox: null, + leaf: true + }; + calcBBox(node, this.toBBox); + return node; + } + if (!height) { + // target height of the bulk-loaded tree + height = Math.ceil(Math.log(N) / Math.log(M)); -/** - * @const - * @type {number} - */ -goog.webgl.CURRENT_PROGRAM = 0x8B8D; + // target number of root entries to maximize storage utilization + M = Math.ceil(N / Math.pow(M, height - 1)); + } + // TODO eliminate recursion? -/** - * @const - * @type {number} - */ -goog.webgl.NEVER = 0x0200; + node = { + children: [], + height: height, + bbox: null + }; + // split the items into M mostly square tiles -/** - * @const - * @type {number} - */ -goog.webgl.LESS = 0x0201; + var N2 = Math.ceil(N / M), + N1 = N2 * Math.ceil(Math.sqrt(M)), + i, j, right2, right3; + multiSelect(items, left, right, N1, this.compareMinX); -/** - * @const - * @type {number} - */ -goog.webgl.EQUAL = 0x0202; + for (i = left; i <= right; i += N1) { + right2 = Math.min(i + N1 - 1, right); -/** - * @const - * @type {number} - */ -goog.webgl.LEQUAL = 0x0203; + multiSelect(items, i, right2, N2, this.compareMinY); + for (j = i; j <= right2; j += N2) { -/** - * @const - * @type {number} - */ -goog.webgl.GREATER = 0x0204; + right3 = Math.min(j + N2 - 1, right2); + // pack each entry recursively + node.children.push(this._build(items, j, right3, height - 1)); + } + } -/** - * @const - * @type {number} - */ -goog.webgl.NOTEQUAL = 0x0205; + calcBBox(node, this.toBBox); + return node; + }, -/** - * @const - * @type {number} - */ -goog.webgl.GEQUAL = 0x0206; + _chooseSubtree: function (bbox, node, level, path) { + var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; -/** - * @const - * @type {number} - */ -goog.webgl.ALWAYS = 0x0207; + while (true) { + path.push(node); + if (node.leaf || path.length - 1 === level) break; -/** - * @const - * @type {number} - */ -goog.webgl.KEEP = 0x1E00; + minArea = minEnlargement = Infinity; + for (i = 0, len = node.children.length; i < len; i++) { + child = node.children[i]; + area = bboxArea(child.bbox); + enlargement = enlargedArea(bbox, child.bbox) - area; -/** - * @const - * @type {number} - */ -goog.webgl.REPLACE = 0x1E01; + // choose entry with the least area enlargement + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + } else if (enlargement === minEnlargement) { + // otherwise choose one with the smallest area + if (area < minArea) { + minArea = area; + targetNode = child; + } + } + } -/** - * @const - * @type {number} - */ -goog.webgl.INCR = 0x1E02; + node = targetNode; + } + return node; + }, -/** - * @const - * @type {number} - */ -goog.webgl.DECR = 0x1E03; + _insert: function (item, level, isNode) { + var toBBox = this.toBBox, + bbox = isNode ? item.bbox : toBBox(item), + insertPath = []; -/** - * @const - * @type {number} - */ -goog.webgl.INVERT = 0x150A; + // find the best node for accommodating the item, saving all nodes along the path too + var node = this._chooseSubtree(bbox, this.data, level, insertPath); + // put the item into the node + node.children.push(item); + extend(node.bbox, bbox); -/** - * @const - * @type {number} - */ -goog.webgl.INCR_WRAP = 0x8507; + // split on node overflow; propagate upwards if necessary + while (level >= 0) { + if (insertPath[level].children.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else break; + } + // adjust bboxes along the insertion path + this._adjustParentBBoxes(bbox, insertPath, level); + }, -/** - * @const - * @type {number} - */ -goog.webgl.DECR_WRAP = 0x8508; + // split overflowed node into two + _split: function (insertPath, level) { + var node = insertPath[level], + M = node.children.length, + m = this._minEntries; -/** - * @const - * @type {number} - */ -goog.webgl.VENDOR = 0x1F00; + this._chooseSplitAxis(node, m, M); + var newNode = { + children: node.children.splice(this._chooseSplitIndex(node, m, M)), + height: node.height + }; -/** - * @const - * @type {number} - */ -goog.webgl.RENDERER = 0x1F01; + if (node.leaf) newNode.leaf = true; + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); -/** - * @const - * @type {number} - */ -goog.webgl.VERSION = 0x1F02; + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); + }, + _splitRoot: function (node, newNode) { + // split root node + this.data = { + children: [node, newNode], + height: node.height + 1 + }; + calcBBox(this.data, this.toBBox); + }, -/** - * @const - * @type {number} - */ -goog.webgl.NEAREST = 0x2600; + _chooseSplitIndex: function (node, m, M) { + var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; -/** - * @const - * @type {number} - */ -goog.webgl.LINEAR = 0x2601; + minOverlap = minArea = Infinity; + for (i = m; i <= M - m; i++) { + bbox1 = distBBox(node, 0, i, this.toBBox); + bbox2 = distBBox(node, i, M, this.toBBox); -/** - * @const - * @type {number} - */ -goog.webgl.NEAREST_MIPMAP_NEAREST = 0x2700; + overlap = intersectionArea(bbox1, bbox2); + area = bboxArea(bbox1) + bboxArea(bbox2); + // choose distribution with minimum overlap + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; -/** - * @const - * @type {number} - */ -goog.webgl.LINEAR_MIPMAP_NEAREST = 0x2701; + minArea = area < minArea ? area : minArea; + } else if (overlap === minOverlap) { + // otherwise choose distribution with minimum area + if (area < minArea) { + minArea = area; + index = i; + } + } + } -/** - * @const - * @type {number} - */ -goog.webgl.NEAREST_MIPMAP_LINEAR = 0x2702; + return index; + }, + // sorts node children by the best axis for split + _chooseSplitAxis: function (node, m, M) { -/** - * @const - * @type {number} - */ -goog.webgl.LINEAR_MIPMAP_LINEAR = 0x2703; + var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, + xMargin = this._allDistMargin(node, m, M, compareMinX), + yMargin = this._allDistMargin(node, m, M, compareMinY); + // if total distributions margin value is minimal for x, sort by minX, + // otherwise it's already sorted by minY + if (xMargin < yMargin) node.children.sort(compareMinX); + }, -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_MAG_FILTER = 0x2800; + // total margin of all possible split distributions where each node is at least m full + _allDistMargin: function (node, m, M, compare) { + node.children.sort(compare); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_MIN_FILTER = 0x2801; + var toBBox = this.toBBox, + leftBBox = distBBox(node, 0, m, toBBox), + rightBBox = distBBox(node, M - m, M, toBBox), + margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), + i, child; + for (i = m; i < M - m; i++) { + child = node.children[i]; + extend(leftBBox, node.leaf ? toBBox(child) : child.bbox); + margin += bboxMargin(leftBBox); + } -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_WRAP_S = 0x2802; + for (i = M - m - 1; i >= m; i--) { + child = node.children[i]; + extend(rightBBox, node.leaf ? toBBox(child) : child.bbox); + margin += bboxMargin(rightBBox); + } + return margin; + }, -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_WRAP_T = 0x2803; + _adjustParentBBoxes: function (bbox, path, level) { + // adjust bboxes along the given tree path + for (var i = level; i >= 0; i--) { + extend(path[i].bbox, bbox); + } + }, + _condense: function (path) { + // go through the path, removing empty nodes and updating bboxes + for (var i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].children.length === 0) { + if (i > 0) { + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_2D = 0x0DE1; + } else this.clear(); + } else calcBBox(path[i], this.toBBox); + } + }, -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE = 0x1702; + _initFormat: function (format) { + // data format (minX, minY, maxX, maxY accessors) + // uses eval-type function compilation instead of just accepting a toBBox function + // because the algorithms are very sensitive to sorting functions performance, + // so they should be dead simple and without inner calls -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP = 0x8513; + // jshint evil: true + var compareArr = ['return a', ' - b', ';']; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_BINDING_CUBE_MAP = 0x8514; + this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); + this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); + this.toBBox = new Function('a', 'return [a' + format.join(', a') + '];'); + } +}; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; +// calculate node's bbox from bboxes of its children +function calcBBox(node, toBBox) { + node.bbox = distBBox(node, 0, node.children.length, toBBox); +} -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; +// min bounding rectangle of node children from k to p-1 +function distBBox(node, k, p, toBBox) { + var bbox = empty(); + for (var i = k, child; i < p; i++) { + child = node.children[i]; + extend(bbox, node.leaf ? toBBox(child) : child.bbox); + } -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; + return bbox; +} +function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; } -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; +function extend(a, b) { + a[0] = Math.min(a[0], b[0]); + a[1] = Math.min(a[1], b[1]); + a[2] = Math.max(a[2], b[2]); + a[3] = Math.max(a[3], b[3]); + return a; +} +function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; } +function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; } -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; +function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); } +function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); } +function enlargedArea(a, b) { + return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * + (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); +} -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; +function intersectionArea(a, b) { + var minX = Math.max(a[0], b[0]), + minY = Math.max(a[1], b[1]), + maxX = Math.min(a[2], b[2]), + maxY = Math.min(a[3], b[3]); + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY); +} -/** - * @const - * @type {number} - */ -goog.webgl.MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; +function contains(a, b) { + return a[0] <= b[0] && + a[1] <= b[1] && + b[2] <= a[2] && + b[3] <= a[3]; +} +function intersects(a, b) { + return b[0] <= a[2] && + b[1] <= a[3] && + b[2] >= a[0] && + b[3] >= a[1]; +} -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE0 = 0x84C0; +// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; +// combines selection algorithm with binary divide & conquer approach +function multiSelect(arr, left, right, n, compare) { + var stack = [left, right], + mid; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE1 = 0x84C1; + while (stack.length) { + right = stack.pop(); + left = stack.pop(); + if (right - left <= n) continue; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE2 = 0x84C2; + mid = left + Math.ceil((right - left) / n / 2) * n; + select(arr, left, right, mid, compare); + stack.push(left, mid, mid, right); + } +} -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE3 = 0x84C3; +// sort array between left and right (inclusive) so that the smallest k elements come first (unordered) +function select(arr, left, right, k, compare) { + var n, i, z, s, sd, newLeft, newRight, t, j; + while (right > left) { + if (right - left > 600) { + n = right - left + 1; + i = k - left + 1; + z = Math.log(n); + s = 0.5 * Math.exp(2 * z / 3); + sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1); + newLeft = Math.max(left, Math.floor(k - i * s / n + sd)); + newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd)); + select(arr, newLeft, newRight, k, compare); + } -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE4 = 0x84C4; + t = arr[k]; + i = left; + j = right; + swap(arr, left, k); + if (compare(arr[right], t) > 0) swap(arr, left, right); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE5 = 0x84C5; + while (i < j) { + swap(arr, i, j); + i++; + j--; + while (compare(arr[i], t) < 0) i++; + while (compare(arr[j], t) > 0) j--; + } + if (compare(arr[left], t) === 0) swap(arr, left, j); + else { + j++; + swap(arr, j, right); + } -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE6 = 0x84C6; + if (j <= k) left = j + 1; + if (k <= j) right = j - 1; + } +} +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE7 = 0x84C7; +// export as AMD/CommonJS module or global variable +if (typeof define === 'function' && define.amd) define('rbush', function() { return rbush; }); +else if (typeof module !== 'undefined') module.exports = rbush; +else if (typeof self !== 'undefined') self.rbush = rbush; +else window.rbush = rbush; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE8 = 0x84C8; +})(); +ol.ext.rbush = module.exports; +})(); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE9 = 0x84C9; +goog.provide('ol.structs.RBush'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.ext.rbush'); +goog.require('ol.extent'); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE10 = 0x84CA; /** - * @const - * @type {number} + * Wrapper around the RBush by Vladimir Agafonkin. + * + * @constructor + * @param {number=} opt_maxEntries Max entries. + * @see https://github.com/mourner/rbush + * @struct + * @template T */ -goog.webgl.TEXTURE11 = 0x84CB; - +ol.structs.RBush = function(opt_maxEntries) { -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE12 = 0x84CC; + /** + * @private + */ + this.rbush_ = ol.ext.rbush(opt_maxEntries); + /** + * A mapping between the objects added to this rbush wrapper + * and the objects that are actually added to the internal rbush. + * @private + * @type {Object.<number, Object>} + */ + this.items_ = {}; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE13 = 0x84CD; + if (goog.DEBUG) { + /** + * @private + * @type {number} + */ + this.readers_ = 0; + } +}; /** - * @const - * @type {number} + * Insert a value into the RBush. + * @param {ol.Extent} extent Extent. + * @param {T} value Value. */ -goog.webgl.TEXTURE14 = 0x84CE; +ol.structs.RBush.prototype.insert = function(extent, value) { + if (goog.DEBUG && this.readers_) { + throw new Error('Can not insert value while reading'); + } + var item = [ + extent[0], + extent[1], + extent[2], + extent[3], + value + ]; + this.rbush_.insert(item); + // remember the object that was added to the internal rbush + goog.asserts.assert( + !goog.object.containsKey(this.items_, goog.getUid(value)), + 'uid (%s) of value (%s) already exists', goog.getUid(value), value); + this.items_[goog.getUid(value)] = item; +}; /** - * @const - * @type {number} + * Bulk-insert values into the RBush. + * @param {Array.<ol.Extent>} extents Extents. + * @param {Array.<T>} values Values. */ -goog.webgl.TEXTURE15 = 0x84CF; +ol.structs.RBush.prototype.load = function(extents, values) { + if (goog.DEBUG && this.readers_) { + throw new Error('Can not insert values while reading'); + } + goog.asserts.assert(extents.length === values.length, + 'extens and values must have same length (%s === %s)', + extents.length, values.length); + var items = new Array(values.length); + for (var i = 0, l = values.length; i < l; i++) { + var extent = extents[i]; + var value = values[i]; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE16 = 0x84D0; + var item = [ + extent[0], + extent[1], + extent[2], + extent[3], + value + ]; + items[i] = item; + goog.asserts.assert( + !goog.object.containsKey(this.items_, goog.getUid(value)), + 'uid (%s) of value (%s) already exists', goog.getUid(value), value); + this.items_[goog.getUid(value)] = item; + } + this.rbush_.load(items); +}; /** - * @const - * @type {number} + * Remove a value from the RBush. + * @param {T} value Value. + * @return {boolean} Removed. */ -goog.webgl.TEXTURE17 = 0x84D1; - +ol.structs.RBush.prototype.remove = function(value) { + if (goog.DEBUG && this.readers_) { + throw new Error('Can not remove value while reading'); + } + var uid = goog.getUid(value); + goog.asserts.assert(goog.object.containsKey(this.items_, uid), + 'uid (%s) of value (%s) does not exist', uid, value); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE18 = 0x84D2; + // get the object in which the value was wrapped when adding to the + // internal rbush. then use that object to do the removal. + var item = this.items_[uid]; + goog.object.remove(this.items_, uid); + return this.rbush_.remove(item) !== null; +}; /** - * @const - * @type {number} + * Update the extent of a value in the RBush. + * @param {ol.Extent} extent Extent. + * @param {T} value Value. */ -goog.webgl.TEXTURE19 = 0x84D3; - +ol.structs.RBush.prototype.update = function(extent, value) { + var uid = goog.getUid(value); + goog.asserts.assert(goog.object.containsKey(this.items_, uid), + 'uid (%s) of value (%s) does not exist', uid, value); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE20 = 0x84D4; + var item = this.items_[uid]; + if (!ol.extent.equals(item.slice(0, 4), extent)) { + if (goog.DEBUG && this.readers_) { + throw new Error('Can not update extent while reading'); + } + this.remove(value); + this.insert(extent, value); + } +}; /** - * @const - * @type {number} + * Return all values in the RBush. + * @return {Array.<T>} All. */ -goog.webgl.TEXTURE21 = 0x84D5; +ol.structs.RBush.prototype.getAll = function() { + var items = this.rbush_.all(); + return goog.array.map(items, function(item) { + return item[4]; + }); +}; /** - * @const - * @type {number} + * Return all values in the given extent. + * @param {ol.Extent} extent Extent. + * @return {Array.<T>} All in extent. */ -goog.webgl.TEXTURE22 = 0x84D6; +ol.structs.RBush.prototype.getInExtent = function(extent) { + var items = this.rbush_.search(extent); + return goog.array.map(items, function(item) { + return item[4]; + }); +}; /** - * @const - * @type {number} + * Calls a callback function with each value in the tree. + * If the callback returns a truthy value, this value is returned without + * checking the rest of the tree. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @return {*} Callback return value. + * @template S */ -goog.webgl.TEXTURE23 = 0x84D7; +ol.structs.RBush.prototype.forEach = function(callback, opt_this) { + if (goog.DEBUG) { + ++this.readers_; + try { + return this.forEach_(this.getAll(), callback, opt_this); + } finally { + --this.readers_; + } + } else { + return this.forEach_(this.getAll(), callback, opt_this); + } +}; /** - * @const - * @type {number} + * Calls a callback function with each value in the provided extent. + * @param {ol.Extent} extent Extent. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @return {*} Callback return value. + * @template S */ -goog.webgl.TEXTURE24 = 0x84D8; +ol.structs.RBush.prototype.forEachInExtent = + function(extent, callback, opt_this) { + if (goog.DEBUG) { + ++this.readers_; + try { + return this.forEach_(this.getInExtent(extent), callback, opt_this); + } finally { + --this.readers_; + } + } else { + return this.forEach_(this.getInExtent(extent), callback, opt_this); + } +}; /** - * @const - * @type {number} + * @param {Array.<T>} values Values. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_this The object to use as `this` in `callback`. + * @private + * @return {*} Callback return value. + * @template S */ -goog.webgl.TEXTURE25 = 0x84D9; +ol.structs.RBush.prototype.forEach_ = function(values, callback, opt_this) { + var result; + for (var i = 0, l = values.length; i < l; i++) { + result = callback.call(opt_this, values[i]); + if (result) { + return result; + } + } + return result; +}; /** - * @const - * @type {number} + * @return {boolean} Is empty. */ -goog.webgl.TEXTURE26 = 0x84DA; +ol.structs.RBush.prototype.isEmpty = function() { + return goog.object.isEmpty(this.items_); +}; /** - * @const - * @type {number} + * Remove all values from the RBush. */ -goog.webgl.TEXTURE27 = 0x84DB; +ol.structs.RBush.prototype.clear = function() { + this.rbush_.clear(); + this.items_ = {}; +}; /** - * @const - * @type {number} + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. */ -goog.webgl.TEXTURE28 = 0x84DC; - +ol.structs.RBush.prototype.getExtent = function(opt_extent) { + // FIXME add getExtent() to rbush + return this.rbush_.data.bbox; +}; -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE29 = 0x84DD; +// FIXME bulk feature upload - suppress events +// FIXME make change-detection more refined (notably, geometry hint) +goog.provide('ol.source.Vector'); +goog.provide('ol.source.VectorEvent'); +goog.provide('ol.source.VectorEventType'); -/** - * @const - * @type {number} - */ -goog.webgl.TEXTURE30 = 0x84DE; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.object'); +goog.require('ol.Collection'); +goog.require('ol.CollectionEventType'); +goog.require('ol.Extent'); +goog.require('ol.Feature'); +goog.require('ol.FeatureLoader'); +goog.require('ol.LoadingStrategy'); +goog.require('ol.ObjectEventType'); +goog.require('ol.extent'); +goog.require('ol.featureloader'); +goog.require('ol.loadingstrategy'); +goog.require('ol.proj'); +goog.require('ol.source.Source'); +goog.require('ol.source.State'); +goog.require('ol.structs.RBush'); /** - * @const - * @type {number} + * @enum {string} */ -goog.webgl.TEXTURE31 = 0x84DF; +ol.source.VectorEventType = { + /** + * Triggered when a feature is added to the source. + * @event ol.source.VectorEvent#addfeature + * @api stable + */ + ADDFEATURE: 'addfeature', + /** + * Triggered when a feature is updated. + * @event ol.source.VectorEvent#changefeature + * @api + */ + CHANGEFEATURE: 'changefeature', -/** - * @const - * @type {number} - */ -goog.webgl.ACTIVE_TEXTURE = 0x84E0; + /** + * Triggered when the clear method is called on the source. + * @event ol.source.VectorEvent#clear + * @api + */ + CLEAR: 'clear', + /** + * Triggered when a feature is removed from the source. + * See {@link ol.source.Vector#clear source.clear()} for exceptions. + * @event ol.source.VectorEvent#removefeature + * @api stable + */ + REMOVEFEATURE: 'removefeature' +}; -/** - * @const - * @type {number} - */ -goog.webgl.REPEAT = 0x2901; /** - * @const - * @type {number} + * @classdesc + * Provides a source of features for vector layers. + * + * @constructor + * @extends {ol.source.Source} + * @fires ol.source.VectorEvent + * @param {olx.source.VectorOptions=} opt_options Vector source options. + * @api stable */ -goog.webgl.CLAMP_TO_EDGE = 0x812F; +ol.source.Vector = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * @const - * @type {number} - */ -goog.webgl.MIRRORED_REPEAT = 0x8370; + goog.base(this, { + attributions: options.attributions, + logo: options.logo, + projection: undefined, + state: ol.source.State.READY, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true + }); + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_ = goog.nullFunction; -/** - * @const - * @type {number} - */ -goog.webgl.FLOAT_VEC2 = 0x8B50; + if (goog.isDef(options.loader)) { + this.loader_ = options.loader; + } else if (goog.isDef(options.url)) { + goog.asserts.assert(goog.isDef(options.format), + 'format must be set when url is set'); + // create a XHR feature loader for "url" and "format" + this.loader_ = ol.featureloader.xhr(options.url, options.format); + } + /** + * @private + * @type {ol.LoadingStrategy} + */ + this.strategy_ = goog.isDef(options.strategy) ? options.strategy : + ol.loadingstrategy.all; -/** - * @const - * @type {number} - */ -goog.webgl.FLOAT_VEC3 = 0x8B51; + var useSpatialIndex = + goog.isDef(options.useSpatialIndex) ? options.useSpatialIndex : true; + /** + * @private + * @type {ol.structs.RBush.<ol.Feature>} + */ + this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null; -/** - * @const - * @type {number} - */ -goog.webgl.FLOAT_VEC4 = 0x8B52; + /** + * @private + * @type {ol.structs.RBush.<{extent: ol.Extent}>} + */ + this.loadedExtentsRtree_ = new ol.structs.RBush(); + + /** + * @private + * @type {Object.<string, ol.Feature>} + */ + this.nullGeometryFeatures_ = {}; + /** + * A lookup of features by id (the return from feature.getId()). + * @private + * @type {Object.<string, ol.Feature>} + */ + this.idIndex_ = {}; -/** - * @const - * @type {number} - */ -goog.webgl.INT_VEC2 = 0x8B53; + /** + * A lookup of features without id (keyed by goog.getUid(feature)). + * @private + * @type {Object.<string, ol.Feature>} + */ + this.undefIdIndex_ = {}; + /** + * @private + * @type {Object.<string, Array.<goog.events.Key>>} + */ + this.featureChangeKeys_ = {}; -/** - * @const - * @type {number} - */ -goog.webgl.INT_VEC3 = 0x8B54; + /** + * @private + * @type {ol.Collection.<ol.Feature>} + */ + this.featuresCollection_ = null; + var collection, features; + if (options.features instanceof ol.Collection) { + collection = options.features; + features = collection.getArray(); + } else if (goog.isArray(options.features)) { + features = options.features; + } + if (!useSpatialIndex && !goog.isDef(collection)) { + collection = new ol.Collection(features); + } + if (goog.isDef(features)) { + this.addFeaturesInternal(features); + } + if (goog.isDef(collection)) { + this.bindFeaturesCollection_(collection); + } -/** - * @const - * @type {number} - */ -goog.webgl.INT_VEC4 = 0x8B55; +}; +goog.inherits(ol.source.Vector, ol.source.Source); /** - * @const - * @type {number} + * Add a single feature to the source. If you want to add a batch of features + * at once, call {@link ol.source.Vector#addFeatures source.addFeatures()} + * instead. + * @param {ol.Feature} feature Feature to add. + * @api stable */ -goog.webgl.BOOL = 0x8B56; +ol.source.Vector.prototype.addFeature = function(feature) { + this.addFeatureInternal(feature); + this.changed(); +}; /** - * @const - * @type {number} + * Add a feature without firing a `change` event. + * @param {ol.Feature} feature Feature. + * @protected */ -goog.webgl.BOOL_VEC2 = 0x8B57; +ol.source.Vector.prototype.addFeatureInternal = function(feature) { + var featureKey = goog.getUid(feature).toString(); + if (!this.addToIndex_(featureKey, feature)) { + return; + } -/** - * @const - * @type {number} - */ -goog.webgl.BOOL_VEC3 = 0x8B58; + this.setupChangeEvents_(featureKey, feature); + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + var extent = geometry.getExtent(); + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.insert(extent, feature); + } + } else { + this.nullGeometryFeatures_[featureKey] = feature; + } -/** - * @const - * @type {number} - */ -goog.webgl.BOOL_VEC4 = 0x8B59; + this.dispatchEvent( + new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature)); +}; /** - * @const - * @type {number} + * @param {string} featureKey + * @param {ol.Feature} feature + * @private */ -goog.webgl.FLOAT_MAT2 = 0x8B5A; +ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) { + goog.asserts.assert(!(featureKey in this.featureChangeKeys_), + 'key (%s) not yet registered in featureChangeKey', featureKey); + this.featureChangeKeys_[featureKey] = [ + goog.events.listen(feature, + goog.events.EventType.CHANGE, + this.handleFeatureChange_, false, this), + goog.events.listen(feature, + ol.ObjectEventType.PROPERTYCHANGE, + this.handleFeatureChange_, false, this) + ]; +}; /** - * @const - * @type {number} + * @param {string} featureKey + * @param {ol.Feature} feature + * @return {boolean} `true` if the feature is "valid", in the sense that it is + * also a candidate for insertion into the Rtree, otherwise `false`. + * @private */ -goog.webgl.FLOAT_MAT3 = 0x8B5B; +ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) { + var valid = true; + var id = feature.getId(); + if (goog.isDef(id)) { + if (!(id.toString() in this.idIndex_)) { + this.idIndex_[id.toString()] = feature; + } else { + valid = false; + } + } else { + goog.asserts.assert(!(featureKey in this.undefIdIndex_), + 'Feature already added to the source'); + this.undefIdIndex_[featureKey] = feature; + } + return valid; +}; /** - * @const - * @type {number} + * Add a batch of features to the source. + * @param {Array.<ol.Feature>} features Features to add. + * @api stable */ -goog.webgl.FLOAT_MAT4 = 0x8B5C; +ol.source.Vector.prototype.addFeatures = function(features) { + this.addFeaturesInternal(features); + this.changed(); +}; /** - * @const - * @type {number} + * Add features without firing a `change` event. + * @param {Array.<ol.Feature>} features Features. + * @protected */ -goog.webgl.SAMPLER_2D = 0x8B5E; +ol.source.Vector.prototype.addFeaturesInternal = function(features) { + var featureKey, i, length, feature; + var extents = []; + var newFeatures = []; + var geometryFeatures = []; -/** - * @const - * @type {number} - */ -goog.webgl.SAMPLER_CUBE = 0x8B60; + for (i = 0, length = features.length; i < length; i++) { + feature = features[i]; + featureKey = goog.getUid(feature).toString(); + if (this.addToIndex_(featureKey, feature)) { + newFeatures.push(feature); + } + } + for (i = 0, length = newFeatures.length; i < length; i++) { + feature = newFeatures[i]; + featureKey = goog.getUid(feature).toString(); + this.setupChangeEvents_(featureKey, feature); -/** - * @const - * @type {number} - */ -goog.webgl.VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622; + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + var extent = geometry.getExtent(); + extents.push(extent); + geometryFeatures.push(feature); + } else { + this.nullGeometryFeatures_[featureKey] = feature; + } + } + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.load(extents, geometryFeatures); + } + + for (i = 0, length = newFeatures.length; i < length; i++) { + this.dispatchEvent(new ol.source.VectorEvent( + ol.source.VectorEventType.ADDFEATURE, newFeatures[i])); + } +}; /** - * @const - * @type {number} + * @param {!ol.Collection.<ol.Feature>} collection Collection. + * @private */ -goog.webgl.VERTEX_ATTRIB_ARRAY_SIZE = 0x8623; +ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) { + goog.asserts.assert(goog.isNull(this.featuresCollection_), + 'bindFeaturesCollection can only be called once'); + var modifyingCollection = false; + goog.events.listen(this, ol.source.VectorEventType.ADDFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.push(evt.feature); + modifyingCollection = false; + } + }); + goog.events.listen(this, ol.source.VectorEventType.REMOVEFEATURE, + function(evt) { + if (!modifyingCollection) { + modifyingCollection = true; + collection.remove(evt.feature); + modifyingCollection = false; + } + }); + goog.events.listen(collection, ol.CollectionEventType.ADD, + function(evt) { + if (!modifyingCollection) { + var feature = evt.element; + goog.asserts.assertInstanceof(feature, ol.Feature); + modifyingCollection = true; + this.addFeature(feature); + modifyingCollection = false; + } + }, false, this); + goog.events.listen(collection, ol.CollectionEventType.REMOVE, + function(evt) { + if (!modifyingCollection) { + var feature = evt.element; + goog.asserts.assertInstanceof(feature, ol.Feature); + modifyingCollection = true; + this.removeFeature(feature); + modifyingCollection = false; + } + }, false, this); + this.featuresCollection_ = collection; +}; /** - * @const - * @type {number} + * Remove all features from the source. + * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events. + * @api stable */ -goog.webgl.VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624; +ol.source.Vector.prototype.clear = function(opt_fast) { + if (opt_fast) { + for (var featureId in this.featureChangeKeys_) { + var keys = this.featureChangeKeys_[featureId]; + goog.array.forEach(keys, goog.events.unlistenByKey); + } + if (goog.isNull(this.featuresCollection_)) { + this.featureChangeKeys_ = {}; + this.idIndex_ = {}; + this.undefIdIndex_ = {}; + } + } else { + var rmFeatureInternal = this.removeFeatureInternal; + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.forEach(rmFeatureInternal, this); + goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this); + } + } + if (!goog.isNull(this.featuresCollection_)) { + this.featuresCollection_.clear(); + } + goog.asserts.assert(goog.object.isEmpty(this.featureChangeKeys_), + 'featureChangeKeys is an empty object now'); + goog.asserts.assert(goog.object.isEmpty(this.idIndex_), + 'idIndex is an empty object now'); + goog.asserts.assert(goog.object.isEmpty(this.undefIdIndex_), + 'undefIdIndex is an empty object now'); + + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.clear(); + } + this.loadedExtentsRtree_.clear(); + this.nullGeometryFeatures_ = {}; + + var clearEvent = new ol.source.VectorEvent(ol.source.VectorEventType.CLEAR); + this.dispatchEvent(clearEvent); + this.changed(); +}; /** - * @const - * @type {number} + * Iterate through all features on the source, calling the provided callback + * with each one. If the callback returns any "truthy" value, iteration will + * stop and the function will return the same value. + * + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * on the source. Return a truthy value to stop iteration. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api stable */ -goog.webgl.VERTEX_ATTRIB_ARRAY_TYPE = 0x8625; +ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) { + if (!goog.isNull(this.featuresRtree_)) { + return this.featuresRtree_.forEach(callback, opt_this); + } else if (!goog.isNull(this.featuresCollection_)) { + return this.featuresCollection_.forEach(callback, opt_this); + } +}; /** - * @const - * @type {number} + * Iterate through all features whose geometries contain the provided + * coordinate, calling the callback with each feature. If the callback returns + * a "truthy" value, iteration will stop and the function will return the same + * value. + * + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose goemetry contains the provided coordinate. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S */ -goog.webgl.VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A; +ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = + function(coordinate, callback, opt_this) { + var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]]; + return this.forEachFeatureInExtent(extent, function(feature) { + var geometry = feature.getGeometry(); + goog.asserts.assert(goog.isDefAndNotNull(geometry), + 'feature geometry is defined and not null'); + if (geometry.containsCoordinate(coordinate)) { + return callback.call(opt_this, feature); + } else { + return undefined; + } + }); +}; /** - * @const - * @type {number} + * Iterate through all features whose bounding box intersects the provided + * extent (note that the feature's geometry may not intersect the extent), + * calling the callback with each feature. If the callback returns a "truthy" + * value, iteration will stop and the function will return the same value. + * + * If you are interested in features whose geometry intersects an extent, call + * the {@link ol.source.Vector#forEachFeatureIntersectingExtent + * source.forEachFeatureIntersectingExtent()} method instead. + * + * When `useSpatialIndex` is set to false, this method will loop through all + * features, equivalent to {@link ol.source.Vector#forEachFeature}. + * + * @param {ol.Extent} extent Extent. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose bounding box intersects the provided extent. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api */ -goog.webgl.VERTEX_ATTRIB_ARRAY_POINTER = 0x8645; +ol.source.Vector.prototype.forEachFeatureInExtent = + function(extent, callback, opt_this) { + if (!goog.isNull(this.featuresRtree_)) { + return this.featuresRtree_.forEachInExtent(extent, callback, opt_this); + } else if (!goog.isNull(this.featuresCollection_)) { + return this.featuresCollection_.forEach(callback, opt_this); + } +}; /** - * @const - * @type {number} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {function(this: T, ol.Feature): S} f Callback. + * @param {T=} opt_this The object to use as `this` in `f`. + * @return {S|undefined} + * @template T,S */ -goog.webgl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F; +ol.source.Vector.prototype.forEachFeatureInExtentAtResolution = + function(extent, resolution, f, opt_this) { + return this.forEachFeatureInExtent(extent, f, opt_this); +}; /** - * @const - * @type {number} + * Iterate through all features whose geometry intersects the provided extent, + * calling the callback with each feature. If the callback returns a "truthy" + * value, iteration will stop and the function will return the same value. + * + * If you only want to test for bounding box intersection, call the + * {@link ol.source.Vector#forEachFeatureInExtent + * source.forEachFeatureInExtent()} method instead. + * + * @param {ol.Extent} extent Extent. + * @param {function(this: T, ol.Feature): S} callback Called with each feature + * whose geometry intersects the provided extent. + * @param {T=} opt_this The object to use as `this` in the callback. + * @return {S|undefined} The return value from the last call to the callback. + * @template T,S + * @api */ -goog.webgl.COMPILE_STATUS = 0x8B81; +ol.source.Vector.prototype.forEachFeatureIntersectingExtent = + function(extent, callback, opt_this) { + return this.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + * @return {S|undefined} + * @template S + */ + function(feature) { + var geometry = feature.getGeometry(); + goog.asserts.assert(goog.isDefAndNotNull(geometry), + 'feature geometry is defined and not null'); + if (geometry.intersectsExtent(extent)) { + var result = callback.call(opt_this, feature); + if (result) { + return result; + } + } + }); +}; /** - * @const - * @type {number} + * Get the features collection associated with this source. Will be `null` + * unless the source was configured with `useSpatialIndex` set to `false`, or + * with an {@link ol.Collection} as `features`. + * @return {ol.Collection.<ol.Feature>} + * @api */ -goog.webgl.LOW_FLOAT = 0x8DF0; +ol.source.Vector.prototype.getFeaturesCollection = function() { + return this.featuresCollection_; +}; /** - * @const - * @type {number} + * Get all features on the source. + * @return {Array.<ol.Feature>} Features. + * @api stable */ -goog.webgl.MEDIUM_FLOAT = 0x8DF1; +ol.source.Vector.prototype.getFeatures = function() { + var features; + if (!goog.isNull(this.featuresCollection_)) { + features = this.featuresCollection_.getArray(); + } else if (!goog.isNull(this.featuresRtree_)) { + features = this.featuresRtree_.getAll(); + if (!goog.object.isEmpty(this.nullGeometryFeatures_)) { + goog.array.extend( + features, goog.object.getValues(this.nullGeometryFeatures_)); + } + } + goog.asserts.assert(goog.isDef(features), + 'Neither featuresRtree_ nor featuresCollection_ are available'); + return features; +}; /** - * @const - * @type {number} + * Get all features whose geometry intersects the provided coordinate. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {Array.<ol.Feature>} Features. + * @api stable */ -goog.webgl.HIGH_FLOAT = 0x8DF2; +ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) { + var features = []; + this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) { + features.push(feature); + }); + return features; +}; /** - * @const - * @type {number} + * Get all features in the provided extent. Note that this returns all features + * whose bounding boxes intersect the given extent (so it may include features + * whose geometries do not intersect the extent). + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @param {ol.Extent} extent Extent. + * @return {Array.<ol.Feature>} Features. + * @api */ -goog.webgl.LOW_INT = 0x8DF3; +ol.source.Vector.prototype.getFeaturesInExtent = function(extent) { + goog.asserts.assert(!goog.isNull(this.featuresRtree_), + 'getFeaturesInExtent does not work when useSpatialIndex is set to false'); + return this.featuresRtree_.getInExtent(extent); +}; /** - * @const - * @type {number} + * Get the closest feature to the provided coordinate. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {ol.Feature} Closest feature. + * @api stable */ -goog.webgl.MEDIUM_INT = 0x8DF4; +ol.source.Vector.prototype.getClosestFeatureToCoordinate = + function(coordinate) { + // Find the closest feature using branch and bound. We start searching an + // infinite extent, and find the distance from the first feature found. This + // becomes the closest feature. We then compute a smaller extent which any + // closer feature must intersect. We continue searching with this smaller + // extent, trying to find a closer feature. Every time we find a closer + // feature, we update the extent being searched so that any even closer + // feature must intersect it. We continue until we run out of features. + var x = coordinate[0]; + var y = coordinate[1]; + var closestFeature = null; + var closestPoint = [NaN, NaN]; + var minSquaredDistance = Infinity; + var extent = [-Infinity, -Infinity, Infinity, Infinity]; + goog.asserts.assert(!goog.isNull(this.featuresRtree_), + 'getClosestFeatureToCoordinate does not work with useSpatialIndex set ' + + 'to false'); + this.featuresRtree_.forEachInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + var geometry = feature.getGeometry(); + goog.asserts.assert(goog.isDefAndNotNull(geometry), + 'feature geometry is defined and not null'); + var previousMinSquaredDistance = minSquaredDistance; + minSquaredDistance = geometry.closestPointXY( + x, y, closestPoint, minSquaredDistance); + if (minSquaredDistance < previousMinSquaredDistance) { + closestFeature = feature; + // This is sneaky. Reduce the extent that it is currently being + // searched while the R-Tree traversal using this same extent object + // is still in progress. This is safe because the new extent is + // strictly contained by the old extent. + var minDistance = Math.sqrt(minSquaredDistance); + extent[0] = x - minDistance; + extent[1] = y - minDistance; + extent[2] = x + minDistance; + extent[3] = y + minDistance; + } + }); + return closestFeature; +}; /** - * @const - * @type {number} + * Get the extent of the features currently in the source. + * + * This method is not available when the source is configured with + * `useSpatialIndex` set to `false`. + * @return {ol.Extent} Extent. + * @api stable */ -goog.webgl.HIGH_INT = 0x8DF5; +ol.source.Vector.prototype.getExtent = function() { + goog.asserts.assert(!goog.isNull(this.featuresRtree_), + 'getExtent does not work when useSpatialIndex is set to false'); + return this.featuresRtree_.getExtent(); +}; /** - * @const - * @type {number} + * Get a feature by its identifier (the value returned by feature.getId()). + * Note that the index treats string and numeric identifiers as the same. So + * `source.getFeatureById(2)` will return a feature with id `'2'` or `2`. + * + * @param {string|number} id Feature identifier. + * @return {ol.Feature} The feature (or `null` if not found). + * @api stable */ -goog.webgl.FRAMEBUFFER = 0x8D40; +ol.source.Vector.prototype.getFeatureById = function(id) { + var feature = this.idIndex_[id.toString()]; + return goog.isDef(feature) ? feature : null; +}; /** - * @const - * @type {number} + * @param {goog.events.Event} event Event. + * @private */ -goog.webgl.RENDERBUFFER = 0x8D41; +ol.source.Vector.prototype.handleFeatureChange_ = function(event) { + var feature = /** @type {ol.Feature} */ (event.target); + var featureKey = goog.getUid(feature).toString(); + var geometry = feature.getGeometry(); + if (!goog.isDefAndNotNull(geometry)) { + if (!(featureKey in this.nullGeometryFeatures_)) { + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.remove(feature); + } + this.nullGeometryFeatures_[featureKey] = feature; + } + } else { + var extent = geometry.getExtent(); + if (featureKey in this.nullGeometryFeatures_) { + delete this.nullGeometryFeatures_[featureKey]; + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.insert(extent, feature); + } + } else { + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.update(extent, feature); + } + } + } + var id = feature.getId(); + var removed; + if (goog.isDef(id)) { + var sid = id.toString(); + if (featureKey in this.undefIdIndex_) { + delete this.undefIdIndex_[featureKey]; + this.idIndex_[sid] = feature; + } else { + if (this.idIndex_[sid] !== feature) { + removed = this.removeFromIdIndex_(feature); + goog.asserts.assert(removed, + 'Expected feature to be removed from index'); + this.idIndex_[sid] = feature; + } + } + } else { + if (!(featureKey in this.undefIdIndex_)) { + removed = this.removeFromIdIndex_(feature); + goog.asserts.assert(removed, + 'Expected feature to be removed from index'); + this.undefIdIndex_[featureKey] = feature; + } else { + goog.asserts.assert(this.undefIdIndex_[featureKey] === feature, + 'feature keyed under %s in undefIdKeys', featureKey); + } + } + this.changed(); + this.dispatchEvent(new ol.source.VectorEvent( + ol.source.VectorEventType.CHANGEFEATURE, feature)); +}; /** - * @const - * @type {number} + * @return {boolean} Is empty. */ -goog.webgl.RGBA4 = 0x8056; +ol.source.Vector.prototype.isEmpty = function() { + return this.featuresRtree_.isEmpty() && + goog.object.isEmpty(this.nullGeometryFeatures_); +}; /** - * @const - * @type {number} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.proj.Projection} projection Projection. */ -goog.webgl.RGB5_A1 = 0x8057; +ol.source.Vector.prototype.loadFeatures = function( + extent, resolution, projection) { + var loadedExtentsRtree = this.loadedExtentsRtree_; + var extentsToLoad = this.strategy_(extent, resolution); + var i, ii; + for (i = 0, ii = extentsToLoad.length; i < ii; ++i) { + var extentToLoad = extentsToLoad[i]; + var alreadyLoaded = loadedExtentsRtree.forEachInExtent(extentToLoad, + /** + * @param {{extent: ol.Extent}} object Object. + * @return {boolean} Contains. + */ + function(object) { + return ol.extent.containsExtent(object.extent, extentToLoad); + }); + if (!alreadyLoaded) { + this.loader_.call(this, extentToLoad, resolution, projection); + loadedExtentsRtree.insert(extentToLoad, {extent: extentToLoad.slice()}); + } + } +}; /** - * @const - * @type {number} + * Remove a single feature from the source. If you want to remove all features + * at once, use the {@link ol.source.Vector#clear source.clear()} method + * instead. + * @param {ol.Feature} feature Feature to remove. + * @api stable */ -goog.webgl.RGB565 = 0x8D62; +ol.source.Vector.prototype.removeFeature = function(feature) { + var featureKey = goog.getUid(feature).toString(); + if (featureKey in this.nullGeometryFeatures_) { + delete this.nullGeometryFeatures_[featureKey]; + } else { + if (!goog.isNull(this.featuresRtree_)) { + this.featuresRtree_.remove(feature); + } + } + this.removeFeatureInternal(feature); + this.changed(); +}; /** - * @const - * @type {number} + * Remove feature without firing a `change` event. + * @param {ol.Feature} feature Feature. + * @protected */ -goog.webgl.DEPTH_COMPONENT16 = 0x81A5; +ol.source.Vector.prototype.removeFeatureInternal = function(feature) { + var featureKey = goog.getUid(feature).toString(); + goog.asserts.assert(featureKey in this.featureChangeKeys_, + 'featureKey exists in featureChangeKeys'); + goog.array.forEach(this.featureChangeKeys_[featureKey], + goog.events.unlistenByKey); + delete this.featureChangeKeys_[featureKey]; + var id = feature.getId(); + if (goog.isDef(id)) { + delete this.idIndex_[id.toString()]; + } else { + delete this.undefIdIndex_[featureKey]; + } + this.dispatchEvent(new ol.source.VectorEvent( + ol.source.VectorEventType.REMOVEFEATURE, feature)); +}; /** - * @const - * @type {number} + * Remove a feature from the id index. Called internally when the feature id + * may have changed. + * @param {ol.Feature} feature The feature. + * @return {boolean} Removed the feature from the index. + * @private */ -goog.webgl.STENCIL_INDEX = 0x1901; +ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) { + var removed = false; + for (var id in this.idIndex_) { + if (this.idIndex_[id] === feature) { + delete this.idIndex_[id]; + removed = true; + break; + } + } + return removed; +}; + /** - * @const - * @type {number} + * @classdesc + * Events emitted by {@link ol.source.Vector} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.source.VectorEvent} + * @param {string} type Type. + * @param {ol.Feature=} opt_feature Feature. */ -goog.webgl.STENCIL_INDEX8 = 0x8D48; +ol.source.VectorEvent = function(type, opt_feature) { + goog.base(this, type); -/** - * @const - * @type {number} - */ -goog.webgl.DEPTH_STENCIL = 0x84F9; + /** + * The feature being added or removed. + * @type {ol.Feature|undefined} + * @api stable + */ + this.feature = opt_feature; +}; +goog.inherits(ol.source.VectorEvent, goog.events.Event); -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_WIDTH = 0x8D42; +goog.provide('ol.source.ImageVector'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.vec.Mat4'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.source.ImageCanvas'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); +goog.require('ol.vec.Mat4'); -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_HEIGHT = 0x8D43; /** - * @const - * @type {number} + * @classdesc + * An image source whose images are canvas elements into which vector features + * read from a vector source (`ol.source.Vector`) are drawn. An + * `ol.source.ImageVector` object is to be used as the `source` of an image + * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated, + * as opposed to being re-rendered, during animations and interactions. So, like + * any other image layer, an image layer configured with an + * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a + * vector layer, where vector features are re-drawn during animations and + * interactions. + * + * @constructor + * @extends {ol.source.ImageCanvas} + * @param {olx.source.ImageVectorOptions} options Options. + * @api */ -goog.webgl.RENDERBUFFER_INTERNAL_FORMAT = 0x8D44; +ol.source.ImageVector = function(options) { + /** + * @private + * @type {ol.source.Vector} + */ + this.source_ = options.source; -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_RED_SIZE = 0x8D50; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.transform_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.canvasContext_ = ol.dom.createCanvasContext2D(); -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_GREEN_SIZE = 0x8D51; + /** + * @private + * @type {ol.Size} + */ + this.canvasSize_ = [0, 0]; + + /** + * @private + * @type {ol.render.canvas.ReplayGroup} + */ + this.replayGroup_ = null; + goog.base(this, { + attributions: options.attributions, + canvasFunction: goog.bind(this.canvasFunctionInternal_, this), + logo: options.logo, + projection: options.projection, + ratio: options.ratio, + resolutions: options.resolutions, + state: this.source_.getState() + }); -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_BLUE_SIZE = 0x8D52; + /** + * User provided style. + * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} + * @private + */ + this.style_ = null; + /** + * Style function for use within the library. + * @type {ol.style.StyleFunction|undefined} + * @private + */ + this.styleFunction_ = undefined; -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_ALPHA_SIZE = 0x8D53; + this.setStyle(options.style); + goog.events.listen(this.source_, goog.events.EventType.CHANGE, + this.handleSourceChange_, undefined, this); -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_DEPTH_SIZE = 0x8D54; +}; +goog.inherits(ol.source.ImageVector, ol.source.ImageCanvas); /** - * @const - * @type {number} + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection; + * @return {HTMLCanvasElement} Canvas element. + * @private */ -goog.webgl.RENDERBUFFER_STENCIL_SIZE = 0x8D55; +ol.source.ImageVector.prototype.canvasFunctionInternal_ = + function(extent, resolution, pixelRatio, size, projection) { + var replayGroup = new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution); -/** - * @const - * @type {number} - */ -goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0; + this.source_.loadFeatures(extent, resolution, projection); + var loading = false; + this.source_.forEachFeatureInExtentAtResolution(extent, resolution, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + loading = loading || + this.renderFeature_(feature, resolution, pixelRatio, replayGroup); + }, this); + replayGroup.finish(); -/** - * @const - * @type {number} - */ -goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1; + if (loading) { + return null; + } + if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) { + this.canvasContext_.canvas.width = size[0]; + this.canvasContext_.canvas.height = size[1]; + this.canvasSize_[0] = size[0]; + this.canvasSize_[1] = size[1]; + } else { + this.canvasContext_.clearRect(0, 0, size[0], size[1]); + } -/** - * @const - * @type {number} - */ -goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2; + var transform = this.getTransform_(ol.extent.getCenter(extent), + resolution, pixelRatio, size); + replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {}); + this.replayGroup_ = replayGroup; -/** - * @const - * @type {number} - */ -goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3; + return this.canvasContext_.canvas; +}; /** - * @const - * @type {number} + * @inheritDoc */ -goog.webgl.COLOR_ATTACHMENT0 = 0x8CE0; +ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, skippedFeatureUids, callback) { + if (goog.isNull(this.replayGroup_)) { + return undefined; + } else { + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate( + coordinate, resolution, 0, skippedFeatureUids, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + goog.asserts.assert(goog.isDef(feature), 'passed a feature'); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback(feature); + } + }); + } +}; /** - * @const - * @type {number} + * Get a reference to the wrapped source. + * @return {ol.source.Vector} Source. + * @api */ -goog.webgl.DEPTH_ATTACHMENT = 0x8D00; +ol.source.ImageVector.prototype.getSource = function() { + return this.source_; +}; /** - * @const - * @type {number} + * Get the style for features. This returns whatever was passed to the `style` + * option at construction or to the `setStyle` method. + * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} + * Layer style. + * @api stable */ -goog.webgl.STENCIL_ATTACHMENT = 0x8D20; +ol.source.ImageVector.prototype.getStyle = function() { + return this.style_; +}; /** - * @const - * @type {number} + * Get the style function. + * @return {ol.style.StyleFunction|undefined} Layer style function. + * @api stable */ -goog.webgl.DEPTH_STENCIL_ATTACHMENT = 0x821A; +ol.source.ImageVector.prototype.getStyleFunction = function() { + return this.styleFunction_; +}; /** - * @const - * @type {number} + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Size} size Size. + * @return {!goog.vec.Mat4.Number} Transform. + * @private */ -goog.webgl.NONE = 0; +ol.source.ImageVector.prototype.getTransform_ = + function(center, resolution, pixelRatio, size) { + return ol.vec.Mat4.makeTransform2D(this.transform_, + size[0] / 2, size[1] / 2, + pixelRatio / resolution, -pixelRatio / resolution, + 0, + -center[0], -center[1]); +}; /** - * @const - * @type {number} + * Handle changes in image style state. + * @param {goog.events.Event} event Image style change event. + * @private */ -goog.webgl.FRAMEBUFFER_COMPLETE = 0x8CD5; +ol.source.ImageVector.prototype.handleImageChange_ = + function(event) { + this.changed(); +}; /** - * @const - * @type {number} + * @private */ -goog.webgl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6; +ol.source.ImageVector.prototype.handleSourceChange_ = function() { + // setState will trigger a CHANGE event, so we always rely + // change events by calling setState. + this.setState(this.source_.getState()); +}; /** - * @const - * @type {number} + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + * @private */ -goog.webgl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7; +ol.source.ImageVector.prototype.renderFeature_ = + function(feature, resolution, pixelRatio, replayGroup) { + var styles; + if (goog.isDef(feature.getStyleFunction())) { + styles = feature.getStyleFunction().call(feature, resolution); + } else if (goog.isDef(this.styleFunction_)) { + styles = this.styleFunction_(feature, resolution); + } + if (!goog.isDefAndNotNull(styles)) { + return false; + } + var i, ii, loading = false; + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleImageChange_, this) || loading; + } + return loading; +}; /** - * @const - * @type {number} + * Set the style for features. This can be a single style object, an array + * of styles, or a function that takes a feature and resolution and returns + * an array of styles. If it is `undefined` the default style is used. If + * it is `null` the layer has no style (a `null` style), so only features + * that have their own styles will be rendered in the layer. See + * {@link ol.style} for information on the default style. + * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined} + * style Layer style. + * @api stable */ -goog.webgl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9; +ol.source.ImageVector.prototype.setStyle = function(style) { + this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction; + this.styleFunction_ = goog.isNull(style) ? + undefined : ol.style.createStyleFunction(this.style_); + this.changed(); +}; + +goog.provide('ol.renderer.canvas.ImageLayer'); +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.vec.Mat4'); +goog.require('ol.ImageBase'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Image'); +goog.require('ol.proj'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.source.ImageVector'); +goog.require('ol.vec.Mat4'); -/** - * @const - * @type {number} - */ -goog.webgl.FRAMEBUFFER_UNSUPPORTED = 0x8CDD; /** - * @const - * @type {number} + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Image} imageLayer Single image layer. */ -goog.webgl.FRAMEBUFFER_BINDING = 0x8CA6; +ol.renderer.canvas.ImageLayer = function(imageLayer) { + goog.base(this, imageLayer); -/** - * @const - * @type {number} - */ -goog.webgl.RENDERBUFFER_BINDING = 0x8CA7; + /** + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.imageTransform_ = goog.vec.Mat4.createNumber(); -/** - * @const - * @type {number} - */ -goog.webgl.MAX_RENDERBUFFER_SIZE = 0x84E8; + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.imageTransformInv_ = null; + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; -/** - * @const - * @type {number} - */ -goog.webgl.INVALID_FRAMEBUFFER_OPERATION = 0x0506; +}; +goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer); /** - * @const - * @type {number} + * @inheritDoc */ -goog.webgl.UNPACK_FLIP_Y_WEBGL = 0x9240; +ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, skippedFeatureUids, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; /** - * @const - * @type {number} + * @inheritDoc */ -goog.webgl.UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; +ol.renderer.canvas.ImageLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.getImage())) { + return undefined; + } + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = pixel.slice(); + ol.vec.Mat4.multVec2( + frameState.pixelToCoordinateMatrix, coordinate, coordinate); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); -/** - * @const - * @type {number} - */ -goog.webgl.CONTEXT_LOST_WEBGL = 0x9242; + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } else { + // for all other image sources directly check the image + if (goog.isNull(this.imageTransformInv_)) { + this.imageTransformInv_ = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_); + } + var pixelOnCanvas = + this.getPixelOnCanvas(pixel, this.imageTransformInv_); -/** - * @const - * @type {number} - */ -goog.webgl.UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243; + if (goog.isNull(this.hitCanvasContext_)) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage( + this.getImage(), pixelOnCanvas[0], pixelOnCanvas[1], 1, 1, 0, 0, 1, 1); -/** - * @const - * @type {number} - */ -goog.webgl.BROWSER_DEFAULT_WEBGL = 0x9244; + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } +}; /** - * From the OES_texture_half_float extension. - * http://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/ - * @const - * @type {number} + * @inheritDoc */ -goog.webgl.HALF_FLOAT_OES = 0x8D61; +ol.renderer.canvas.ImageLayer.prototype.getImage = function() { + return goog.isNull(this.image_) ? + null : this.image_.getImage(); +}; /** - * From the OES_standard_derivatives extension. - * http://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/ - * @const - * @type {number} + * @inheritDoc */ -goog.webgl.FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B; +ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() { + return this.imageTransform_; +}; /** - * From the OES_vertex_array_object extension. - * http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ - * @const - * @type {number} + * @inheritDoc */ -goog.webgl.VERTEX_ARRAY_BINDING_OES = 0x85B5; - +ol.renderer.canvas.ImageLayer.prototype.prepareFrame = + function(frameState, layerState) { -/** - * From the WEBGL_debug_renderer_info extension. - * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/ - * @const - * @type {number} - */ -goog.webgl.UNMASKED_VENDOR_WEBGL = 0x9245; + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; + var image; + var imageLayer = this.getLayer(); + goog.asserts.assertInstanceof(imageLayer, ol.layer.Image, + 'layer is an instance of ol.layer.Image'); + var imageSource = imageLayer.getSource(); -/** - * From the WEBGL_debug_renderer_info extension. - * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/ - * @const - * @type {number} - */ -goog.webgl.UNMASKED_RENDERER_WEBGL = 0x9246; + var hints = frameState.viewHints; + var renderedExtent = frameState.extent; + if (goog.isDef(layerState.extent)) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } -/** - * From the WEBGL_compressed_texture_s3tc extension. - * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ - * @const - * @type {number} - */ -goog.webgl.COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + var sourceProjection = imageSource.getProjection(); + if (!goog.isNull(sourceProjection)) { + goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + image = imageSource.getImage( + renderedExtent, viewResolution, pixelRatio, projection); + if (!goog.isNull(image)) { + var loaded = this.loadImage(image); + if (loaded) { + this.image_ = image; + } + } + } + if (!goog.isNull(this.image_)) { + image = this.image_; + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var imagePixelRatio = image.getPixelRatio(); + var scale = pixelRatio * imageResolution / + (viewResolution * imagePixelRatio); + ol.vec.Mat4.makeTransform2D(this.imageTransform_, + pixelRatio * frameState.size[0] / 2, + pixelRatio * frameState.size[1] / 2, + scale, scale, + viewRotation, + imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, + imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + this.imageTransformInv_ = null; + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } -/** - * From the WEBGL_compressed_texture_s3tc extension. - * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ - * @const - * @type {number} - */ -goog.webgl.COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; + return true; +}; +// FIXME find correct globalCompositeOperation +// FIXME optimize :-) -/** - * From the WEBGL_compressed_texture_s3tc extension. - * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ - * @const - * @type {number} - */ -goog.webgl.COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; +goog.provide('ol.renderer.canvas.TileLayer'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('goog.vec.Mat4'); +goog.require('ol.Size'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Tile'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); +goog.require('ol.vec.Mat4'); -/** - * From the WEBGL_compressed_texture_s3tc extension. - * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ - * @const - * @type {number} - */ -goog.webgl.COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; /** - * From the EXT_texture_filter_anisotropic extension. - * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ - * @const - * @type {number} + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Tile} tileLayer Tile layer. */ -goog.webgl.TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE; +ol.renderer.canvas.TileLayer = function(tileLayer) { + goog.base(this, tileLayer); -/** - * From the EXT_texture_filter_anisotropic extension. - * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ - * @const - * @type {number} - */ -goog.webgl.MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF; + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = null; -goog.provide('ol.webgl.shader'); + /** + * @private + * @type {ol.Size} + */ + this.canvasSize_ = null; -goog.require('goog.functions'); -goog.require('goog.webgl'); -goog.require('ol.webgl'); + /** + * @private + * @type {boolean} + */ + this.canvasTooBig_ = false; + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = null; + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.imageTransform_ = goog.vec.Mat4.createNumber(); -/** - * @constructor - * @param {string} source Source. - * @struct - */ -ol.webgl.Shader = function(source) { + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.imageTransformInv_ = null; /** * @private - * @type {string} + * @type {number} */ - this.source_ = source; + this.renderedCanvasZ_ = NaN; -}; + /** + * @private + * @type {number} + */ + this.renderedTileWidth_ = NaN; + /** + * @private + * @type {number} + */ + this.renderedTileHeight_ = NaN; -/** - * @return {number} Type. - */ -ol.webgl.Shader.prototype.getType = goog.abstractMethod; + /** + * @private + * @type {ol.TileRange} + */ + this.renderedCanvasTileRange_ = null; + + /** + * @private + * @type {Array.<ol.Tile|undefined>} + */ + this.renderedTiles_ = null; + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; -/** - * @return {string} Source. - */ -ol.webgl.Shader.prototype.getSource = function() { - return this.source_; }; +goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer); /** - * @return {boolean} Is animated? + * @inheritDoc */ -ol.webgl.Shader.prototype.isAnimated = goog.functions.FALSE; - +ol.renderer.canvas.TileLayer.prototype.getImage = function() { + return this.canvas_; +}; /** - * @constructor - * @extends {ol.webgl.Shader} - * @param {string} source Source. - * @struct + * @inheritDoc */ -ol.webgl.shader.Fragment = function(source) { - goog.base(this, source); +ol.renderer.canvas.TileLayer.prototype.getImageTransform = function() { + return this.imageTransform_; }; -goog.inherits(ol.webgl.shader.Fragment, ol.webgl.Shader); /** * @inheritDoc */ -ol.webgl.shader.Fragment.prototype.getType = function() { - return goog.webgl.FRAGMENT_SHADER; -}; +ol.renderer.canvas.TileLayer.prototype.prepareFrame = + function(frameState, layerState) { + // + // Warning! You're entering a dangerous zone! + // + // The canvas tile layer renderering is highly optimized, hence + // the complexity of this function. For best performance we try + // to minimize the number of pixels to update on the canvas. This + // includes: + // + // - Only drawing pixels that will be visible. + // - Not re-drawing pixels/tiles that are already correct. + // - Minimizing calls to clearRect. + // - Never shrink the canvas. Just make it bigger when necessary. + // Re-sizing the canvas also clears it, which further means + // re-creating it (expensive). + // + // The various steps performed by this functions: + // + // - Create a canvas element if none has been created yet. + // + // - Make the canvas bigger if it's too small. Note that we never shrink + // the canvas, we just make it bigger when necessary, when rotating for + // example. Note also that the canvas always contains a whole number + // of tiles. + // + // - Invalidate the canvas tile range (renderedCanvasTileRange_ = null) + // if (1) the canvas has been enlarged, or (2) the zoom level changes, + // or (3) the canvas tile range doesn't contain the required tile + // range. This canvas tile range invalidation thing is related to + // an optimization where we attempt to redraw as few pixels as + // possible on each prepareFrame call. + // + // - If the canvas tile range has been invalidated we reset + // renderedCanvasTileRange_ and reset the renderedTiles_ array. + // The renderedTiles_ array is the structure used to determine + // the canvas pixels that need not be redrawn from one prepareFrame + // call to another. It records while tile has been rendered at + // which position in the canvas. + // + // - We then determine the tiles to draw on the canvas. Tiles for + // the target resolution may not be loaded yet. In that case we + // use low-resolution/interim tiles if loaded already. And, if + // for a non-yet-loaded tile we haven't found a corresponding + // low-resolution tile we indicate that the pixels for that + // tile must be cleared on the canvas. Note: determining the + // interim tiles is based on tile extents instead of tile + // coords, this is to be able to handler irregular tile grids. + // + // - We're now ready to render. We start by calling clearRect + // for the tiles that aren't loaded yet and are not fully covered + // by a low-resolution tile (if they're loaded, we'll draw them; + // if they're fully covered by a low-resolution tile then there's + // no need to clear). We then render the tiles "back to front", + // i.e. starting with the low-resolution tiles. + // + // - After rendering some bookkeeping is performed (updateUsedTiles, + // etc.). manageTilePyramid is what enqueue tiles in the tile + // queue for loading. + // + // - The last step involves updating the image transform matrix, + // which will be used by the map renderer for the final + // composition and positioning. + // + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var projection = viewState.projection; -/** - * @constructor - * @extends {ol.webgl.Shader} - * @param {string} source Source. - * @struct - */ -ol.webgl.shader.Vertex = function(source) { - goog.base(this, source); -}; -goog.inherits(ol.webgl.shader.Vertex, ol.webgl.Shader); + var tileLayer = this.getLayer(); + goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile, + 'layer is an instance of ol.layer.Tile'); + var tileSource = tileLayer.getSource(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var tileGutter = tileSource.getGutter(); + var z = tileGrid.getZForResolution(viewState.resolution); + var tilePixelSize = + tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); + var tilePixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; + var tileResolution = tileGrid.getResolution(z); + var tilePixelResolution = tileResolution / tilePixelRatio; + var center = viewState.center; + var extent; + if (tileResolution == viewState.resolution) { + center = this.snapCenterToPixel(center, tileResolution, frameState.size); + extent = ol.extent.getForViewAndSize( + center, tileResolution, viewState.rotation, frameState.size); + } else { + extent = frameState.extent; + } + if (goog.isDef(layerState.extent)) { + extent = ol.extent.getIntersection(extent, layerState.extent); + } + if (ol.extent.isEmpty(extent)) { + // Return false to prevent the rendering of the layer. + return false; + } -/** - * @inheritDoc - */ -ol.webgl.shader.Vertex.prototype.getType = function() { - return goog.webgl.VERTEX_SHADER; -}; + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); -// This file is automatically generated, do not edit -goog.provide('ol.render.webgl.imagereplay.shader.Color'); + var canvasWidth = tilePixelSize[0] * tileRange.getWidth(); + var canvasHeight = tilePixelSize[1] * tileRange.getHeight(); -goog.require('ol.webgl.shader'); + var canvas, context; + if (goog.isNull(this.canvas_)) { + goog.asserts.assert(goog.isNull(this.canvasSize_), + 'canvasSize is null (because canvas is null)'); + goog.asserts.assert(goog.isNull(this.context_), + 'context is null (because canvas is null)'); + goog.asserts.assert(goog.isNull(this.renderedCanvasTileRange_), + 'renderedCanvasTileRange is null (because canvas is null)'); + context = ol.dom.createCanvasContext2D(canvasWidth, canvasHeight); + this.canvas_ = context.canvas; + this.canvasSize_ = [canvasWidth, canvasHeight]; + this.context_ = context; + this.canvasTooBig_ = + !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_); + } else { + goog.asserts.assert(!goog.isNull(this.canvasSize_), + 'non-null canvasSize (because canvas is not null)'); + goog.asserts.assert(!goog.isNull(this.context_), + 'non-null context (because canvas is not null)'); + canvas = this.canvas_; + context = this.context_; + if (this.canvasSize_[0] < canvasWidth || + this.canvasSize_[1] < canvasHeight || + this.renderedTileWidth_ !== tilePixelSize[0] || + this.renderedTileHeight_ !== tilePixelSize[1] || + (this.canvasTooBig_ && (this.canvasSize_[0] > canvasWidth || + this.canvasSize_[1] > canvasHeight))) { + // Canvas is too small or tileSize has changed, resize it. + // We never shrink the canvas, unless + // we know that the current canvas size exceeds the maximum size + canvas.width = canvasWidth; + canvas.height = canvasHeight; + this.canvasSize_ = [canvasWidth, canvasHeight]; + this.canvasTooBig_ = + !ol.renderer.canvas.Layer.testCanvasSize(this.canvasSize_); + this.renderedCanvasTileRange_ = null; + } else { + canvasWidth = this.canvasSize_[0]; + canvasHeight = this.canvasSize_[1]; + if (z != this.renderedCanvasZ_ || + !this.renderedCanvasTileRange_.containsTileRange(tileRange)) { + this.renderedCanvasTileRange_ = null; + } + } + } + var canvasTileRange, canvasTileRangeWidth, minX, minY; + if (goog.isNull(this.renderedCanvasTileRange_)) { + canvasTileRangeWidth = canvasWidth / tilePixelSize[0]; + var canvasTileRangeHeight = canvasHeight / tilePixelSize[1]; + minX = tileRange.minX - + Math.floor((canvasTileRangeWidth - tileRange.getWidth()) / 2); + minY = tileRange.minY - + Math.floor((canvasTileRangeHeight - tileRange.getHeight()) / 2); + this.renderedCanvasZ_ = z; + this.renderedTileWidth_ = tilePixelSize[0]; + this.renderedTileHeight_ = tilePixelSize[1]; + this.renderedCanvasTileRange_ = new ol.TileRange( + minX, minX + canvasTileRangeWidth - 1, + minY, minY + canvasTileRangeHeight - 1); + this.renderedTiles_ = + new Array(canvasTileRangeWidth * canvasTileRangeHeight); + canvasTileRange = this.renderedCanvasTileRange_; + } else { + canvasTileRange = this.renderedCanvasTileRange_; + canvasTileRangeWidth = canvasTileRange.getWidth(); + } + goog.asserts.assert(canvasTileRange.containsTileRange(tileRange), + 'tileRange is contained in canvasTileRange'); -/** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct - */ -ol.render.webgl.imagereplay.shader.ColorFragment = function() { - goog.base(this, ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE); -}; -goog.inherits(ol.render.webgl.imagereplay.shader.ColorFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorFragment); + /** + * @type {Object.<number, Object.<string, ol.Tile>>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + /** @type {Array.<ol.Tile>} */ + var tilesToClear = []; + var findLoadedTiles = this.createLoadedTileFinder(tileSource, tilesToDrawByZ); -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n}\n'; + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var tmpExtent = ol.extent.createEmpty(); + var tmpTileRange = new ol.TileRange(0, 0, 0, 0); + var childTileRange, fullyLoaded, tile, tileState, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}'; + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + tileState = tile.getState(); + if (tileState == ol.TileState.LOADED || + tileState == ol.TileState.EMPTY || + (tileState == ol.TileState.ERROR && !useInterimTilesOnError)) { + tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile; + continue; + } + fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + // FIXME we do not need to clear the tile if it is fully covered by its + // children + tilesToClear.push(tile); + childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (!goog.isNull(childTileRange)) { + findLoadedTiles(z + 1, childTileRange); + } + } -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE = goog.DEBUG ? - ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE : - ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE; + } + } + var i, ii; + for (i = 0, ii = tilesToClear.length; i < ii; ++i) { + tile = tilesToClear[i]; + x = tilePixelSize[0] * (tile.tileCoord[1] - canvasTileRange.minX); + y = tilePixelSize[1] * (canvasTileRange.maxY - tile.tileCoord[2]); + context.clearRect(x, y, tilePixelSize[0], tilePixelSize[1]); + } + /** @type {Array.<number>} */ + var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); + goog.array.sort(zs); + var opaque = tileSource.getOpaque(); + var origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent( + [z, canvasTileRange.minX, canvasTileRange.maxY], + tmpExtent)); + var currentZ, index, scale, tileCoordKey, tileExtent, tilesToDraw; + var ix, iy, interimTileExtent, interimTileRange, maxX, maxY; + var height, width; + for (i = 0, ii = zs.length; i < ii; ++i) { + currentZ = zs[i]; + tilePixelSize = + tileSource.getTilePixelSize(currentZ, pixelRatio, projection); + tilesToDraw = tilesToDrawByZ[currentZ]; + if (currentZ == z) { + for (tileCoordKey in tilesToDraw) { + tile = tilesToDraw[tileCoordKey]; + index = + (tile.tileCoord[2] - canvasTileRange.minY) * canvasTileRangeWidth + + (tile.tileCoord[1] - canvasTileRange.minX); + if (this.renderedTiles_[index] != tile) { + x = tilePixelSize[0] * (tile.tileCoord[1] - canvasTileRange.minX); + y = tilePixelSize[1] * (canvasTileRange.maxY - tile.tileCoord[2]); + tileState = tile.getState(); + if (tileState == ol.TileState.EMPTY || + (tileState == ol.TileState.ERROR && !useInterimTilesOnError) || + !opaque) { + context.clearRect(x, y, tilePixelSize[0], tilePixelSize[1]); + } + if (tileState == ol.TileState.LOADED) { + context.drawImage(tile.getImage(), + tileGutter, tileGutter, tilePixelSize[0], tilePixelSize[1], + x, y, tilePixelSize[0], tilePixelSize[1]); + } + this.renderedTiles_[index] = tile; + } + } + } else { + scale = tileGrid.getResolution(currentZ) / tileResolution; + for (tileCoordKey in tilesToDraw) { + tile = tilesToDraw[tileCoordKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + x = (tileExtent[0] - origin[0]) / tilePixelResolution; + y = (origin[1] - tileExtent[3]) / tilePixelResolution; + width = scale * tilePixelSize[0]; + height = scale * tilePixelSize[1]; + tileState = tile.getState(); + if (tileState == ol.TileState.EMPTY || !opaque) { + context.clearRect(x, y, width, height); + } + if (tileState == ol.TileState.LOADED) { + context.drawImage(tile.getImage(), + tileGutter, tileGutter, tilePixelSize[0], tilePixelSize[1], + x, y, width, height); + } + interimTileRange = + tileGrid.getTileRangeForExtentAndZ(tileExtent, z, tmpTileRange); + minX = Math.max(interimTileRange.minX, canvasTileRange.minX); + maxX = Math.min(interimTileRange.maxX, canvasTileRange.maxX); + minY = Math.max(interimTileRange.minY, canvasTileRange.minY); + maxY = Math.min(interimTileRange.maxY, canvasTileRange.maxY); + for (ix = minX; ix <= maxX; ++ix) { + for (iy = minY; iy <= maxY; ++iy) { + index = (iy - canvasTileRange.minY) * canvasTileRangeWidth + + (ix - canvasTileRange.minX); + this.renderedTiles_[index] = undefined; + } + } + } + } + } -/** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct - */ -ol.render.webgl.imagereplay.shader.ColorVertex = function() { - goog.base(this, ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE); + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, + projection, extent, z, tileLayer.getPreload()); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + ol.vec.Mat4.makeTransform2D(this.imageTransform_, + pixelRatio * frameState.size[0] / 2, + pixelRatio * frameState.size[1] / 2, + pixelRatio * tilePixelResolution / viewState.resolution, + pixelRatio * tilePixelResolution / viewState.resolution, + viewState.rotation, + (origin[0] - center[0]) / tilePixelResolution, + (center[1] - origin[1]) / tilePixelResolution); + this.imageTransformInv_ = null; + + return true; }; -goog.inherits(ol.render.webgl.imagereplay.shader.ColorVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorVertex); /** - * @const - * @type {string} + * @inheritDoc */ -ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; +ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.context_)) { + return undefined; + } + if (goog.isNull(this.imageTransformInv_)) { + this.imageTransformInv_ = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_); + } -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}'; + var pixelOnCanvas = + this.getPixelOnCanvas(pixel, this.imageTransformInv_); + var imageData = this.context_.getImageData( + pixelOnCanvas[0], pixelOnCanvas[1], 1, 1).data; -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE = goog.DEBUG ? - ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE : - ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } +}; + +goog.provide('ol.renderer.canvas.VectorLayer'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.EventType'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.renderer.vector'); +goog.require('ol.source.Vector'); /** * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct + * @extends {ol.renderer.canvas.Layer} + * @param {ol.layer.Vector} vectorLayer Vector layer. */ -ol.render.webgl.imagereplay.shader.Color.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_colorMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_colorMatrix' : 'k'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_image = gl.getUniformLocation( - program, goog.DEBUG ? 'u_image' : 'm'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j'); +ol.renderer.canvas.VectorLayer = function(vectorLayer) { - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + goog.base(this, vectorLayer); /** - * @type {WebGLUniformLocation} + * @private + * @type {boolean} */ - this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'l'); + this.dirty_ = false; /** - * @type {WebGLUniformLocation} + * @private + * @type {number} */ - this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'h'); + this.renderedRevision_ = -1; /** + * @private * @type {number} */ - this.a_offsets = gl.getAttribLocation( - program, goog.DEBUG ? 'a_offsets' : 'e'); + this.renderedResolution_ = NaN; /** - * @type {number} + * @private + * @type {ol.Extent} */ - this.a_opacity = gl.getAttribLocation( - program, goog.DEBUG ? 'a_opacity' : 'f'); + this.renderedExtent_ = ol.extent.createEmpty(); /** - * @type {number} + * @private + * @type {function(ol.Feature, ol.Feature): number|null} */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'c'); + this.renderedRenderOrder_ = null; /** - * @type {number} + * @private + * @type {ol.render.canvas.ReplayGroup} */ - this.a_rotateWithView = gl.getAttribLocation( - program, goog.DEBUG ? 'a_rotateWithView' : 'g'); + this.replayGroup_ = null; /** - * @type {number} + * @private + * @type {CanvasRenderingContext2D} */ - this.a_texCoord = gl.getAttribLocation( - program, goog.DEBUG ? 'a_texCoord' : 'd'); -}; - -// This file is automatically generated, do not edit -goog.provide('ol.render.webgl.imagereplay.shader.Default'); - -goog.require('ol.webgl.shader'); - - + this.context_ = ol.dom.createCanvasContext2D(); -/** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct - */ -ol.render.webgl.imagereplay.shader.DefaultFragment = function() { - goog.base(this, ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE); }; -goog.inherits(ol.render.webgl.imagereplay.shader.DefaultFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultFragment); +goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer); /** - * @const - * @type {string} + * @inheritDoc */ -ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - +ol.renderer.canvas.VectorLayer.prototype.composeFrame = + function(frameState, layerState, context) { -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var skippedFeatureUids = layerState.managed ? + frameState.skippedFeatureUids : {}; + var viewState = frameState.viewState; + var projection = viewState.projection; + var rotation = viewState.rotation; + var projectionExtent = projection.getExtent(); + var vectorSource = this.getLayer().getSource(); + goog.asserts.assertInstanceof(vectorSource, ol.source.Vector); + var transform = this.getTransform(frameState, 0); -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? - ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE : - ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE; + this.dispatchPreComposeEvent(context, frameState, transform); + var replayGroup = this.replayGroup_; + if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { + var layer = this.getLayer(); + var replayContext; + if (layer.hasListener(ol.render.EventType.RENDER)) { + // resize and clear + this.context_.canvas.width = context.canvas.width; + this.context_.canvas.height = context.canvas.height; + replayContext = this.context_; + } else { + replayContext = context; + } + // for performance reasons, context.save / context.restore is not used + // to save and restore the transformation matrix and the opacity. + // see http://jsperf.com/context-save-restore-versus-variable + var alpha = replayContext.globalAlpha; + replayContext.globalAlpha = layerState.opacity; + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + if (vectorSource.getWrapX() && projection.canWrapX() && + !ol.extent.containsExtent(projectionExtent, extent)) { + var startX = extent[0]; + var worldWidth = ol.extent.getWidth(projectionExtent); + var world = 0; + var offsetX; + while (startX < projectionExtent[0]) { + --world; + offsetX = worldWidth * world; + transform = this.getTransform(frameState, offsetX); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + startX += worldWidth; + } + world = 0; + startX = extent[2]; + while (startX > projectionExtent[2]) { + ++world; + offsetX = worldWidth * world; + transform = this.getTransform(frameState, offsetX); + replayGroup.replay(replayContext, pixelRatio, transform, rotation, + skippedFeatureUids); + startX -= worldWidth; + } + // restore original transform for render and compose events + transform = this.getTransform(frameState, 0); + } -/** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct - */ -ol.render.webgl.imagereplay.shader.DefaultVertex = function() { - goog.base(this, ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE); -}; -goog.inherits(ol.render.webgl.imagereplay.shader.DefaultVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultVertex); + if (replayContext != context) { + this.dispatchRenderEvent(replayContext, frameState, transform); + context.drawImage(replayContext.canvas, 0, 0); + } + replayContext.globalAlpha = alpha; + } + this.dispatchPostComposeEvent(context, frameState, transform); -/** - * @const - * @type {string} - */ -ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; +}; /** - * @const - * @type {string} + * @inheritDoc */ -ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}'; +ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.replayGroup_)) { + return undefined; + } else { + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var layer = this.getLayer(); + var layerState = frameState.layerStates[goog.getUid(layer)]; + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, + rotation, layerState.managed ? frameState.skippedFeatureUids : {}, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + goog.asserts.assert(goog.isDef(feature), 'received a feature'); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; /** - * @const - * @type {string} + * Handle changes in image style state. + * @param {goog.events.Event} event Image style change event. + * @private */ -ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? - ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE : - ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE; - +ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = + function(event) { + this.renderIfReadyAndVisible(); +}; /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct + * @inheritDoc */ -ol.render.webgl.imagereplay.shader.Default.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_image = gl.getUniformLocation( - program, goog.DEBUG ? 'u_image' : 'l'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'k'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'h'); - - /** - * @type {number} - */ - this.a_offsets = gl.getAttribLocation( - program, goog.DEBUG ? 'a_offsets' : 'e'); +ol.renderer.canvas.VectorLayer.prototype.prepareFrame = + function(frameState, layerState) { - /** - * @type {number} - */ - this.a_opacity = gl.getAttribLocation( - program, goog.DEBUG ? 'a_opacity' : 'f'); + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector, + 'layer is an instance of ol.layer.Vector'); + var vectorSource = vectorLayer.getSource(); - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'c'); + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); - /** - * @type {number} - */ - this.a_rotateWithView = gl.getAttribLocation( - program, goog.DEBUG ? 'a_rotateWithView' : 'g'); + var animating = frameState.viewHints[ol.ViewHint.ANIMATING]; + var interacting = frameState.viewHints[ol.ViewHint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, goog.DEBUG ? 'a_texCoord' : 'd'); -}; + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; + } -goog.provide('ol.webgl.Buffer'); + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); -goog.require('goog.array'); -goog.require('goog.webgl'); -goog.require('ol'); + if (!goog.isDef(vectorLayerRenderOrder)) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); + var projectionExtent = viewState.projection.getExtent(); -/** - * @enum {number} - */ -ol.webgl.BufferUsage = { - STATIC_DRAW: goog.webgl.STATIC_DRAW, - STREAM_DRAW: goog.webgl.STREAM_DRAW, - DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW -}; + if (vectorSource.getWrapX() && viewState.projection.canWrapX() && + !ol.extent.containsExtent(projectionExtent, frameState.extent)) { + // For the replay group, we need an extent that intersects the real world + // (-180° to +180°). To support geometries in a coordinate range from -540° + // to +540°, we add at least 1 world width on each side of the projection + // extent. If the viewport is wider than the world, we need to add half of + // the viewport width to make sure we cover the whole viewport. + var worldWidth = ol.extent.getWidth(projectionExtent); + var buffer = Math.max(ol.extent.getWidth(extent) / 2, worldWidth); + extent[0] = projectionExtent[0] - buffer; + extent[2] = projectionExtent[2] + buffer; + } + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } + // FIXME dispose of old replayGroup in post render + goog.dispose(this.replayGroup_); + this.replayGroup_ = null; -/** - * @constructor - * @param {Array.<number>=} opt_arr Array. - * @param {number=} opt_usage Usage. - * @struct - */ -ol.webgl.Buffer = function(opt_arr, opt_usage) { + this.dirty_ = false; - /** - * @private - * @type {Array.<number>} - */ - this.arr_ = goog.isDef(opt_arr) ? opt_arr : []; + var replayGroup = + new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + var renderFeature = + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorLayer} + */ + function(feature) { + var styles; + if (goog.isDef(feature.getStyleFunction())) { + styles = feature.getStyleFunction().call(feature, resolution); + } else if (goog.isDef(vectorLayer.getStyleFunction())) { + styles = vectorLayer.getStyleFunction()(feature, resolution); + } + if (goog.isDefAndNotNull(styles)) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (!goog.isNull(vectorLayerRenderOrder)) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + goog.array.sort(features, vectorLayerRenderOrder); + goog.array.forEach(features, renderFeature, this); + } else { + vectorSource.forEachFeatureInExtentAtResolution( + extent, resolution, renderFeature, this); + } + replayGroup.finish(); - /** - * @private - * @type {number} - */ - this.usage_ = goog.isDef(opt_usage) ? - opt_usage : ol.webgl.BufferUsage.STATIC_DRAW; + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + return true; }; /** - * @return {Array.<number>} Array. + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.style.Style>} styles Array of styles + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. */ -ol.webgl.Buffer.prototype.getArray = function() { - return this.arr_; +ol.renderer.canvas.VectorLayer.prototype.renderFeature = + function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!goog.isDefAndNotNull(styles)) { + return false; + } + var i, ii, loading = false; + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; }; +// FIXME offset panning -/** - * @return {number} Usage. - */ -ol.webgl.Buffer.prototype.getUsage = function() { - return this.usage_; -}; - -goog.provide('ol.render.webgl.ImageReplay'); -goog.provide('ol.render.webgl.ReplayGroup'); +goog.provide('ol.renderer.canvas.Map'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.functions'); -goog.require('goog.object'); +goog.require('goog.dom'); +goog.require('goog.style'); goog.require('goog.vec.Mat4'); -goog.require('ol.color.Matrix'); -goog.require('ol.extent'); -goog.require('ol.render.IReplayGroup'); -goog.require('ol.render.webgl.imagereplay.shader.Color'); -goog.require('ol.render.webgl.imagereplay.shader.Default'); +goog.require('ol'); +goog.require('ol.RendererType'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.Event'); +goog.require('ol.render.EventType'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.renderer.canvas.VectorLayer'); +goog.require('ol.source.State'); goog.require('ol.vec.Mat4'); -goog.require('ol.webgl.Buffer'); /** * @constructor - * @implements {ol.render.IVectorContext} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. */ -ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { - - /** - * @type {number|undefined} - * @private - */ - this.anchorX_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.anchorY_ = undefined; - - /** - * @private - * @type {ol.color.Matrix} - */ - this.colorMatrix_ = new ol.color.Matrix(); - - /** - * The origin of the coordinate system for the point coordinates sent to - * the GPU. To eliminate jitter caused by precision problems in the GPU - * we use the "Rendering Relative to Eye" technique described in the "3D - * Engine Design for Virtual Globes" book. - * @private - * @type {ol.Coordinate} - */ - this.origin_ = ol.extent.getCenter(maxExtent); - - /** - * @type {Array.<number>} - * @private - */ - this.groupIndices_ = []; - - /** - * @type {number|undefined} - * @private - */ - this.height_ = undefined; - - /** - * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} - * @private - */ - this.images_ = []; - - /** - * @type {number|undefined} - * @private - */ - this.imageHeight_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.imageWidth_ = undefined; - - /** - * @type {Array.<number>} - * @private - */ - this.indices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.indicesBuffer_ = null; - - /** - * @private - * @type {ol.render.webgl.imagereplay.shader.Color.Locations} - */ - this.colorLocations_ = null; +ol.renderer.canvas.Map = function(container, map) { - /** - * @private - * @type {ol.render.webgl.imagereplay.shader.Default.Locations} - */ - this.defaultLocations_ = null; + goog.base(this, container, map); /** * @private - * @type {number|undefined} + * @type {CanvasRenderingContext2D} */ - this.opacity_ = undefined; + this.context_ = ol.dom.createCanvasContext2D(); /** - * @type {!goog.vec.Mat4.Number} * @private + * @type {HTMLCanvasElement} */ - this.offsetRotateMatrix_ = goog.vec.Mat4.createNumberIdentity(); + this.canvas_ = this.context_.canvas; - /** - * @type {!goog.vec.Mat4.Number} - * @private - */ - this.offsetScaleMatrix_ = goog.vec.Mat4.createNumberIdentity(); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + goog.dom.insertChildAt(container, this.canvas_, 0); /** - * @type {number|undefined} * @private + * @type {boolean} */ - this.originX_ = undefined; + this.renderedVisible_ = true; /** - * @type {number|undefined} * @private - */ - this.originY_ = undefined; - - /** * @type {!goog.vec.Mat4.Number} - * @private */ - this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); + this.transform_ = goog.vec.Mat4.createNumber(); - /** - * @private - * @type {boolean|undefined} - */ - this.rotateWithView_ = undefined; +}; +goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map); - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = undefined; - /** - * @private - * @type {number|undefined} - */ - this.scale_ = undefined; +/** + * @inheritDoc + */ +ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) { + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + return new ol.renderer.canvas.ImageLayer(layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + return new ol.renderer.canvas.TileLayer(layer); + } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + return new ol.renderer.canvas.VectorLayer(layer); + } else { + goog.asserts.fail('unexpected layer configuration'); + return null; + } +}; - /** - * @type {Array.<WebGLTexture>} - * @private - */ - this.textures_ = []; - /** - * @type {Array.<number>} - * @private - */ - this.vertices_ = []; +/** + * @param {ol.render.EventType} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = + function(type, frameState) { + var map = this.getMap(); + var context = this.context_; + if (map.hasListener(type)) { + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var rotation = viewState.rotation; - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.verticesBuffer_ = null; + var transform = this.getTransform(frameState); - /** - * @type {number|undefined} - * @private - */ - this.width_ = undefined; + var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, + extent, transform, rotation); + var composeEvent = new ol.render.Event(type, map, vectorContext, + frameState, context, null); + map.dispatchEvent(composeEvent); + vectorContext.flush(); + } }; /** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. + * @param {olx.FrameState} frameState Frame state. + * @protected + * @return {!goog.vec.Mat4.Number} Transform. */ -ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = - function(context) { - // We only delete our stuff here. The shaders and the program may - // be used by other ImageReplay instances (for other layers). And - // they will be deleted when disposing of the ol.webgl.Context - // object. - goog.asserts.assert(!goog.isNull(this.verticesBuffer_)); - goog.asserts.assert(!goog.isNull(this.indicesBuffer_)); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; - var textures = this.textures_; - var gl = context.getGL(); - return function() { - if (!gl.isContextLost()) { - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { - gl.deleteTexture(textures[i]); - } - } - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - }; +ol.renderer.canvas.Map.prototype.getTransform = function(frameState) { + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var resolution = viewState.resolution; + return ol.vec.Mat4.makeTransform2D(this.transform_, + this.canvas_.width / 2, this.canvas_.height / 2, + pixelRatio / resolution, -pixelRatio / resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); }; /** * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawAsync = goog.abstractMethod; +ol.renderer.canvas.Map.prototype.getType = function() { + return ol.RendererType.CANVAS; +}; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} My end. - * @private + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = - function(flatCoordinates, offset, end, stride) { - goog.asserts.assert(goog.isDef(this.anchorX_)); - goog.asserts.assert(goog.isDef(this.anchorY_)); - goog.asserts.assert(goog.isDef(this.height_)); - goog.asserts.assert(goog.isDef(this.imageHeight_)); - goog.asserts.assert(goog.isDef(this.imageWidth_)); - goog.asserts.assert(goog.isDef(this.opacity_)); - goog.asserts.assert(goog.isDef(this.originX_)); - goog.asserts.assert(goog.isDef(this.originY_)); - goog.asserts.assert(goog.isDef(this.rotateWithView_)); - goog.asserts.assert(goog.isDef(this.rotation_)); - goog.asserts.assert(goog.isDef(this.scale_)); - goog.asserts.assert(goog.isDef(this.width_)); - var anchorX = this.anchorX_; - var anchorY = this.anchorY_; - var height = this.height_; - var imageHeight = this.imageHeight_; - var imageWidth = this.imageWidth_; - var opacity = this.opacity_; - var originX = this.originX_; - var originY = this.originY_; - var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; - var rotation = this.rotation_; - var scale = this.scale_; - var width = this.width_; - var cos = Math.cos(rotation); - var sin = Math.sin(rotation); - var numIndices = this.indices_.length; - var numVertices = this.vertices_.length; - var i, n, offsetX, offsetY, x, y; - for (i = offset; i < end; i += stride) { - x = flatCoordinates[i] - this.origin_[0]; - y = flatCoordinates[i + 1] - this.origin_[1]; +ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) { - // There are 4 vertices per [x, y] point, one for each corner of the - // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if - // WebGL supported Geometry Shaders (which can emit new vertices), but that - // is not currently the case. - // - // And each vertex includes 8 values: the x and y coordinates, the x and - // y offsets used to calculate the position of the corner, the u and - // v texture coordinates for the corner, the opacity, and whether the - // the image should be rotated with the view (rotateWithView). + if (goog.isNull(frameState)) { + if (this.renderedVisible_) { + goog.style.setElementShown(this.canvas_, false); + this.renderedVisible_ = false; + } + return; + } - n = numVertices / 8; + var context = this.context_; + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } else { + context.clearRect(0, 0, this.canvas_.width, this.canvas_.height); + } - // bottom-left corner - offsetX = -scale * anchorX; - offsetY = -scale * (height - anchorY); - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = originX / imageWidth; - this.vertices_[numVertices++] = (originY + height) / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.calculateMatrices2D(frameState); - // bottom-right corner - offsetX = scale * (width - anchorX); - offsetY = -scale * (height - anchorY); - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = (originX + width) / imageWidth; - this.vertices_[numVertices++] = (originY + height) / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); - // top-right corner - offsetX = scale * (width - anchorX); - offsetY = scale * anchorY; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = (originX + width) / imageWidth; - this.vertices_[numVertices++] = originY / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + var layerStatesArray = frameState.layerStatesArray; + var viewResolution = frameState.viewState.resolution; + var i, ii, layer, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + layer = layerState.layer; + layerRenderer = this.getLayerRenderer(layer); + goog.asserts.assertInstanceof(layerRenderer, ol.renderer.canvas.Layer, + 'layerRenderer is an instance of ol.renderer.canvas.Layer'); + if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) || + layerState.sourceState != ol.source.State.READY) { + continue; + } + if (layerRenderer.prepareFrame(frameState, layerState)) { + layerRenderer.composeFrame(frameState, layerState, context); + } + } - // top-left corner - offsetX = -scale * anchorX; - offsetY = scale * anchorY; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = originX / imageWidth; - this.vertices_[numVertices++] = originY / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.dispatchComposeEvent_( + ol.render.EventType.POSTCOMPOSE, frameState); - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 3; + if (!this.renderedVisible_) { + goog.style.setElementShown(this.canvas_, true); + this.renderedVisible_ = true; } - return numVertices; + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); }; +goog.provide('ol.renderer.dom.Layer'); + +goog.require('ol.layer.Layer'); +goog.require('ol.renderer.Layer'); -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.drawCircleGeometry = goog.abstractMethod; /** - * @inheritDoc + * @constructor + * @extends {ol.renderer.Layer} + * @param {ol.layer.Layer} layer Layer. + * @param {!Element} target Target. */ -ol.render.webgl.ImageReplay.prototype.drawFeature = goog.abstractMethod; +ol.renderer.dom.Layer = function(layer, target) { + goog.base(this, layer); -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.drawGeometryCollectionGeometry = - goog.abstractMethod; + /** + * @type {!Element} + * @protected + */ + this.target = target; + +}; +goog.inherits(ol.renderer.dom.Layer, ol.renderer.Layer); /** - * @inheritDoc + * Clear rendered elements. */ -ol.render.webgl.ImageReplay.prototype.drawLineStringGeometry = - goog.abstractMethod; +ol.renderer.dom.Layer.prototype.clearFrame = goog.nullFunction; /** - * @inheritDoc + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. */ -ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry = - goog.abstractMethod; +ol.renderer.dom.Layer.prototype.composeFrame = goog.nullFunction; /** - * @inheritDoc + * @return {!Element} Target. */ -ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry = - function(multiPointGeometry, feature) { - var flatCoordinates = multiPointGeometry.getFlatCoordinates(); - var stride = multiPointGeometry.getStride(); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); +ol.renderer.dom.Layer.prototype.getTarget = function() { + return this.target; }; /** - * @inheritDoc + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @return {boolean} whether composeFrame should be called. */ -ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry = - goog.abstractMethod; +ol.renderer.dom.Layer.prototype.prepareFrame = goog.abstractMethod; + +goog.provide('ol.renderer.dom.ImageLayer'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.vec.Mat4'); +goog.require('ol.ImageBase'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Image'); +goog.require('ol.proj'); +goog.require('ol.renderer.dom.Layer'); +goog.require('ol.vec.Mat4'); + /** - * @inheritDoc + * @constructor + * @extends {ol.renderer.dom.Layer} + * @param {ol.layer.Image} imageLayer Image layer. */ -ol.render.webgl.ImageReplay.prototype.drawPointGeometry = - function(pointGeometry, feature) { - var flatCoordinates = pointGeometry.getFlatCoordinates(); - var stride = pointGeometry.getStride(); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); +ol.renderer.dom.ImageLayer = function(imageLayer) { + var target = goog.dom.createElement(goog.dom.TagName.DIV); + target.style.position = 'absolute'; + + goog.base(this, imageLayer, target); + + /** + * The last rendered image. + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.transform_ = goog.vec.Mat4.createNumberIdentity(); + }; +goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer); /** * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawPolygonGeometry = goog.abstractMethod; +ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, skippedFeatureUids, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; /** * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawText = goog.abstractMethod; +ol.renderer.dom.ImageLayer.prototype.clearFrame = function() { + goog.dom.removeChildren(this.target); + this.image_ = null; +}; /** - * @param {ol.webgl.Context} context Context. + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.finish = function(context) { - var gl = context.getGL(); - - this.groupIndices_.push(this.indices_.length); - goog.asserts.assert(this.images_.length == this.groupIndices_.length); - - // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); +ol.renderer.dom.ImageLayer.prototype.prepareFrame = + function(frameState, layerState) { - var indices = this.indices_; - var bits = context.hasOESElementIndexUint ? 32 : 16; - goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits), - 'Too large element index detected [%s] (OES_element_index_uint "%s")', - indices[indices.length - 1], context.hasOESElementIndexUint); + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; - // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(indices); - context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + var image = this.image_; + var imageLayer = this.getLayer(); + goog.asserts.assertInstanceof(imageLayer, ol.layer.Image, + 'layer is an instance of ol.layer.Image'); + var imageSource = imageLayer.getSource(); - goog.asserts.assert(this.textures_.length === 0); + var hints = frameState.viewHints; - // create textures - var texture, image, uid; - /** @type {Object.<string, WebGLTexture>} */ - var texturePerImage = {}; - var i; - var ii = this.images_.length; - for (i = 0; i < ii; ++i) { - image = this.images_[i]; + var renderedExtent = frameState.extent; + if (goog.isDef(layerState.extent)) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } - uid = goog.getUid(image).toString(); - if (goog.object.containsKey(texturePerImage, uid)) { - texture = goog.object.get(texturePerImage, uid); - } else { - texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_WRAP_S, goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, image); - goog.object.set(texturePerImage, uid, texture); + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + var sourceProjection = imageSource.getProjection(); + if (!goog.isNull(sourceProjection)) { + goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + var image_ = imageSource.getImage(renderedExtent, viewResolution, + frameState.pixelRatio, projection); + if (!goog.isNull(image_)) { + var loaded = this.loadImage(image_); + if (loaded) { + image = image_; + } } - this.textures_[i] = texture; } - goog.asserts.assert(this.textures_.length == this.groupIndices_.length); + if (!goog.isNull(image)) { + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var transform = goog.vec.Mat4.createNumber(); + ol.vec.Mat4.makeTransform2D(transform, + frameState.size[0] / 2, frameState.size[1] / 2, + imageResolution / viewResolution, imageResolution / viewResolution, + viewRotation, + (imageExtent[0] - viewCenter[0]) / imageResolution, + (viewCenter[1] - imageExtent[3]) / imageResolution); + if (image != this.image_) { + var imageElement = image.getImage(this); + // Bootstrap sets the style max-width: 100% for all images, which breaks + // prevents the image from being displayed in FireFox. Workaround by + // overriding the max-width style. + imageElement.style.maxWidth = 'none'; + imageElement.style.position = 'absolute'; + goog.dom.removeChildren(this.target); + goog.dom.appendChild(this.target, imageElement); + this.image_ = image; + } + this.setTransform_(transform); + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } - this.anchorX_ = undefined; - this.anchorY_ = undefined; - this.height_ = undefined; - this.images_ = null; - this.imageHeight_ = undefined; - this.imageWidth_ = undefined; - this.indices_ = null; - this.opacity_ = undefined; - this.originX_ = undefined; - this.originY_ = undefined; - this.rotateWithView_ = undefined; - this.rotation_ = undefined; - this.scale_ = undefined; - this.vertices_ = null; - this.width_ = undefined; + return true; }; /** - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {number} brightness Global brightness. - * @param {number} contrast Global contrast. - * @param {number} hue Global hue. - * @param {number} saturation Global saturation. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @return {T|undefined} Callback result. - * @template T + * @param {goog.vec.Mat4.Number} transform Transform. + * @private */ -ol.render.webgl.ImageReplay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { - var gl = context.getGL(); - - // bind the vertices buffer - goog.asserts.assert(!goog.isNull(this.verticesBuffer_)); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // bind the indices buffer - goog.asserts.assert(!goog.isNull(this.indicesBuffer_)); - context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - var useColor = brightness || contrast != 1 || hue || saturation != 1; - - // get the program - var fragmentShader, vertexShader; - if (useColor) { - fragmentShader = - ol.render.webgl.imagereplay.shader.ColorFragment.getInstance(); - vertexShader = - ol.render.webgl.imagereplay.shader.ColorVertex.getInstance(); - } else { - fragmentShader = - ol.render.webgl.imagereplay.shader.DefaultFragment.getInstance(); - vertexShader = - ol.render.webgl.imagereplay.shader.DefaultVertex.getInstance(); +ol.renderer.dom.ImageLayer.prototype.setTransform_ = function(transform) { + if (!ol.vec.Mat4.equals2D(transform, this.transform_)) { + ol.dom.transformElement2D(this.target, transform, 6); + goog.vec.Mat4.setFromArray(this.transform_, transform); } - var program = context.getProgram(fragmentShader, vertexShader); +}; - // get the locations - var locations; - if (useColor) { - if (goog.isNull(this.colorLocations_)) { - locations = - new ol.render.webgl.imagereplay.shader.Color.Locations(gl, program); - this.colorLocations_ = locations; - } else { - locations = this.colorLocations_; - } - } else { - if (goog.isNull(this.defaultLocations_)) { - locations = - new ol.render.webgl.imagereplay.shader.Default.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - } +// FIXME probably need to reset TileLayerZ if offsets get too large +// FIXME when zooming out, preserve higher Z divs to avoid white flash - // use the program (FIXME: use the return value) - context.useProgram(program); +goog.provide('ol.renderer.dom.TileLayer'); - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, - false, 32, 0); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('goog.vec.Mat4'); +goog.require('ol'); +goog.require('ol.Coordinate'); +goog.require('ol.TileCoord'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.dom.BrowserFeature'); +goog.require('ol.extent'); +goog.require('ol.layer.Tile'); +goog.require('ol.renderer.dom.Layer'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); +goog.require('ol.tilegrid.TileGrid'); +goog.require('ol.vec.Mat4'); - gl.enableVertexAttribArray(locations.a_offsets); - gl.vertexAttribPointer(locations.a_offsets, 2, goog.webgl.FLOAT, - false, 32, 8); - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer(locations.a_texCoord, 2, goog.webgl.FLOAT, - false, 32, 16); - gl.enableVertexAttribArray(locations.a_opacity); - gl.vertexAttribPointer(locations.a_opacity, 1, goog.webgl.FLOAT, - false, 32, 24); +/** + * @constructor + * @extends {ol.renderer.dom.Layer} + * @param {ol.layer.Tile} tileLayer Tile layer. + */ +ol.renderer.dom.TileLayer = function(tileLayer) { - gl.enableVertexAttribArray(locations.a_rotateWithView); - gl.vertexAttribPointer(locations.a_rotateWithView, 1, goog.webgl.FLOAT, - false, 32, 28); + var target = goog.dom.createElement(goog.dom.TagName.DIV); + target.style.position = 'absolute'; - // set the "uniform" values - var projectionMatrix = this.projectionMatrix_; - ol.vec.Mat4.makeTransform2D(projectionMatrix, - 0.0, 0.0, - 2 / (resolution * size[0]), - 2 / (resolution * size[1]), - -rotation, - -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1])); + // Needed for IE7-8 to render a transformed element correctly + if (ol.dom.BrowserFeature.USE_MS_MATRIX_TRANSFORM) { + target.style.width = '100%'; + target.style.height = '100%'; + } - var offsetScaleMatrix = this.offsetScaleMatrix_; - goog.vec.Mat4.makeScale(offsetScaleMatrix, 2 / size[0], 2 / size[1], 1); + goog.base(this, tileLayer, target); - var offsetRotateMatrix = this.offsetRotateMatrix_; - goog.vec.Mat4.makeIdentity(offsetRotateMatrix); - if (rotation !== 0) { - goog.vec.Mat4.rotateZ(offsetRotateMatrix, -rotation); - } + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix); - gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, offsetScaleMatrix); - gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, - offsetRotateMatrix); - gl.uniform1f(locations.u_opacity, opacity); - if (useColor) { - gl.uniformMatrix4fv(locations.u_colorMatrix, false, - this.colorMatrix_.getMatrix(brightness, contrast, hue, saturation)); - } + /** + * @private + * @type {number} + */ + this.renderedOpacity_ = 1; - // draw! - goog.asserts.assert(this.textures_.length == this.groupIndices_.length); - var i, ii, start; - for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) { - gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]); - var end = this.groupIndices_[i]; - var numItems = end - start; - var offsetInBytes = start * (context.hasOESElementIndexUint ? 4 : 2); - var elementType = context.hasOESElementIndexUint ? - goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; - gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); - start = end; - } + /** + * @private + * @type {number} + */ + this.renderedRevision_ = 0; + + /** + * @private + * @type {Object.<number, ol.renderer.dom.TileLayerZ_>} + */ + this.tileLayerZs_ = {}; - // disable the vertex attrib arrays - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_offsets); - gl.disableVertexAttribArray(locations.a_texCoord); - gl.disableVertexAttribArray(locations.a_opacity); - gl.disableVertexAttribArray(locations.a_rotateWithView); }; +goog.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer); /** * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = goog.abstractMethod; +ol.renderer.dom.TileLayer.prototype.clearFrame = function() { + goog.dom.removeChildren(this.target); + this.renderedRevision_ = 0; +}; /** * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { - var anchor = imageStyle.getAnchor(); - goog.asserts.assert(!goog.isNull(anchor)); - var image = imageStyle.getImage(1); - goog.asserts.assert(!goog.isNull(image)); - var imageSize = imageStyle.getImageSize(); - goog.asserts.assert(!goog.isNull(imageSize)); - var opacity = imageStyle.getOpacity(); - goog.asserts.assert(goog.isDef(opacity)); - var origin = imageStyle.getOrigin(); - goog.asserts.assert(!goog.isNull(origin)); - var rotateWithView = imageStyle.getRotateWithView(); - goog.asserts.assert(goog.isDef(rotateWithView)); - var rotation = imageStyle.getRotation(); - goog.asserts.assert(goog.isDef(rotation)); - var size = imageStyle.getSize(); - goog.asserts.assert(!goog.isNull(size)); - var scale = imageStyle.getScale(); - goog.asserts.assert(goog.isDef(scale)); +ol.renderer.dom.TileLayer.prototype.prepareFrame = + function(frameState, layerState) { - if (this.images_.length === 0) { - this.images_.push(image); - } else { - var currentImage = this.images_[this.images_.length - 1]; - if (goog.getUid(currentImage) != goog.getUid(image)) { - this.groupIndices_.push(this.indices_.length); - goog.asserts.assert(this.groupIndices_.length == this.images_.length); - this.images_.push(image); + if (!layerState.visible) { + if (this.renderedVisible_) { + goog.style.setElementShown(this.target, false); + this.renderedVisible_ = false; } + return true; } - this.anchorX_ = anchor[0]; - this.anchorY_ = anchor[1]; - this.height_ = size[1]; - this.imageHeight_ = imageSize[1]; - this.imageWidth_ = imageSize[0]; - this.opacity_ = opacity; - this.originX_ = origin[0]; - this.originY_ = origin[1]; - this.rotation_ = rotation; - this.rotateWithView_ = rotateWithView; - this.scale_ = scale; - this.width_ = size[0]; -}; + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var projection = viewState.projection; + + var tileLayer = this.getLayer(); + goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile, + 'layer is an instance of ol.layer.Tile'); + var tileSource = tileLayer.getSource(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var tileGutter = tileSource.getGutter(); + var z = tileGrid.getZForResolution(viewState.resolution); + var tileResolution = tileGrid.getResolution(z); + var center = viewState.center; + var extent; + if (tileResolution == viewState.resolution) { + center = this.snapCenterToPixel(center, tileResolution, frameState.size); + extent = ol.extent.getForViewAndSize( + center, tileResolution, viewState.rotation, frameState.size); + } else { + extent = frameState.extent; + } + + if (goog.isDef(layerState.extent)) { + extent = ol.extent.getIntersection(extent, layerState.extent); + } + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.setTextStyle = goog.abstractMethod; + /** @type {Object.<number, Object.<string, ol.Tile>>} */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + var findLoadedTiles = this.createLoadedTileFinder(tileSource, tilesToDrawByZ); + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); -/** - * @constructor - * @implements {ol.render.IReplayGroup} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ -ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) { + var tmpExtent = ol.extent.createEmpty(); + var tmpTileRange = new ol.TileRange(0, 0, 0, 0); + var childTileRange, fullyLoaded, tile, tileState, x, y; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - /** - * @type {ol.Extent} - * @private - */ - this.maxExtent_ = maxExtent; + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + tileState = tile.getState(); + if (tileState == ol.TileState.LOADED) { + tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile; + continue; + } else if (tileState == ol.TileState.EMPTY || + (tileState == ol.TileState.ERROR && + !useInterimTilesOnError)) { + continue; + } - /** - * @type {number} - * @private - */ - this.tolerance_ = tolerance; + fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (!goog.isNull(childTileRange)) { + findLoadedTiles(z + 1, childTileRange); + } + } - /** - * ImageReplay only is supported at this point. - * @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>} - * @private - */ - this.replays_ = {}; + } -}; + } + + // If the tile source revision changes, we destroy the existing DOM structure + // so that a new one will be created. It would be more efficient to modify + // the existing structure. + var tileLayerZ, tileLayerZKey; + if (this.renderedRevision_ != tileSource.getRevision()) { + for (tileLayerZKey in this.tileLayerZs_) { + tileLayerZ = this.tileLayerZs_[+tileLayerZKey]; + goog.dom.removeNode(tileLayerZ.target); + } + this.tileLayerZs_ = {}; + this.renderedRevision_ = tileSource.getRevision(); + } + /** @type {Array.<number>} */ + var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); + goog.array.sort(zs); -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = - function(context) { - var functions = []; - var replayKey; - for (replayKey in this.replays_) { - functions.push( - this.replays_[replayKey].getDeleteResourcesFunction(context)); + /** @type {Object.<number, boolean>} */ + var newTileLayerZKeys = {}; + + var iz, iziz, tileCoordKey, tileCoordOrigin, tilesToDraw; + for (iz = 0, iziz = zs.length; iz < iziz; ++iz) { + tileLayerZKey = zs[iz]; + if (tileLayerZKey in this.tileLayerZs_) { + tileLayerZ = this.tileLayerZs_[tileLayerZKey]; + } else { + tileCoordOrigin = + tileGrid.getTileCoordForCoordAndZ(center, tileLayerZKey); + tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin); + newTileLayerZKeys[tileLayerZKey] = true; + this.tileLayerZs_[tileLayerZKey] = tileLayerZ; + } + tilesToDraw = tilesToDrawByZ[tileLayerZKey]; + for (tileCoordKey in tilesToDraw) { + tileLayerZ.addTile(tilesToDraw[tileCoordKey], tileGutter); + } + tileLayerZ.finalizeAddTiles(); + } + + /** @type {Array.<number>} */ + var tileLayerZKeys = + goog.array.map(goog.object.getKeys(this.tileLayerZs_), Number); + goog.array.sort(tileLayerZKeys); + + var i, ii, j, origin, resolution; + var transform = goog.vec.Mat4.createNumber(); + for (i = 0, ii = tileLayerZKeys.length; i < ii; ++i) { + tileLayerZKey = tileLayerZKeys[i]; + tileLayerZ = this.tileLayerZs_[tileLayerZKey]; + if (!(tileLayerZKey in tilesToDrawByZ)) { + goog.dom.removeNode(tileLayerZ.target); + delete this.tileLayerZs_[tileLayerZKey]; + continue; + } + resolution = tileLayerZ.getResolution(); + origin = tileLayerZ.getOrigin(); + ol.vec.Mat4.makeTransform2D(transform, + frameState.size[0] / 2, frameState.size[1] / 2, + resolution / viewState.resolution, + resolution / viewState.resolution, + viewState.rotation, + (origin[0] - center[0]) / resolution, + (center[1] - origin[1]) / resolution); + tileLayerZ.setTransform(transform); + if (tileLayerZKey in newTileLayerZKeys) { + for (j = tileLayerZKey - 1; j >= 0; --j) { + if (j in this.tileLayerZs_) { + goog.dom.insertSiblingAfter( + tileLayerZ.target, this.tileLayerZs_[j].target); + break; + } + } + if (j < 0) { + goog.dom.insertChildAt(this.target, tileLayerZ.target, 0); + } + } else { + if (!frameState.viewHints[ol.ViewHint.ANIMATING] && + !frameState.viewHints[ol.ViewHint.INTERACTING]) { + tileLayerZ.removeTilesOutsideExtent(extent, tmpTileRange); + } + } } - return goog.functions.sequence.apply(null, functions); -}; - -/** - * @param {ol.webgl.Context} context Context. - */ -ol.render.webgl.ReplayGroup.prototype.finish = function(context) { - var replayKey; - for (replayKey in this.replays_) { - this.replays_[replayKey].finish(context); + if (layerState.opacity != this.renderedOpacity_) { + ol.dom.setOpacity(this.target, layerState.opacity); + this.renderedOpacity_ = layerState.opacity; } -}; - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.getReplay = - function(zIndex, replayType) { - var replay = this.replays_[replayType]; - if (!goog.isDef(replay)) { - var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType]; - goog.asserts.assert(goog.isDef(constructor)); - replay = new constructor(this.tolerance_, this.maxExtent_); - this.replays_[replayType] = replay; + if (layerState.visible && !this.renderedVisible_) { + goog.style.setElementShown(this.target, true); + this.renderedVisible_ = true; } - return replay; -}; + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, + projection, extent, z, tileLayer.getPreload()); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { - return goog.object.isEmpty(this.replays_); + return true; }; -/** - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @param {number} opacity Global opacity. - * @param {number} brightness Global brightness. - * @param {number} contrast Global contrast. - * @param {number} hue Global hue. - * @param {number} saturation Global saturation. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ReplayGroup.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { - var i, ii, replay, result; - for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) { - replay = this.replays_[ol.render.REPLAY_ORDER[i]]; - if (goog.isDef(replay)) { - result = replay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash); - if (result) { - return result; - } - } - } - return undefined; -}; - /** - * @const + * @constructor * @private - * @type {Object.<ol.render.ReplayType, - * function(new: ol.render.webgl.ImageReplay, number, - * ol.Extent)>} + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.TileCoord} tileCoordOrigin Tile coord origin. */ -ol.render.webgl.BATCH_CONSTRUCTORS_ = { - 'Image': ol.render.webgl.ImageReplay -}; - -goog.provide('ol.render.webgl.Immediate'); -goog.require('goog.array'); -goog.require('goog.object'); -goog.require('ol.extent'); -goog.require('ol.render.webgl.ReplayGroup'); +ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) { + /** + * @type {!Element} + */ + this.target = goog.dom.createElement(goog.dom.TagName.DIV); + this.target.style.position = 'absolute'; + this.target.style.width = '100%'; + this.target.style.height = '100%'; + if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { + /** + * Needed due to issues with IE7-8 clipping of transformed elements + * Solution is to translate separately from the scaled/rotated elements + * @private + * @type {!Element} + */ + this.translateTarget_ = goog.dom.createElement(goog.dom.TagName.DIV); + this.translateTarget_.style.position = 'absolute'; + this.translateTarget_.style.width = '100%'; + this.translateTarget_.style.height = '100%'; -/** - * @constructor - * @implements {ol.render.IVectorContext} - * @param {ol.webgl.Context} context Context. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} rotation Rotation. - * @param {ol.Size} size Size. - * @param {ol.Extent} extent Extent. - * @param {number} pixelRatio Pixel ratio. - * @struct - */ -ol.render.webgl.Immediate = function(context, - center, resolution, rotation, size, extent, pixelRatio) { + goog.dom.appendChild(this.target, this.translateTarget_); + } /** * @private + * @type {ol.tilegrid.TileGrid} */ - this.context_ = context; + this.tileGrid_ = tileGrid; /** * @private + * @type {ol.TileCoord} */ - this.center_ = center; + this.tileCoordOrigin_ = tileCoordOrigin; /** * @private + * @type {ol.Coordinate} */ - this.extent_ = extent; + this.origin_ = + ol.extent.getTopLeft(tileGrid.getTileCoordExtent(tileCoordOrigin)); /** * @private + * @type {number} */ - this.pixelRatio_ = pixelRatio; + this.resolution_ = tileGrid.getResolution(tileCoordOrigin[0]); /** * @private + * @type {Object.<string, ol.Tile>} */ - this.size_ = size; + this.tiles_ = {}; /** * @private + * @type {DocumentFragment} */ - this.rotation_ = rotation; + this.documentFragment_ = null; /** * @private + * @type {goog.vec.Mat4.Number} */ - this.resolution_ = resolution; + this.transform_ = goog.vec.Mat4.createNumberIdentity(); /** * @private - * @type {ol.style.Image} + * @type {ol.Size} */ - this.imageStyle_ = null; + this.tmpSize_ = [0, 0]; - /** - * @private - * @type {Object.<string, - * Array.<function(ol.render.webgl.Immediate)>>} - */ - this.callbacksByZIndex_ = {}; }; /** - * FIXME: empty description for jsdoc + * @param {ol.Tile} tile Tile. + * @param {number} tileGutter Tile gutter. */ -ol.render.webgl.Immediate.prototype.flush = function() { - /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number); - goog.array.sort(zs); - var i, ii, callbacks, j, jj; - for (i = 0, ii = zs.length; i < ii; ++i) { - callbacks = this.callbacksByZIndex_[zs[i].toString()]; - for (j = 0, jj = callbacks.length; j < jj; ++j) { - callbacks[j](this); - } +ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) { + var tileCoord = tile.tileCoord; + var tileCoordZ = tileCoord[0]; + var tileCoordX = tileCoord[1]; + var tileCoordY = tileCoord[2]; + goog.asserts.assert(tileCoordZ == this.tileCoordOrigin_[0], + 'tileCoordZ matches z of tileCoordOrigin'); + var tileCoordKey = ol.tilecoord.toString(tileCoord); + if (tileCoordKey in this.tiles_) { + return; } -}; - - -/** - * @param {number} zIndex Z index. - * @param {function(ol.render.webgl.Immediate)} callback Callback. - * @api - */ -ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) { - var zIndexKey = zIndex.toString(); - var callbacks = this.callbacksByZIndex_[zIndexKey]; - if (goog.isDef(callbacks)) { - callbacks.push(callback); + var tileSize = ol.size.toSize( + this.tileGrid_.getTileSize(tileCoordZ), this.tmpSize_); + var image = tile.getImage(this); + var imageStyle = image.style; + // Bootstrap sets the style max-width: 100% for all images, which + // prevents the tile from being displayed in FireFox. Workaround + // by overriding the max-width style. + imageStyle.maxWidth = 'none'; + var tileElement; + var tileElementStyle; + if (tileGutter > 0) { + tileElement = goog.dom.createElement(goog.dom.TagName.DIV); + tileElementStyle = tileElement.style; + tileElementStyle.overflow = 'hidden'; + tileElementStyle.width = tileSize[0] + 'px'; + tileElementStyle.height = tileSize[1] + 'px'; + imageStyle.position = 'absolute'; + imageStyle.left = -tileGutter + 'px'; + imageStyle.top = -tileGutter + 'px'; + imageStyle.width = (tileSize[0] + 2 * tileGutter) + 'px'; + imageStyle.height = (tileSize[1] + 2 * tileGutter) + 'px'; + goog.dom.appendChild(tileElement, image); } else { - this.callbacksByZIndex_[zIndexKey] = [callback]; - } -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawCircleGeometry = - function(circleGeometry, data) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) { - var geometry = style.getGeometryFunction()(feature); - if (!goog.isDefAndNotNull(geometry) || - !ol.extent.intersects(this.extent_, geometry.getExtent())) { - return; + imageStyle.width = tileSize[0] + 'px'; + imageStyle.height = tileSize[1] + 'px'; + tileElement = image; + tileElementStyle = imageStyle; } - var zIndex = style.getZIndex(); - if (!goog.isDef(zIndex)) { - zIndex = 0; + tileElementStyle.position = 'absolute'; + tileElementStyle.left = + ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize[0]) + 'px'; + tileElementStyle.top = + ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize[1]) + 'px'; + if (goog.isNull(this.documentFragment_)) { + this.documentFragment_ = document.createDocumentFragment(); } - this.drawAsync(zIndex, function(render) { - render.setFillStrokeStyle(style.getFill(), style.getStroke()); - render.setImageStyle(style.getImage()); - render.setTextStyle(style.getText()); - var type = geometry.getType(); - var renderGeometry = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_[type]; - // Do not assert since all kinds of geometries are not handled yet. - // In spite, render what we support. - if (renderGeometry) { - renderGeometry.call(render, geometry, null); - } - }); + goog.dom.appendChild(this.documentFragment_, tileElement); + this.tiles_[tileCoordKey] = tile; }; /** - * @inheritDoc - * @api + * FIXME empty description for jsdoc */ -ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry = - function(geometryCollectionGeometry, data) { - var geometries = geometryCollectionGeometry.getGeometriesArray(); - var renderers = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - var geometry = geometries[i]; - var geometryRenderer = renderers[geometry.getType()]; - // Do not assert since all kinds of geometries are not handled yet. - // In order to support hierarchies, delegate instead what we can to - // valid renderers. - if (geometryRenderer) { - geometryRenderer.call(this, geometry, data); +ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() { + if (!goog.isNull(this.documentFragment_)) { + if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { + goog.dom.appendChild(this.translateTarget_, this.documentFragment_); + } else { + goog.dom.appendChild(this.target, this.documentFragment_); } + this.documentFragment_ = null; } }; /** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawPointGeometry = - function(pointGeometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE); - replay.setImageStyle(this.imageStyle_); - replay.drawPointGeometry(pointGeometry, data); - replay.finish(context); - // default colors - var opacity = 1; - var brightness = 0; - var contrast = 1; - var hue = 0; - var saturation = 1; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.extent_, this.pixelRatio_, opacity, brightness, - contrast, hue, saturation, {}); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawLineStringGeometry = - function(lineStringGeometry, data) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry = - function(multiLineStringGeometry, data) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawMultiPointGeometry = - function(multiPointGeometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = replayGroup.getReplay(0, ol.render.ReplayType.IMAGE); - replay.setImageStyle(this.imageStyle_); - replay.drawMultiPointGeometry(multiPointGeometry, data); - replay.finish(context); - // default colors - var opacity = 1; - var brightness = 0; - var contrast = 1; - var hue = 0; - var saturation = 1; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.extent_, this.pixelRatio_, opacity, brightness, - contrast, hue, saturation, {}); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry = - function(multiPolygonGeometry, data) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawPolygonGeometry = - function(polygonGeometry, data) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawText = - function(flatCoordinates, offset, end, stride, geometry, data) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.setFillStrokeStyle = - function(fillStyle, strokeStyle) { -}; - - -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { - this.imageStyle_ = imageStyle; -}; - - -/** - * @inheritDoc - * @api + * @return {ol.Coordinate} Origin. */ -ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) { +ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() { + return this.origin_; }; /** - * @const - * @private - * @type {Object.<ol.geom.GeometryType, - * function(this: ol.render.webgl.Immediate, ol.geom.Geometry, - * Object)>} + * @return {number} Resolution. */ -ol.render.webgl.Immediate.GEOMETRY_RENDERERS_ = { - 'Point': ol.render.webgl.Immediate.prototype.drawPointGeometry, - 'MultiPoint': ol.render.webgl.Immediate.prototype.drawMultiPointGeometry, - 'GeometryCollection': - ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry +ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() { + return this.resolution_; }; -// This file is automatically generated, do not edit -goog.provide('ol.renderer.webgl.map.shader.Color'); - -goog.require('ol.webgl.shader'); - - /** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct + * @param {ol.Extent} extent Extent. + * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object. */ -ol.renderer.webgl.map.shader.ColorFragment = function() { - goog.base(this, ol.renderer.webgl.map.shader.ColorFragment.SOURCE); +ol.renderer.dom.TileLayerZ_.prototype.removeTilesOutsideExtent = + function(extent, opt_tileRange) { + var tileRange = this.tileGrid_.getTileRangeForExtentAndZ( + extent, this.tileCoordOrigin_[0], opt_tileRange); + /** @type {Array.<ol.Tile>} */ + var tilesToRemove = []; + var tile, tileCoordKey; + for (tileCoordKey in this.tiles_) { + tile = this.tiles_[tileCoordKey]; + if (!tileRange.contains(tile.tileCoord)) { + tilesToRemove.push(tile); + } + } + var i, ii; + for (i = 0, ii = tilesToRemove.length; i < ii; ++i) { + tile = tilesToRemove[i]; + tileCoordKey = ol.tilecoord.toString(tile.tileCoord); + goog.dom.removeNode(tile.getImage(this)); + delete this.tiles_[tileCoordKey]; + } }; -goog.inherits(ol.renderer.webgl.map.shader.ColorFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorFragment); - - -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; - - -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}'; - - -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.ColorFragment.SOURCE = goog.DEBUG ? - ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE : - ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE; - /** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct + * @param {goog.vec.Mat4.Number} transform Transform. */ -ol.renderer.webgl.map.shader.ColorVertex = function() { - goog.base(this, ol.renderer.webgl.map.shader.ColorVertex.SOURCE); +ol.renderer.dom.TileLayerZ_.prototype.setTransform = function(transform) { + if (!ol.vec.Mat4.equals2D(transform, this.transform_)) { + if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { + ol.dom.transformElement2D(this.target, transform, 6, + this.translateTarget_); + } else { + ol.dom.transformElement2D(this.target, transform, 6); + } + goog.vec.Mat4.setFromArray(this.transform_, transform); + } }; -goog.inherits(ol.renderer.webgl.map.shader.ColorVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorVertex); +goog.provide('ol.renderer.dom.VectorLayer'); -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; - +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.vec.Mat4'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.Event'); +goog.require('ol.render.EventType'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.render.canvas.ReplayGroup'); +goog.require('ol.renderer.dom.Layer'); +goog.require('ol.renderer.vector'); +goog.require('ol.vec.Mat4'); -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.ColorVertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; /** - * @const - * @type {string} + * @constructor + * @extends {ol.renderer.dom.Layer} + * @param {ol.layer.Vector} vectorLayer Vector layer. */ -ol.renderer.webgl.map.shader.ColorVertex.SOURCE = goog.DEBUG ? - ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE : - ol.renderer.webgl.map.shader.ColorVertex.OPTIMIZED_SOURCE; +ol.renderer.dom.VectorLayer = function(vectorLayer) { + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + var target = this.context_.canvas; + // Bootstrap sets the style max-width: 100% for all images, which breaks + // prevents the image from being displayed in FireFox. Workaround by + // overriding the max-width style. + target.style.maxWidth = 'none'; + target.style.position = 'absolute'; -/** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ -ol.renderer.webgl.map.shader.Color.Locations = function(gl, program) { + goog.base(this, vectorLayer, target); /** - * @type {WebGLUniformLocation} + * @private + * @type {boolean} */ - this.u_colorMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_colorMatrix' : 'f'); + this.dirty_ = false; /** - * @type {WebGLUniformLocation} + * @private + * @type {number} */ - this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'g'); + this.renderedRevision_ = -1; /** - * @type {WebGLUniformLocation} + * @private + * @type {number} */ - this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'e'); + this.renderedResolution_ = NaN; /** - * @type {WebGLUniformLocation} + * @private + * @type {ol.Extent} */ - this.u_texCoordMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_texCoordMatrix' : 'd'); + this.renderedExtent_ = ol.extent.createEmpty(); /** - * @type {WebGLUniformLocation} + * @private + * @type {function(ol.Feature, ol.Feature): number|null} */ - this.u_texture = gl.getUniformLocation( - program, goog.DEBUG ? 'u_texture' : 'h'); + this.renderedRenderOrder_ = null; /** - * @type {number} + * @private + * @type {ol.render.canvas.ReplayGroup} */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'b'); + this.replayGroup_ = null; /** - * @type {number} + * @private + * @type {goog.vec.Mat4.Number} */ - this.a_texCoord = gl.getAttribLocation( - program, goog.DEBUG ? 'a_texCoord' : 'c'); -}; - -// This file is automatically generated, do not edit -goog.provide('ol.renderer.webgl.map.shader.Default'); + this.transform_ = goog.vec.Mat4.createNumber(); -goog.require('ol.webgl.shader'); + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.elementTransform_ = goog.vec.Mat4.createNumber(); +}; +goog.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer); /** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct + * @inheritDoc */ -ol.renderer.webgl.map.shader.DefaultFragment = function() { - goog.base(this, ol.renderer.webgl.map.shader.DefaultFragment.SOURCE); -}; -goog.inherits(ol.renderer.webgl.map.shader.DefaultFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultFragment); +ol.renderer.dom.VectorLayer.prototype.composeFrame = + function(frameState, layerState) { + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector, + 'layer is an instance of ol.layer.Vector'); -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewRotation = viewState.rotation; + var viewResolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var viewWidth = frameState.size[0]; + var viewHeight = frameState.size[1]; + var imageWidth = viewWidth * pixelRatio; + var imageHeight = viewHeight * pixelRatio; + var transform = ol.vec.Mat4.makeTransform2D(this.transform_, + pixelRatio * viewWidth / 2, + pixelRatio * viewHeight / 2, + pixelRatio / viewResolution, + -pixelRatio / viewResolution, + -viewRotation, + -viewCenter[0], -viewCenter[1]); -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}'; + var context = this.context_; + // Clear the canvas and set the correct size + context.canvas.width = imageWidth; + context.canvas.height = imageHeight; -/** - * @const - * @type {string} - */ -ol.renderer.webgl.map.shader.DefaultFragment.SOURCE = goog.DEBUG ? - ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE : - ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE; + var elementTransform = ol.vec.Mat4.makeTransform2D(this.elementTransform_, + 0, 0, + 1 / pixelRatio, 1 / pixelRatio, + 0, + -(imageWidth - viewWidth) / 2 * pixelRatio, + -(imageHeight - viewHeight) / 2 * pixelRatio); + ol.dom.transformElement2D(context.canvas, elementTransform, 6); + + this.dispatchEvent_(ol.render.EventType.PRECOMPOSE, frameState, transform); + var replayGroup = this.replayGroup_; + + if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { + + context.globalAlpha = layerState.opacity; + replayGroup.replay(context, pixelRatio, transform, viewRotation, + layerState.managed ? frameState.skippedFeatureUids : {}); + + this.dispatchEvent_(ol.render.EventType.RENDER, frameState, transform); + } + + this.dispatchEvent_(ol.render.EventType.POSTCOMPOSE, frameState, transform); +}; /** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct + * @param {ol.render.EventType} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @param {goog.vec.Mat4.Number} transform Transform. + * @private */ -ol.renderer.webgl.map.shader.DefaultVertex = function() { - goog.base(this, ol.renderer.webgl.map.shader.DefaultVertex.SOURCE); +ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ = + function(type, frameState, transform) { + var context = this.context_; + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var render = new ol.render.canvas.Immediate( + context, frameState.pixelRatio, frameState.extent, transform, + frameState.viewState.rotation); + var event = new ol.render.Event(type, layer, render, frameState, + context, null); + layer.dispatchEvent(event); + render.flush(); + } }; -goog.inherits(ol.renderer.webgl.map.shader.DefaultVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultVertex); /** - * @const - * @type {string} + * @inheritDoc */ -ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; +ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.replayGroup_)) { + return undefined; + } else { + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var layer = this.getLayer(); + var layerState = frameState.layerStates[goog.getUid(layer)]; + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, + rotation, layerState.managed ? frameState.skippedFeatureUids : {}, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + goog.asserts.assert(goog.isDef(feature), 'received a feature'); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; /** - * @const - * @type {string} + * Handle changes in image style state. + * @param {goog.events.Event} event Image style change event. + * @private */ -ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; +ol.renderer.dom.VectorLayer.prototype.handleStyleImageChange_ = + function(event) { + this.renderIfReadyAndVisible(); +}; /** - * @const - * @type {string} + * @inheritDoc */ -ol.renderer.webgl.map.shader.DefaultVertex.SOURCE = goog.DEBUG ? - ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE : - ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE; +ol.renderer.dom.VectorLayer.prototype.prepareFrame = + function(frameState, layerState) { + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector, + 'layer is an instance of ol.layer.Vector'); + var vectorSource = vectorLayer.getSource(); + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); -/** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ -ol.renderer.webgl.map.shader.Default.Locations = function(gl, program) { + var animating = frameState.viewHints[ol.ViewHint.ANIMATING]; + var interacting = frameState.viewHints[ol.ViewHint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'f'); + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; + } - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'e'); + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); - /** - * @type {WebGLUniformLocation} - */ - this.u_texCoordMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_texCoordMatrix' : 'd'); + if (!goog.isDef(vectorLayerRenderOrder)) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } - /** - * @type {WebGLUniformLocation} - */ - this.u_texture = gl.getUniformLocation( - program, goog.DEBUG ? 'u_texture' : 'g'); + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'b'); + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, goog.DEBUG ? 'a_texCoord' : 'c'); + // FIXME dispose of old replayGroup in post render + goog.dispose(this.replayGroup_); + this.replayGroup_ = null; + + this.dirty_ = false; + + var replayGroup = + new ol.render.canvas.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, + resolution, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + var renderFeature = + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.dom.VectorLayer} + */ + function(feature) { + var styles; + if (goog.isDef(feature.getStyleFunction())) { + styles = feature.getStyleFunction().call(feature, resolution); + } else if (goog.isDef(vectorLayer.getStyleFunction())) { + styles = vectorLayer.getStyleFunction()(feature, resolution); + } + if (goog.isDefAndNotNull(styles)) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (!goog.isNull(vectorLayerRenderOrder)) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + goog.array.sort(features, vectorLayerRenderOrder); + goog.array.forEach(features, renderFeature, this); + } else { + vectorSource.forEachFeatureInExtentAtResolution( + extent, resolution, renderFeature, this); + } + replayGroup.finish(); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + + return true; }; -goog.provide('ol.renderer.webgl.Layer'); +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.style.Style>} styles Array of styles + * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.dom.VectorLayer.prototype.renderFeature = + function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!goog.isDefAndNotNull(styles)) { + return false; + } + var i, ii, loading = false; + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; + +goog.provide('ol.renderer.dom.Map'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.functions'); +goog.require('goog.style'); goog.require('goog.vec.Mat4'); -goog.require('goog.webgl'); -goog.require('ol.color.Matrix'); +goog.require('ol'); +goog.require('ol.RendererType'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.layer.Image'); goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); -goog.require('ol.render.webgl.Immediate'); -goog.require('ol.renderer.Layer'); -goog.require('ol.renderer.webgl.map.shader.Color'); -goog.require('ol.renderer.webgl.map.shader.Default'); -goog.require('ol.webgl.Buffer'); +goog.require('ol.render.canvas.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.dom.ImageLayer'); +goog.require('ol.renderer.dom.Layer'); +goog.require('ol.renderer.dom.TileLayer'); +goog.require('ol.renderer.dom.VectorLayer'); +goog.require('ol.source.State'); +goog.require('ol.vec.Mat4'); /** * @constructor - * @extends {ol.renderer.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Layer} layer Layer. + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. */ -ol.renderer.webgl.Layer = function(mapRenderer, layer) { +ol.renderer.dom.Map = function(container, map) { - goog.base(this, mapRenderer, layer); + goog.base(this, container, map); /** * @private - * @type {ol.webgl.Buffer} + * @type {CanvasRenderingContext2D} */ - this.arrayBuffer_ = new ol.webgl.Buffer([ - -1, -1, 0, 0, - 1, -1, 1, 0, - -1, 1, 0, 1, - 1, 1, 1, 1 - ]); + this.context_ = null; + if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE)) { + this.context_ = ol.dom.createCanvasContext2D(); + var canvas = this.context_.canvas; + canvas.style.position = 'absolute'; + canvas.style.width = '100%'; + canvas.style.height = '100%'; + canvas.className = ol.css.CLASS_UNSELECTABLE; + goog.dom.insertChildAt(container, canvas, 0); + } /** - * @protected - * @type {WebGLTexture} + * @private + * @type {!goog.vec.Mat4.Number} */ - this.texture = null; + this.transform_ = goog.vec.Mat4.createNumber(); /** - * @protected - * @type {WebGLFramebuffer} + * @type {!Element} + * @private */ - this.framebuffer = null; + this.layersPane_ = goog.dom.createElement(goog.dom.TagName.DIV); + this.layersPane_.className = ol.css.CLASS_UNSELECTABLE; + var style = this.layersPane_.style; + style.position = 'absolute'; + style.width = '100%'; + style.height = '100%'; - /** - * @protected - * @type {number|undefined} - */ - this.framebufferDimension = undefined; + // in IE < 9, we need to return false from ondragstart to cancel the default + // behavior of dragging images, which is interfering with the custom handler + // in the Drag interaction subclasses + if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { + this.layersPane_.ondragstart = goog.functions.FALSE; + this.layersPane_.onselectstart = goog.functions.FALSE; + } - /** - * @protected - * @type {!goog.vec.Mat4.Number} - */ - this.texCoordMatrix = goog.vec.Mat4.createNumber(); + // prevent the img context menu on mobile devices + goog.events.listen(this.layersPane_, goog.events.EventType.TOUCHSTART, + goog.events.Event.preventDefault); - /** - * @protected - * @type {!goog.vec.Mat4.Number} - */ - this.projectionMatrix = goog.vec.Mat4.createNumberIdentity(); + goog.dom.insertChildAt(container, this.layersPane_, 0); /** * @private - * @type {ol.color.Matrix} + * @type {boolean} */ - this.colorMatrix_ = new ol.color.Matrix(); + this.renderedVisible_ = true; - /** - * @private - * @type {ol.renderer.webgl.map.shader.Color.Locations} - */ - this.colorLocations_ = null; +}; +goog.inherits(ol.renderer.dom.Map, ol.renderer.Map); + + +/** + * @inheritDoc + */ +ol.renderer.dom.Map.prototype.disposeInternal = function() { + goog.dom.removeNode(this.layersPane_); + goog.base(this, 'disposeInternal'); +}; + + +/** + * @inheritDoc + */ +ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { + var layerRenderer; + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + layerRenderer = new ol.renderer.dom.ImageLayer(layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + layerRenderer = new ol.renderer.dom.TileLayer(layer); + } else if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && + ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + layerRenderer = new ol.renderer.dom.VectorLayer(layer); + } else { + goog.asserts.fail('unexpected layer configuration'); + return null; + } + return layerRenderer; +}; + + +/** + * @param {ol.render.EventType} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.dom.Map.prototype.dispatchComposeEvent_ = + function(type, frameState) { + var map = this.getMap(); + if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && map.hasListener(type)) { + var extent = frameState.extent; + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var rotation = viewState.rotation; + var context = this.context_; + var canvas = context.canvas; + + ol.vec.Mat4.makeTransform2D(this.transform_, + canvas.width / 2, + canvas.height / 2, + pixelRatio / viewState.resolution, + -pixelRatio / viewState.resolution, + -viewState.rotation, + -viewState.center[0], -viewState.center[1]); + var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio, + extent, this.transform_, rotation); + var composeEvent = new ol.render.Event(type, map, vectorContext, + frameState, context, null); + map.dispatchEvent(composeEvent); + vectorContext.flush(); + } +}; - /** - * @private - * @type {ol.renderer.webgl.map.shader.Default.Locations} - */ - this.defaultLocations_ = null; +/** + * @inheritDoc + */ +ol.renderer.dom.Map.prototype.getType = function() { + return ol.RendererType.DOM; }; -goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); /** - * @param {olx.FrameState} frameState Frame state. - * @param {number} framebufferDimension Framebuffer dimension. - * @protected + * @inheritDoc */ -ol.renderer.webgl.Layer.prototype.bindFramebuffer = - function(frameState, framebufferDimension) { +ol.renderer.dom.Map.prototype.renderFrame = function(frameState) { - var mapRenderer = this.getWebGLMapRenderer(); - var gl = mapRenderer.getGL(); + if (goog.isNull(frameState)) { + if (this.renderedVisible_) { + goog.style.setElementShown(this.layersPane_, false); + this.renderedVisible_ = false; + } + return; + } - if (!goog.isDef(this.framebufferDimension) || - this.framebufferDimension != framebufferDimension) { + /** + * @this {ol.renderer.dom.Map} + * @param {Element} elem + * @param {number} i + */ + var addChild; - frameState.postRenderFunctions.push( - goog.partial( - /** - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLFramebuffer} framebuffer Framebuffer. - * @param {WebGLTexture} texture Texture. - */ - function(gl, framebuffer, texture) { - if (!gl.isContextLost()) { - gl.deleteFramebuffer(framebuffer); - gl.deleteTexture(texture); - } - }, gl, this.framebuffer, this.texture)); + // appendChild is actually more performant than insertBefore + // in IE 7 and 8. http://jsperf.com/reattaching-dom-nodes + if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { + addChild = + /** + * @this {ol.renderer.dom.Map} + * @param {Element} elem + */ ( + function(elem) { + goog.dom.appendChild(this.layersPane_, elem); + }); + } else { + addChild = + /** + * @this {ol.renderer.dom.Map} + * @param {Element} elem + * @param {number} i + */ ( + function(elem, i) { + goog.dom.insertChildAt(this.layersPane_, elem, i); + }); + } - var texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, - framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, null); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, - goog.webgl.LINEAR); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, - goog.webgl.LINEAR); + var map = this.getMap(); + if (!(ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) && + (map.hasListener(ol.render.EventType.PRECOMPOSE) || + map.hasListener(ol.render.EventType.POSTCOMPOSE))) { + var canvas = this.context_.canvas; + var pixelRatio = frameState.pixelRatio; + canvas.width = frameState.size[0] * pixelRatio; + canvas.height = frameState.size[1] * pixelRatio; + } - var framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER, - goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0); + this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); - this.texture = texture; - this.framebuffer = framebuffer; - this.framebufferDimension = framebufferDimension; + var layerStatesArray = frameState.layerStatesArray; + var viewResolution = frameState.viewState.resolution; + var i, ii, layer, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + layer = layerState.layer; + layerRenderer = /** @type {ol.renderer.dom.Layer} */ ( + this.getLayerRenderer(layer)); + goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer, + 'renderer is an instance of ol.renderer.dom.Layer'); + addChild.call(this, layerRenderer.getTarget(), i); + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerState.sourceState == ol.source.State.READY) { + if (layerRenderer.prepareFrame(frameState, layerState)) { + layerRenderer.composeFrame(frameState, layerState); + } + } else { + layerRenderer.clearFrame(); + } + } - } else { - gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer); + var layerStates = frameState.layerStates; + var layerKey; + for (layerKey in this.getLayerRenderers()) { + if (!(layerKey in layerStates)) { + layerRenderer = this.getLayerRendererByKey(layerKey); + goog.asserts.assertInstanceof(layerRenderer, ol.renderer.dom.Layer, + 'renderer is an instance of ol.renderer.dom.Layer'); + goog.dom.removeNode(layerRenderer.getTarget()); + } + } + + if (!this.renderedVisible_) { + goog.style.setElementShown(this.layersPane_, true); + this.renderedVisible_ = true; } + this.calculateMatrices2D(frameState); + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); + + this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); }; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.layer.LayerState} layerState Layer state. - * @param {ol.webgl.Context} context Context. + * @fileoverview Constants used by the WebGL rendering, including all of the + * constants used from the WebGL context. For example, instead of using + * context.ARRAY_BUFFER, your code can use + * goog.webgl.ARRAY_BUFFER. The benefits for doing this include allowing + * the compiler to optimize your code so that the compiled code does not have to + * contain large strings to reference these properties, and reducing runtime + * property access. + * + * Values are taken from the WebGL Spec: + * https://www.khronos.org/registry/webgl/specs/1.0/#WEBGLRENDERINGCONTEXT */ -ol.renderer.webgl.Layer.prototype.composeFrame = - function(frameState, layerState, context) { - this.dispatchComposeEvent_( - ol.render.EventType.PRECOMPOSE, context, frameState); +goog.provide('goog.webgl'); - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); - var gl = context.getGL(); +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_BUFFER_BIT = 0x00000100; - var useColor = - layerState.brightness || - layerState.contrast != 1 || - layerState.hue || - layerState.saturation != 1; - var fragmentShader, vertexShader; - if (useColor) { - fragmentShader = ol.renderer.webgl.map.shader.ColorFragment.getInstance(); - vertexShader = ol.renderer.webgl.map.shader.ColorVertex.getInstance(); - } else { - fragmentShader = - ol.renderer.webgl.map.shader.DefaultFragment.getInstance(); - vertexShader = ol.renderer.webgl.map.shader.DefaultVertex.getInstance(); - } +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BUFFER_BIT = 0x00000400; - var program = context.getProgram(fragmentShader, vertexShader); - // FIXME colorLocations_ and defaultLocations_ should be shared somehow - var locations; - if (useColor) { - if (goog.isNull(this.colorLocations_)) { - locations = - new ol.renderer.webgl.map.shader.Color.Locations(gl, program); - this.colorLocations_ = locations; - } else { - locations = this.colorLocations_; - } - } else { - if (goog.isNull(this.defaultLocations_)) { - locations = - new ol.renderer.webgl.map.shader.Default.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - } +/** + * @const + * @type {number} + */ +goog.webgl.COLOR_BUFFER_BIT = 0x00004000; - if (context.useProgram(program)) { - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer( - locations.a_position, 2, goog.webgl.FLOAT, false, 16, 0); - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer( - locations.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8); - gl.uniform1i(locations.u_texture, 0); - } - gl.uniformMatrix4fv( - locations.u_texCoordMatrix, false, this.getTexCoordMatrix()); - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, - this.getProjectionMatrix()); - if (useColor) { - gl.uniformMatrix4fv(locations.u_colorMatrix, false, - this.colorMatrix_.getMatrix( - layerState.brightness, - layerState.contrast, - layerState.hue, - layerState.saturation - )); - } - gl.uniform1f(locations.u_opacity, layerState.opacity); - gl.bindTexture(goog.webgl.TEXTURE_2D, this.getTexture()); - gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); +/** + * @const + * @type {number} + */ +goog.webgl.POINTS = 0x0000; - this.dispatchComposeEvent_( - ol.render.EventType.POSTCOMPOSE, context, frameState); -}; +/** + * @const + * @type {number} + */ +goog.webgl.LINES = 0x0001; /** - * @param {ol.render.EventType} type Event type. - * @param {ol.webgl.Context} context WebGL context. - * @param {olx.FrameState} frameState Frame state. - * @private + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = - function(type, context, frameState) { - var layer = this.getLayer(); - if (layer.hasListener(type)) { - var viewState = frameState.viewState; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var extent = frameState.extent; - var center = viewState.center; - var rotation = viewState.rotation; - var size = frameState.size; - - var render = new ol.render.webgl.Immediate( - context, center, resolution, rotation, size, extent, pixelRatio); - var composeEvent = new ol.render.Event( - type, layer, render, null, frameState, null, context); - layer.dispatchEvent(composeEvent); - } -}; +goog.webgl.LINE_LOOP = 0x0002; /** - * @protected - * @return {ol.renderer.webgl.Map} MapRenderer. + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.getWebGLMapRenderer = function() { - return /** @type {ol.renderer.webgl.Map} */ (this.getMapRenderer()); -}; +goog.webgl.LINE_STRIP = 0x0003; /** - * @return {!goog.vec.Mat4.Number} Matrix. + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { - return this.texCoordMatrix; -}; +goog.webgl.TRIANGLES = 0x0004; /** - * @return {WebGLTexture} Texture. + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.getTexture = function() { - return this.texture; -}; +goog.webgl.TRIANGLE_STRIP = 0x0005; /** - * @return {!goog.vec.Mat4.Number} Matrix. + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { - return this.projectionMatrix; -}; +goog.webgl.TRIANGLE_FAN = 0x0006; /** - * Handle webglcontextlost. + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { - this.texture = null; - this.framebuffer = null; - this.framebufferDimension = undefined; -}; +goog.webgl.ZERO = 0; /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.layer.LayerState} layerState Layer state. - * @param {ol.webgl.Context} context Context. - * @return {boolean} whether composeFrame should be called. + * @const + * @type {number} */ -ol.renderer.webgl.Layer.prototype.prepareFrame = goog.abstractMethod; - -goog.provide('ol.renderer.webgl.ImageLayer'); +goog.webgl.ONE = 1; -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.vec.Mat4'); -goog.require('goog.webgl'); -goog.require('ol.Coordinate'); -goog.require('ol.Extent'); -goog.require('ol.ImageBase'); -goog.require('ol.ImageState'); -goog.require('ol.ViewHint'); -goog.require('ol.extent'); -goog.require('ol.layer.Image'); -goog.require('ol.proj'); -goog.require('ol.renderer.webgl.Layer'); +/** + * @const + * @type {number} + */ +goog.webgl.SRC_COLOR = 0x0300; /** - * @constructor - * @extends {ol.renderer.webgl.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Image} imageLayer Tile layer. + * @const + * @type {number} */ -ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { - - goog.base(this, mapRenderer, imageLayer); +goog.webgl.ONE_MINUS_SRC_COLOR = 0x0301; - /** - * The last rendered image. - * @private - * @type {?ol.ImageBase} - */ - this.image_ = null; -}; -goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); +/** + * @const + * @type {number} + */ +goog.webgl.SRC_ALPHA = 0x0302; /** - * @param {ol.ImageBase} image Image. - * @private - * @return {WebGLTexture} Texture. + * @const + * @type {number} */ -ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { +goog.webgl.ONE_MINUS_SRC_ALPHA = 0x0303; - // We meet the conditions to work with non-power of two textures. - // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support - // http://learningwebgl.com/blog/?p=2101 - var imageElement = image.getImage(); - var gl = this.getWebGLMapRenderer().getGL(); +/** + * @const + * @type {number} + */ +goog.webgl.DST_ALPHA = 0x0304; - var texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, - goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement); - - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, - goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, - goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR); +/** + * @const + * @type {number} + */ +goog.webgl.ONE_MINUS_DST_ALPHA = 0x0305; + - return texture; -}; +/** + * @const + * @type {number} + */ +goog.webgl.DST_COLOR = 0x0306; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg) { - var layer = this.getLayer(); - var source = layer.getSource(); - var resolution = frameState.viewState.resolution; - var rotation = frameState.viewState.rotation; - var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtPixel( - resolution, rotation, coordinate, skippedFeatureUids, +goog.webgl.ONE_MINUS_DST_COLOR = 0x0307; - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - return callback.call(thisArg, feature, layer); - }); -}; + +/** + * @const + * @type {number} + */ +goog.webgl.SRC_ALPHA_SATURATE = 0x0308; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.ImageLayer.prototype.prepareFrame = - function(frameState, layerState, context) { +goog.webgl.FUNC_ADD = 0x8006; - var gl = this.getWebGLMapRenderer().getGL(); - var viewState = frameState.viewState; - var viewCenter = viewState.center; - var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; +/** + * @const + * @type {number} + */ +goog.webgl.BLEND_EQUATION = 0x8009; - var image = this.image_; - var texture = this.texture; - var imageLayer = this.getLayer(); - goog.asserts.assertInstanceof(imageLayer, ol.layer.Image); - var imageSource = imageLayer.getSource(); - var hints = frameState.viewHints; +/** + * Same as BLEND_EQUATION + * @const + * @type {number} + */ +goog.webgl.BLEND_EQUATION_RGB = 0x8009; - var renderedExtent = frameState.extent; - if (goog.isDef(layerState.extent)) { - renderedExtent = ol.extent.getIntersection( - renderedExtent, layerState.extent); - } - if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && - !ol.extent.isEmpty(renderedExtent)) { - var projection = viewState.projection; - var sourceProjection = imageSource.getProjection(); - if (!goog.isNull(sourceProjection)) { - goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection)); - projection = sourceProjection; - } - var image_ = imageSource.getImage(renderedExtent, viewResolution, - frameState.pixelRatio, projection); - if (!goog.isNull(image_)) { - var imageState = image_.getState(); - if (imageState == ol.ImageState.IDLE) { - goog.events.listenOnce(image_, goog.events.EventType.CHANGE, - this.handleImageChange, false, this); - image_.load(); - } else if (imageState == ol.ImageState.LOADED) { - image = image_; - texture = this.createTexture_(image_); - if (!goog.isNull(this.texture)) { - frameState.postRenderFunctions.push( - goog.partial( - /** - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLTexture} texture Texture. - */ - function(gl, texture) { - if (!gl.isContextLost()) { - gl.deleteTexture(texture); - } - }, gl, this.texture)); - } - } - } - } - if (!goog.isNull(image)) { - goog.asserts.assert(!goog.isNull(texture)); +/** + * @const + * @type {number} + */ +goog.webgl.BLEND_EQUATION_ALPHA = 0x883D; - var canvas = this.getWebGLMapRenderer().getContext().getCanvas(); - this.updateProjectionMatrix_(canvas.width, canvas.height, - viewCenter, viewResolution, viewRotation, image.getExtent()); +/** + * @const + * @type {number} + */ +goog.webgl.FUNC_SUBTRACT = 0x800A; - // Translate and scale to flip the Y coord. - var texCoordMatrix = this.texCoordMatrix; - goog.vec.Mat4.makeIdentity(texCoordMatrix); - goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1); - goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0); - this.image_ = image; - this.texture = texture; +/** + * @const + * @type {number} + */ +goog.webgl.FUNC_REVERSE_SUBTRACT = 0x800B; - this.updateAttributions(frameState.attributions, image.getAttributions()); - this.updateLogos(frameState, imageSource); - } - return true; -}; +/** + * @const + * @type {number} + */ +goog.webgl.BLEND_DST_RGB = 0x80C8; /** - * @param {number} canvasWidth Canvas width. - * @param {number} canvasHeight Canvas height. - * @param {ol.Coordinate} viewCenter View center. - * @param {number} viewResolution View resolution. - * @param {number} viewRotation View rotation. - * @param {ol.Extent} imageExtent Image extent. - * @private + * @const + * @type {number} */ -ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = - function(canvasWidth, canvasHeight, viewCenter, - viewResolution, viewRotation, imageExtent) { +goog.webgl.BLEND_SRC_RGB = 0x80C9; - var canvasExtentWidth = canvasWidth * viewResolution; - var canvasExtentHeight = canvasHeight * viewResolution; - var projectionMatrix = this.projectionMatrix; - goog.vec.Mat4.makeIdentity(projectionMatrix); - goog.vec.Mat4.scale(projectionMatrix, - 2 / canvasExtentWidth, 2 / canvasExtentHeight, 1); - goog.vec.Mat4.rotateZ(projectionMatrix, -viewRotation); - goog.vec.Mat4.translate(projectionMatrix, - imageExtent[0] - viewCenter[0], - imageExtent[1] - viewCenter[1], - 0); - goog.vec.Mat4.scale(projectionMatrix, - (imageExtent[2] - imageExtent[0]) / 2, - (imageExtent[3] - imageExtent[1]) / 2, - 1); - goog.vec.Mat4.translate(projectionMatrix, 1, 1, 0); +/** + * @const + * @type {number} + */ +goog.webgl.BLEND_DST_ALPHA = 0x80CA; -}; -// This file is automatically generated, do not edit -goog.provide('ol.renderer.webgl.tilelayer.shader'); +/** + * @const + * @type {number} + */ +goog.webgl.BLEND_SRC_ALPHA = 0x80CB; -goog.require('ol.webgl.shader'); +/** + * @const + * @type {number} + */ +goog.webgl.CONSTANT_COLOR = 0x8001; /** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct + * @const + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Fragment = function() { - goog.base(this, ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE); -}; -goog.inherits(ol.renderer.webgl.tilelayer.shader.Fragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Fragment); +goog.webgl.ONE_MINUS_CONSTANT_COLOR = 0x8002; /** * @const - * @type {string} + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n'; +goog.webgl.CONSTANT_ALPHA = 0x8003; /** * @const - * @type {string} + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}'; +goog.webgl.ONE_MINUS_CONSTANT_ALPHA = 0x8004; /** * @const - * @type {string} + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE = goog.DEBUG ? - ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE : - ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE; - +goog.webgl.BLEND_COLOR = 0x8005; /** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct + * @const + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Vertex = function() { - goog.base(this, ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE); -}; -goog.inherits(ol.renderer.webgl.tilelayer.shader.Vertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Vertex); +goog.webgl.ARRAY_BUFFER = 0x8892; /** * @const - * @type {string} + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n v_texCoord = a_texCoord;\n}\n\n\n'; +goog.webgl.ELEMENT_ARRAY_BUFFER = 0x8893; /** * @const - * @type {string} + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}'; +goog.webgl.ARRAY_BUFFER_BINDING = 0x8894; /** * @const - * @type {string} + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE = goog.DEBUG ? - ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE : - ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE; - +goog.webgl.ELEMENT_ARRAY_BUFFER_BINDING = 0x8895; /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct + * @const + * @type {number} */ -ol.renderer.webgl.tilelayer.shader.Locations = function(gl, program) { +goog.webgl.STREAM_DRAW = 0x88E0; - /** - * @type {WebGLUniformLocation} - */ - this.u_texture = gl.getUniformLocation( - program, goog.DEBUG ? 'u_texture' : 'e'); - /** - * @type {WebGLUniformLocation} - */ - this.u_tileOffset = gl.getUniformLocation( - program, goog.DEBUG ? 'u_tileOffset' : 'd'); +/** + * @const + * @type {number} + */ +goog.webgl.STATIC_DRAW = 0x88E4; - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'b'); - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, goog.DEBUG ? 'a_texCoord' : 'c'); -}; +/** + * @const + * @type {number} + */ +goog.webgl.DYNAMIC_DRAW = 0x88E8; -// FIXME large resolutions lead to too large framebuffers :-( -// FIXME animated shaders! check in redraw -goog.provide('ol.renderer.webgl.TileLayer'); +/** + * @const + * @type {number} + */ +goog.webgl.BUFFER_SIZE = 0x8764; -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('goog.vec.Mat4'); -goog.require('goog.vec.Vec4'); -goog.require('goog.webgl'); -goog.require('ol.TileRange'); -goog.require('ol.TileState'); -goog.require('ol.extent'); -goog.require('ol.layer.Tile'); -goog.require('ol.math'); -goog.require('ol.renderer.webgl.Layer'); -goog.require('ol.renderer.webgl.tilelayer.shader'); -goog.require('ol.tilecoord'); -goog.require('ol.webgl.Buffer'); +/** + * @const + * @type {number} + */ +goog.webgl.BUFFER_USAGE = 0x8765; /** - * @constructor - * @extends {ol.renderer.webgl.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Tile} tileLayer Tile layer. + * @const + * @type {number} */ -ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { +goog.webgl.CURRENT_VERTEX_ATTRIB = 0x8626; - goog.base(this, mapRenderer, tileLayer); - /** - * @private - * @type {ol.webgl.shader.Fragment} - */ - this.fragmentShader_ = - ol.renderer.webgl.tilelayer.shader.Fragment.getInstance(); +/** + * @const + * @type {number} + */ +goog.webgl.FRONT = 0x0404; - /** - * @private - * @type {ol.webgl.shader.Vertex} - */ - this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance(); - /** - * @private - * @type {ol.renderer.webgl.tilelayer.shader.Locations} - */ - this.locations_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.BACK = 0x0405; - /** - * @private - * @type {ol.webgl.Buffer} - */ - this.renderArrayBuffer_ = new ol.webgl.Buffer([ - 0, 0, 0, 1, - 1, 0, 1, 1, - 0, 1, 0, 0, - 1, 1, 1, 0 - ]); - /** - * @private - * @type {ol.TileRange} - */ - this.renderedTileRange_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.FRONT_AND_BACK = 0x0408; - /** - * @private - * @type {ol.Extent} - */ - this.renderedFramebufferExtent_ = null; - /** - * @private - * @type {number} - */ - this.renderedRevision_ = -1; +/** + * @const + * @type {number} + */ +goog.webgl.CULL_FACE = 0x0B44; -}; -goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); + +/** + * @const + * @type {number} + */ +goog.webgl.BLEND = 0x0BE2; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { - var mapRenderer = this.getWebGLMapRenderer(); - var context = mapRenderer.getContext(); - context.deleteBuffer(this.renderArrayBuffer_); - goog.base(this, 'disposeInternal'); -}; +goog.webgl.DITHER = 0x0BD0; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { - goog.base(this, 'handleWebGLContextLost'); - this.locations_ = null; -}; +goog.webgl.STENCIL_TEST = 0x0B90; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.TileLayer.prototype.prepareFrame = - function(frameState, layerState, context) { +goog.webgl.DEPTH_TEST = 0x0B71; - var mapRenderer = this.getWebGLMapRenderer(); - var gl = context.getGL(); - var viewState = frameState.viewState; - var projection = viewState.projection; +/** + * @const + * @type {number} + */ +goog.webgl.SCISSOR_TEST = 0x0C11; - var tileLayer = this.getLayer(); - goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile); - var tileSource = tileLayer.getSource(); - var tileGrid = tileSource.getTileGridForProjection(projection); - var z = tileGrid.getZForResolution(viewState.resolution); - var tileResolution = tileGrid.getResolution(z); - var tilePixelSize = - tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); - var pixelRatio = tilePixelSize / tileGrid.getTileSize(z); - var tilePixelResolution = tileResolution / pixelRatio; - var tileGutter = tileSource.getGutter(); +/** + * @const + * @type {number} + */ +goog.webgl.POLYGON_OFFSET_FILL = 0x8037; - var center = viewState.center; - var extent; - if (tileResolution == viewState.resolution) { - center = this.snapCenterToPixel(center, tileResolution, frameState.size); - extent = ol.extent.getForViewAndSize( - center, tileResolution, viewState.rotation, frameState.size); - } else { - extent = frameState.extent; - } - var tileRange = tileGrid.getTileRangeForExtentAndResolution( - extent, tileResolution); - var framebufferExtent; - if (!goog.isNull(this.renderedTileRange_) && - this.renderedTileRange_.equals(tileRange) && - this.renderedRevision_ == tileSource.getRevision()) { - framebufferExtent = this.renderedFramebufferExtent_; - } else { +/** + * @const + * @type {number} + */ +goog.webgl.SAMPLE_ALPHA_TO_COVERAGE = 0x809E; - var tileRangeSize = tileRange.getSize(); - var maxDimension = Math.max( - tileRangeSize[0] * tilePixelSize, tileRangeSize[1] * tilePixelSize); - var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); - var framebufferExtentDimension = tilePixelResolution * framebufferDimension; - var origin = tileGrid.getOrigin(z); - var minX = origin[0] + tileRange.minX * tilePixelSize * tilePixelResolution; - var minY = origin[1] + tileRange.minY * tilePixelSize * tilePixelResolution; - framebufferExtent = [ - minX, minY, - minX + framebufferExtentDimension, minY + framebufferExtentDimension - ]; +/** + * @const + * @type {number} + */ +goog.webgl.SAMPLE_COVERAGE = 0x80A0; - this.bindFramebuffer(frameState, framebufferDimension); - gl.viewport(0, 0, framebufferDimension, framebufferDimension); - gl.clearColor(0, 0, 0, 0); - gl.clear(goog.webgl.COLOR_BUFFER_BIT); - gl.disable(goog.webgl.BLEND); +/** + * @const + * @type {number} + */ +goog.webgl.NO_ERROR = 0; - var program = context.getProgram(this.fragmentShader_, this.vertexShader_); - context.useProgram(program); - if (goog.isNull(this.locations_)) { - this.locations_ = - new ol.renderer.webgl.tilelayer.shader.Locations(gl, program); - } - context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.renderArrayBuffer_); - gl.enableVertexAttribArray(this.locations_.a_position); - gl.vertexAttribPointer( - this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0); - gl.enableVertexAttribArray(this.locations_.a_texCoord); - gl.vertexAttribPointer( - this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8); - gl.uniform1i(this.locations_.u_texture, 0); +/** + * @const + * @type {number} + */ +goog.webgl.INVALID_ENUM = 0x0500; - /** - * @type {Object.<number, Object.<string, ol.Tile>>} - */ - var tilesToDrawByZ = {}; - tilesToDrawByZ[z] = {}; - var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) { - return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED && - mapRenderer.isTileTextureLoaded(tile); - }, tileSource, pixelRatio, projection); - var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource, - tilesToDrawByZ, getTileIfLoaded); +/** + * @const + * @type {number} + */ +goog.webgl.INVALID_VALUE = 0x0501; - var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); - if (!goog.isDef(useInterimTilesOnError)) { - useInterimTilesOnError = true; - } - var allTilesLoaded = true; - var tmpExtent = ol.extent.createEmpty(); - var tmpTileRange = new ol.TileRange(0, 0, 0, 0); - var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { +/** + * @const + * @type {number} + */ +goog.webgl.INVALID_OPERATION = 0x0502; - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - if (goog.isDef(layerState.extent)) { - // ignore tiles outside layer extent - tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); - if (!ol.extent.intersects(tileExtent, layerState.extent)) { - continue; - } - } - tileState = tile.getState(); - if (tileState == ol.TileState.LOADED) { - if (mapRenderer.isTileTextureLoaded(tile)) { - tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile; - continue; - } - } else if (tileState == ol.TileState.EMPTY || - (tileState == ol.TileState.ERROR && - !useInterimTilesOnError)) { - continue; - } - allTilesLoaded = false; - fullyLoaded = tileGrid.forEachTileCoordParentTileRange( - tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); - if (!fullyLoaded) { - childTileRange = tileGrid.getTileCoordChildTileRange( - tile.tileCoord, tmpTileRange, tmpExtent); - if (!goog.isNull(childTileRange)) { - findLoadedTiles(z + 1, childTileRange); - } - } +/** + * @const + * @type {number} + */ +goog.webgl.OUT_OF_MEMORY = 0x0505; - } - } +/** + * @const + * @type {number} + */ +goog.webgl.CW = 0x0900; - /** @type {Array.<number>} */ - var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); - goog.array.sort(zs); - var u_tileOffset = goog.vec.Vec4.createFloat32(); - var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty; - for (i = 0, ii = zs.length; i < ii; ++i) { - tilesToDraw = tilesToDrawByZ[zs[i]]; - for (tileKey in tilesToDraw) { - tile = tilesToDraw[tileKey]; - tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); - sx = 2 * (tileExtent[2] - tileExtent[0]) / - framebufferExtentDimension; - sy = 2 * (tileExtent[3] - tileExtent[1]) / - framebufferExtentDimension; - tx = 2 * (tileExtent[0] - framebufferExtent[0]) / - framebufferExtentDimension - 1; - ty = 2 * (tileExtent[1] - framebufferExtent[1]) / - framebufferExtentDimension - 1; - goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty); - gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); - mapRenderer.bindTileTexture(tile, tilePixelSize, - tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR); - gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); - } - } - if (allTilesLoaded) { - this.renderedTileRange_ = tileRange; - this.renderedFramebufferExtent_ = framebufferExtent; - this.renderedRevision_ = tileSource.getRevision(); - } else { - this.renderedTileRange_ = null; - this.renderedFramebufferExtent_ = null; - this.renderedRevision_ = -1; - frameState.animate = true; - } +/** + * @const + * @type {number} + */ +goog.webgl.CCW = 0x0901; - } - this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - var tileTextureQueue = mapRenderer.getTileTextureQueue(); - this.manageTilePyramid( - frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, - tileLayer.getPreload(), - /** - * @param {ol.Tile} tile Tile. - */ - function(tile) { - if (tile.getState() == ol.TileState.LOADED && - !mapRenderer.isTileTextureLoaded(tile) && - !tileTextureQueue.isKeyQueued(tile.getKey())) { - tileTextureQueue.enqueue([ - tile, - tileGrid.getTileCoordCenter(tile.tileCoord), - tileGrid.getResolution(tile.tileCoord[0]), - tilePixelSize, tileGutter * pixelRatio - ]); - } - }, this); - this.scheduleExpireCache(frameState, tileSource); - this.updateLogos(frameState, tileSource); +/** + * @const + * @type {number} + */ +goog.webgl.LINE_WIDTH = 0x0B21; - var texCoordMatrix = this.texCoordMatrix; - goog.vec.Mat4.makeIdentity(texCoordMatrix); - goog.vec.Mat4.translate(texCoordMatrix, - (center[0] - framebufferExtent[0]) / - (framebufferExtent[2] - framebufferExtent[0]), - (center[1] - framebufferExtent[1]) / - (framebufferExtent[3] - framebufferExtent[1]), - 0); - if (viewState.rotation !== 0) { - goog.vec.Mat4.rotateZ(texCoordMatrix, viewState.rotation); - } - goog.vec.Mat4.scale(texCoordMatrix, - frameState.size[0] * viewState.resolution / - (framebufferExtent[2] - framebufferExtent[0]), - frameState.size[1] * viewState.resolution / - (framebufferExtent[3] - framebufferExtent[1]), - 1); - goog.vec.Mat4.translate(texCoordMatrix, - -0.5, - -0.5, - 0); - return true; -}; +/** + * @const + * @type {number} + */ +goog.webgl.ALIASED_POINT_SIZE_RANGE = 0x846D; -goog.provide('ol.renderer.webgl.VectorLayer'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('ol.ViewHint'); -goog.require('ol.extent'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.webgl.ReplayGroup'); -goog.require('ol.renderer.vector'); -goog.require('ol.renderer.webgl.Layer'); +/** + * @const + * @type {number} + */ +goog.webgl.ALIASED_LINE_WIDTH_RANGE = 0x846E; + +/** + * @const + * @type {number} + */ +goog.webgl.CULL_FACE_MODE = 0x0B45; /** - * @constructor - * @extends {ol.renderer.webgl.Layer} - * @param {ol.renderer.Map} mapRenderer Map renderer. - * @param {ol.layer.Vector} vectorLayer Vector layer. + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { +goog.webgl.FRONT_FACE = 0x0B46; - goog.base(this, mapRenderer, vectorLayer); - /** - * @private - * @type {boolean} - */ - this.dirty_ = false; +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_RANGE = 0x0B70; - /** - * @private - * @type {number} - */ - this.renderedRevision_ = -1; - /** - * @private - * @type {number} - */ - this.renderedResolution_ = NaN; +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_WRITEMASK = 0x0B72; - /** - * @private - * @type {ol.Extent} - */ - this.renderedExtent_ = ol.extent.createEmpty(); - /** - * @private - * @type {function(ol.Feature, ol.Feature): number|null} - */ - this.renderedRenderOrder_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_CLEAR_VALUE = 0x0B73; - /** - * @private - * @type {ol.render.webgl.ReplayGroup} - */ - this.replayGroup_ = null; -}; -goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_FUNC = 0x0B74; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer.prototype.composeFrame = - function(frameState, layerState, context) { - var viewState = frameState.viewState; - var replayGroup = this.replayGroup_; - if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { - replayGroup.replay(context, - viewState.center, viewState.resolution, viewState.rotation, - frameState.size, frameState.pixelRatio, layerState.opacity, - layerState.brightness, layerState.contrast, layerState.hue, - layerState.saturation, frameState.skippedFeatureUids); - } +goog.webgl.STENCIL_CLEAR_VALUE = 0x0B91; -}; + +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_FUNC = 0x0B92; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { - var replayGroup = this.replayGroup_; - if (!goog.isNull(replayGroup)) { - var mapRenderer = this.getWebGLMapRenderer(); - var context = mapRenderer.getContext(); - replayGroup.getDeleteResourcesFunction(context)(); - this.replayGroup_ = null; - } - goog.base(this, 'disposeInternal'); -}; +goog.webgl.STENCIL_FAIL = 0x0B94; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = - function(coordinate, frameState, callback, thisArg) { -}; +goog.webgl.STENCIL_PASS_DEPTH_FAIL = 0x0B95; /** - * Handle changes in image style state. - * @param {goog.events.Event} event Image style change event. - * @private + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer.prototype.handleImageChange_ = - function(event) { - this.renderIfReadyAndVisible(); -}; +goog.webgl.STENCIL_PASS_DEPTH_PASS = 0x0B96; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer.prototype.prepareFrame = - function(frameState, layerState, context) { +goog.webgl.STENCIL_REF = 0x0B97; - var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); - goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector); - var vectorSource = vectorLayer.getSource(); - this.updateAttributions( - frameState.attributions, vectorSource.getAttributions()); - this.updateLogos(frameState, vectorSource); +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_VALUE_MASK = 0x0B93; - if (!this.dirty_ && (frameState.viewHints[ol.ViewHint.ANIMATING] || - frameState.viewHints[ol.ViewHint.INTERACTING])) { - return true; - } - var frameStateExtent = frameState.extent; - var viewState = frameState.viewState; - var projection = viewState.projection; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var vectorLayerRevision = vectorLayer.getRevision(); - var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); - var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_WRITEMASK = 0x0B98; - if (!goog.isDef(vectorLayerRenderOrder)) { - vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; - } - var extent = ol.extent.buffer(frameStateExtent, - vectorLayerRenderBuffer * resolution); +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BACK_FUNC = 0x8800; - if (!this.dirty_ && - this.renderedResolution_ == resolution && - this.renderedRevision_ == vectorLayerRevision && - this.renderedRenderOrder_ == vectorLayerRenderOrder && - ol.extent.containsExtent(this.renderedExtent_, extent)) { - return true; - } - if (!goog.isNull(this.replayGroup_)) { - frameState.postRenderFunctions.push( - this.replayGroup_.getDeleteResourcesFunction(context)); - } +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BACK_FAIL = 0x8801; - this.dirty_ = false; - var replayGroup = new ol.render.webgl.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), - extent); - vectorSource.loadFeatures(extent, resolution, projection); - var renderFeature = - /** - * @param {ol.Feature} feature Feature. - * @this {ol.renderer.webgl.VectorLayer} - */ - function(feature) { - var styles; - if (goog.isDef(feature.getStyleFunction())) { - styles = feature.getStyleFunction().call(feature, resolution); - } else if (goog.isDef(vectorLayer.getStyleFunction())) { - styles = vectorLayer.getStyleFunction()(feature, resolution); - } - if (goog.isDefAndNotNull(styles)) { - var dirty = this.renderFeature( - feature, resolution, pixelRatio, styles, replayGroup); - this.dirty_ = this.dirty_ || dirty; - } - }; - if (!goog.isNull(vectorLayerRenderOrder)) { - /** @type {Array.<ol.Feature>} */ - var features = []; - vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - features.push(feature); - }, this); - goog.array.sort(features, vectorLayerRenderOrder); - goog.array.forEach(features, renderFeature, this); - } else { - vectorSource.forEachFeatureInExtentAtResolution( - extent, resolution, renderFeature, this); - } - replayGroup.finish(context); +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802; - this.renderedResolution_ = resolution; - this.renderedRevision_ = vectorLayerRevision; - this.renderedRenderOrder_ = vectorLayerRenderOrder; - this.renderedExtent_ = extent; - this.replayGroup_ = replayGroup; - return true; -}; +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BACK_PASS_DEPTH_PASS = 0x8803; /** - * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {Array.<ol.style.Style>} styles Array of styles - * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. - * @return {boolean} `true` if an image is loading. + * @const + * @type {number} */ -ol.renderer.webgl.VectorLayer.prototype.renderFeature = - function(feature, resolution, pixelRatio, styles, replayGroup) { - if (!goog.isDefAndNotNull(styles)) { - return false; - } - var i, ii, loading = false; - for (i = 0, ii = styles.length; i < ii; ++i) { - loading = ol.renderer.vector.renderFeature( - replayGroup, feature, styles[i], - ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), - this.handleImageChange_, this) || loading; - } - return loading; -}; +goog.webgl.STENCIL_BACK_REF = 0x8CA3; -goog.provide('ol.structs.LRUCache'); -goog.require('goog.asserts'); -goog.require('goog.object'); +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BACK_VALUE_MASK = 0x8CA4; +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_BACK_WRITEMASK = 0x8CA5; + /** - * Implements a Least-Recently-Used cache where the keys do not conflict with - * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring - * items from the cache is the responsibility of the user. - * @constructor - * @struct - * @template T + * @const + * @type {number} */ -ol.structs.LRUCache = function() { +goog.webgl.VIEWPORT = 0x0BA2; - /** - * @private - * @type {number} - */ - this.count_ = 0; - /** - * @private - * @type {Object.<string, ol.structs.LRUCacheEntry>} - */ - this.entries_ = {}; +/** + * @const + * @type {number} + */ +goog.webgl.SCISSOR_BOX = 0x0C10; - /** - * @private - * @type {?ol.structs.LRUCacheEntry} - */ - this.oldest_ = null; - /** - * @private - * @type {?ol.structs.LRUCacheEntry} - */ - this.newest_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.COLOR_CLEAR_VALUE = 0x0C22; -}; + +/** + * @const + * @type {number} + */ +goog.webgl.COLOR_WRITEMASK = 0x0C23; /** - * FIXME empty description for jsdoc + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.assertValid = function() { - if (this.count_ === 0) { - goog.asserts.assert(goog.object.isEmpty(this.entries_)); - goog.asserts.assert(goog.isNull(this.oldest_)); - goog.asserts.assert(goog.isNull(this.newest_)); - } else { - goog.asserts.assert(goog.object.getCount(this.entries_) == this.count_); - goog.asserts.assert(!goog.isNull(this.oldest_)); - goog.asserts.assert(goog.isNull(this.oldest_.older)); - goog.asserts.assert(!goog.isNull(this.newest_)); - goog.asserts.assert(goog.isNull(this.newest_.newer)); - var i, entry; - var older = null; - i = 0; - for (entry = this.oldest_; !goog.isNull(entry); entry = entry.newer) { - goog.asserts.assert(entry.older === older); - older = entry; - ++i; - } - goog.asserts.assert(i == this.count_); - var newer = null; - i = 0; - for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) { - goog.asserts.assert(entry.newer === newer); - newer = entry; - ++i; - } - goog.asserts.assert(i == this.count_); - } -}; +goog.webgl.UNPACK_ALIGNMENT = 0x0CF5; /** - * FIXME empty description for jsdoc + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.clear = function() { - this.count_ = 0; - this.entries_ = {}; - this.oldest_ = null; - this.newest_ = null; -}; +goog.webgl.PACK_ALIGNMENT = 0x0D05; /** - * @param {string} key Key. - * @return {boolean} Contains key. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.containsKey = function(key) { - return this.entries_.hasOwnProperty(key); -}; +goog.webgl.MAX_TEXTURE_SIZE = 0x0D33; /** - * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function - * to call for every entry from the oldest to the newer. This function takes - * 3 arguments (the entry value, the entry key and the LRUCache object). - * The return value is ignored. - * @param {S=} opt_this The object to use as `this` in `f`. - * @template S + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.forEach = function(f, opt_this) { - var entry = this.oldest_; - while (!goog.isNull(entry)) { - f.call(opt_this, entry.value_, entry.key_, this); - entry = entry.newer; - } -}; +goog.webgl.MAX_VIEWPORT_DIMS = 0x0D3A; /** - * @param {string} key Key. - * @return {T} Value. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.get = function(key) { - var entry = this.entries_[key]; - goog.asserts.assert(goog.isDef(entry)); - if (entry === this.newest_) { - return entry.value_; - } else if (entry === this.oldest_) { - this.oldest_ = this.oldest_.newer; - this.oldest_.older = null; - } else { - entry.newer.older = entry.older; - entry.older.newer = entry.newer; - } - entry.newer = null; - entry.older = this.newest_; - this.newest_.newer = entry; - this.newest_ = entry; - return entry.value_; -}; +goog.webgl.SUBPIXEL_BITS = 0x0D50; /** - * @return {number} Count. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.getCount = function() { - return this.count_; -}; +goog.webgl.RED_BITS = 0x0D52; /** - * @return {Array.<string>} Keys. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.getKeys = function() { - var keys = new Array(this.count_); - var i = 0; - var entry; - for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) { - keys[i++] = entry.key_; - } - goog.asserts.assert(i == this.count_); - return keys; -}; +goog.webgl.GREEN_BITS = 0x0D53; /** - * @return {Array.<T>} Values. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.getValues = function() { - var values = new Array(this.count_); - var i = 0; - var entry; - for (entry = this.newest_; !goog.isNull(entry); entry = entry.older) { - values[i++] = entry.value_; - } - goog.asserts.assert(i == this.count_); - return values; -}; +goog.webgl.BLUE_BITS = 0x0D54; /** - * @return {T} Last value. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.peekLast = function() { - goog.asserts.assert(!goog.isNull(this.oldest_)); - return this.oldest_.value_; -}; +goog.webgl.ALPHA_BITS = 0x0D55; /** - * @return {string} Last key. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.peekLastKey = function() { - goog.asserts.assert(!goog.isNull(this.oldest_)); - return this.oldest_.key_; -}; +goog.webgl.DEPTH_BITS = 0x0D56; /** - * @return {T} value Value. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.pop = function() { - goog.asserts.assert(!goog.isNull(this.oldest_)); - goog.asserts.assert(!goog.isNull(this.newest_)); - var entry = this.oldest_; - goog.asserts.assert(entry.key_ in this.entries_); - delete this.entries_[entry.key_]; - if (!goog.isNull(entry.newer)) { - entry.newer.older = null; - } - this.oldest_ = entry.newer; - if (goog.isNull(this.oldest_)) { - this.newest_ = null; - } - --this.count_; - return entry.value_; -}; +goog.webgl.STENCIL_BITS = 0x0D57; /** - * @param {string} key Key. - * @param {T} value Value. + * @const + * @type {number} */ -ol.structs.LRUCache.prototype.set = function(key, value) { - goog.asserts.assert(!(key in {})); - goog.asserts.assert(!(key in this.entries_)); - var entry = { - key_: key, - newer: null, - older: this.newest_, - value_: value - }; - if (goog.isNull(this.newest_)) { - this.oldest_ = entry; - } else { - this.newest_.newer = entry; - } - this.newest_ = entry; - this.entries_[key] = entry; - ++this.count_; -}; +goog.webgl.POLYGON_OFFSET_UNITS = 0x2A00; /** - * @typedef {{key_: string, - * newer: ol.structs.LRUCacheEntry, - * older: ol.structs.LRUCacheEntry, - * value_: *}} + * @const + * @type {number} */ -ol.structs.LRUCacheEntry; +goog.webgl.POLYGON_OFFSET_FACTOR = 0x8038; -goog.provide('ol.webgl.Context'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.log'); -goog.require('goog.object'); -goog.require('ol'); -goog.require('ol.webgl.Buffer'); -goog.require('ol.webgl.WebGLContextEventType'); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_BINDING_2D = 0x8069; /** - * @typedef {{buf: ol.webgl.Buffer, - * buffer: WebGLBuffer}} + * @const + * @type {number} */ -ol.webgl.BufferCacheEntry; +goog.webgl.SAMPLE_BUFFERS = 0x80A8; +/** + * @const + * @type {number} + */ +goog.webgl.SAMPLES = 0x80A9; + /** - * @classdesc - * A WebGL context for accessing low-level WebGL capabilities. - * - * @constructor - * @extends {goog.events.EventTarget} - * @param {HTMLCanvasElement} canvas Canvas. - * @param {WebGLRenderingContext} gl GL. - * @api + * @const + * @type {number} */ -ol.webgl.Context = function(canvas, gl) { +goog.webgl.SAMPLE_COVERAGE_VALUE = 0x80AA; - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = canvas; - /** - * @private - * @type {WebGLRenderingContext} - */ - this.gl_ = gl; +/** + * @const + * @type {number} + */ +goog.webgl.SAMPLE_COVERAGE_INVERT = 0x80AB; - /** - * @private - * @type {Object.<number, ol.webgl.BufferCacheEntry>} - */ - this.bufferCache_ = {}; - /** - * @private - * @type {Object.<number, WebGLShader>} - */ - this.shaderCache_ = {}; +/** + * @const + * @type {number} + */ +goog.webgl.COMPRESSED_TEXTURE_FORMATS = 0x86A3; - /** - * @private - * @type {Object.<string, WebGLProgram>} - */ - this.programCache_ = {}; - /** - * @private - * @type {WebGLProgram} - */ - this.currentProgram_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.DONT_CARE = 0x1100; - /** - * @type {boolean} - */ - this.hasOESElementIndexUint = goog.array.contains( - ol.WEBGL_EXTENSIONS, 'OES_element_index_uint'); - // use the OES_element_index_uint extension if available - if (this.hasOESElementIndexUint) { - var ext = gl.getExtension('OES_element_index_uint'); - goog.asserts.assert(!goog.isNull(ext)); - } +/** + * @const + * @type {number} + */ +goog.webgl.FASTEST = 0x1101; - goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST, - this.handleWebGLContextLost, false, this); - goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED, - this.handleWebGLContextRestored, false, this); -}; +/** + * @const + * @type {number} + */ +goog.webgl.NICEST = 0x1102; /** - * Just bind the buffer if it's in the cache. Otherwise create - * the WebGL buffer, bind it, populate it, and add an entry to - * the cache. - * @param {number} target Target. - * @param {ol.webgl.Buffer} buf Buffer. + * @const + * @type {number} */ -ol.webgl.Context.prototype.bindBuffer = function(target, buf) { - var gl = this.getGL(); - var arr = buf.getArray(); - var bufferKey = goog.getUid(buf); - if (bufferKey in this.bufferCache_) { - var bufferCacheEntry = this.bufferCache_[bufferKey]; - gl.bindBuffer(target, bufferCacheEntry.buffer); - } else { - var buffer = gl.createBuffer(); - gl.bindBuffer(target, buffer); - goog.asserts.assert(target == goog.webgl.ARRAY_BUFFER || - target == goog.webgl.ELEMENT_ARRAY_BUFFER); - var /** @type {ArrayBufferView} */ arrayBuffer; - if (target == goog.webgl.ARRAY_BUFFER) { - arrayBuffer = new Float32Array(arr); - } else if (target == goog.webgl.ELEMENT_ARRAY_BUFFER) { - arrayBuffer = this.hasOESElementIndexUint ? - new Uint32Array(arr) : new Uint16Array(arr); - } else { - goog.asserts.fail(); - } - gl.bufferData(target, arrayBuffer, buf.getUsage()); - this.bufferCache_[bufferKey] = { - buf: buf, - buffer: buffer - }; - } -}; +goog.webgl.GENERATE_MIPMAP_HINT = 0x8192; /** - * @param {ol.webgl.Buffer} buf Buffer. + * @const + * @type {number} */ -ol.webgl.Context.prototype.deleteBuffer = function(buf) { - var gl = this.getGL(); - var bufferKey = goog.getUid(buf); - goog.asserts.assert(bufferKey in this.bufferCache_); - var bufferCacheEntry = this.bufferCache_[bufferKey]; - if (!gl.isContextLost()) { - gl.deleteBuffer(bufferCacheEntry.buffer); - } - delete this.bufferCache_[bufferKey]; -}; +goog.webgl.BYTE = 0x1400; /** - * @inheritDoc + * @const + * @type {number} */ -ol.webgl.Context.prototype.disposeInternal = function() { - var gl = this.getGL(); - if (!gl.isContextLost()) { - goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) { - gl.deleteBuffer(bufferCacheEntry.buffer); - }); - goog.object.forEach(this.programCache_, function(program) { - gl.deleteProgram(program); - }); - goog.object.forEach(this.shaderCache_, function(shader) { - gl.deleteShader(shader); - }); - } -}; +goog.webgl.UNSIGNED_BYTE = 0x1401; /** - * @return {HTMLCanvasElement} Canvas. + * @const + * @type {number} */ -ol.webgl.Context.prototype.getCanvas = function() { - return this.canvas_; -}; +goog.webgl.SHORT = 0x1402; /** - * @return {WebGLRenderingContext} GL. - * @api + * @const + * @type {number} */ -ol.webgl.Context.prototype.getGL = function() { - return this.gl_; -}; +goog.webgl.UNSIGNED_SHORT = 0x1403; /** - * Get shader from the cache if it's in the cache. Otherwise, create - * the WebGL shader, compile it, and add entry to cache. - * @param {ol.webgl.Shader} shaderObject Shader object. - * @return {WebGLShader} Shader. + * @const + * @type {number} */ -ol.webgl.Context.prototype.getShader = function(shaderObject) { - var shaderKey = goog.getUid(shaderObject); - if (shaderKey in this.shaderCache_) { - return this.shaderCache_[shaderKey]; - } else { - var gl = this.getGL(); - var shader = gl.createShader(shaderObject.getType()); - gl.shaderSource(shader, shaderObject.getSource()); - gl.compileShader(shader); - if (goog.DEBUG) { - if (!gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) && - !gl.isContextLost()) { - goog.log.error(this.logger_, gl.getShaderInfoLog(shader)); - } - } - goog.asserts.assert( - gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) || - gl.isContextLost()); - this.shaderCache_[shaderKey] = shader; - return shader; - } -}; +goog.webgl.INT = 0x1404; /** - * Get the program from the cache if it's in the cache. Otherwise create - * the WebGL program, attach the shaders to it, and add an entry to the - * cache. - * @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader. - * @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader. - * @return {WebGLProgram} Program. + * @const + * @type {number} */ -ol.webgl.Context.prototype.getProgram = function( - fragmentShaderObject, vertexShaderObject) { - var programKey = - goog.getUid(fragmentShaderObject) + '/' + goog.getUid(vertexShaderObject); - if (programKey in this.programCache_) { - return this.programCache_[programKey]; - } else { - var gl = this.getGL(); - var program = gl.createProgram(); - gl.attachShader(program, this.getShader(fragmentShaderObject)); - gl.attachShader(program, this.getShader(vertexShaderObject)); - gl.linkProgram(program); - if (goog.DEBUG) { - if (!gl.getProgramParameter(program, goog.webgl.LINK_STATUS) && - !gl.isContextLost()) { - goog.log.error(this.logger_, gl.getProgramInfoLog(program)); - } - } - goog.asserts.assert( - gl.getProgramParameter(program, goog.webgl.LINK_STATUS) || - gl.isContextLost()); - this.programCache_[programKey] = program; - return program; - } -}; +goog.webgl.UNSIGNED_INT = 0x1405; /** - * FIXME empy description for jsdoc + * @const + * @type {number} */ -ol.webgl.Context.prototype.handleWebGLContextLost = function() { - goog.object.clear(this.bufferCache_); - goog.object.clear(this.shaderCache_); - goog.object.clear(this.programCache_); - this.currentProgram_ = null; -}; +goog.webgl.FLOAT = 0x1406; /** - * FIXME empy description for jsdoc + * @const + * @type {number} */ -ol.webgl.Context.prototype.handleWebGLContextRestored = function() { -}; +goog.webgl.DEPTH_COMPONENT = 0x1902; /** - * Just return false if that program is used already. Other use - * that program (call `gl.useProgram`) and make it the "current - * program". - * @param {WebGLProgram} program Program. - * @return {boolean} Changed. - * @api + * @const + * @type {number} */ -ol.webgl.Context.prototype.useProgram = function(program) { - if (program == this.currentProgram_) { - return false; - } else { - var gl = this.getGL(); - gl.useProgram(program); - this.currentProgram_ = program; - return true; - } -}; +goog.webgl.ALPHA = 0x1906; /** - * @private - * @type {goog.log.Logger} + * @const + * @type {number} */ -ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context'); - -// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE) +goog.webgl.RGB = 0x1907; -goog.provide('ol.renderer.webgl.Map'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.log'); -goog.require('goog.log.Logger'); -goog.require('goog.object'); -goog.require('goog.style'); -goog.require('goog.webgl'); -goog.require('ol'); -goog.require('ol.RendererType'); -goog.require('ol.css'); -goog.require('ol.dom'); -goog.require('ol.layer.Image'); -goog.require('ol.layer.Layer'); -goog.require('ol.layer.Tile'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.Event'); -goog.require('ol.render.EventType'); -goog.require('ol.render.webgl.Immediate'); -goog.require('ol.render.webgl.ReplayGroup'); -goog.require('ol.renderer.Map'); -goog.require('ol.renderer.vector'); -goog.require('ol.renderer.webgl.ImageLayer'); -goog.require('ol.renderer.webgl.Layer'); -goog.require('ol.renderer.webgl.TileLayer'); -goog.require('ol.renderer.webgl.VectorLayer'); -goog.require('ol.source.State'); -goog.require('ol.structs.LRUCache'); -goog.require('ol.structs.PriorityQueue'); -goog.require('ol.webgl'); -goog.require('ol.webgl.Context'); -goog.require('ol.webgl.WebGLContextEventType'); +/** + * @const + * @type {number} + */ +goog.webgl.RGBA = 0x1908; /** - * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}} + * @const + * @type {number} */ -ol.renderer.webgl.TextureCacheEntry; - +goog.webgl.LUMINANCE = 0x1909; /** - * @constructor - * @extends {ol.renderer.Map} - * @param {Element} container Container. - * @param {ol.Map} map Map. + * @const + * @type {number} */ -ol.renderer.webgl.Map = function(container, map) { - - goog.base(this, container, map); +goog.webgl.LUMINANCE_ALPHA = 0x190A; - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement(goog.dom.TagName.CANVAS)); - this.canvas_.style.width = '100%'; - this.canvas_.style.height = '100%'; - this.canvas_.className = ol.css.CLASS_UNSELECTABLE; - goog.dom.insertChildAt(container, this.canvas_, 0); - /** - * @private - * @type {number} - */ - this.clipTileCanvasSize_ = 0; +/** + * @const + * @type {number} + */ +goog.webgl.UNSIGNED_SHORT_4_4_4_4 = 0x8033; - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.clipTileContext_ = ol.dom.createCanvasContext2D(); - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; +/** + * @const + * @type {number} + */ +goog.webgl.UNSIGNED_SHORT_5_5_5_1 = 0x8034; - /** - * @private - * @type {WebGLRenderingContext} - */ - this.gl_ = ol.webgl.getContext(this.canvas_, { - antialias: true, - depth: false, - failIfMajorPerformanceCaveat: true, - preserveDrawingBuffer: false, - stencil: true - }); - goog.asserts.assert(!goog.isNull(this.gl_)); - /** - * @private - * @type {ol.webgl.Context} - */ - this.context_ = new ol.webgl.Context(this.canvas_, this.gl_); +/** + * @const + * @type {number} + */ +goog.webgl.UNSIGNED_SHORT_5_6_5 = 0x8363; - goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST, - this.handleWebGLContextLost, false, this); - goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED, - this.handleWebGLContextRestored, false, this); - /** - * @private - * @type {ol.structs.LRUCache.<ol.renderer.webgl.TextureCacheEntry|null>} - */ - this.textureCache_ = new ol.structs.LRUCache(); +/** + * @const + * @type {number} + */ +goog.webgl.FRAGMENT_SHADER = 0x8B30; - /** - * @private - * @type {ol.Coordinate} - */ - this.focus_ = null; - /** - * @private - * @type {ol.structs.PriorityQueue.<Array>} - */ - this.tileTextureQueue_ = new ol.structs.PriorityQueue( - goog.bind( - /** - * @param {Array.<*>} element Element. - * @return {number} Priority. - * @this {ol.renderer.webgl.Map} - */ - function(element) { - var tileCenter = /** @type {ol.Coordinate} */ (element[1]); - var tileResolution = /** @type {number} */ (element[2]); - var deltaX = tileCenter[0] - this.focus_[0]; - var deltaY = tileCenter[1] - this.focus_[1]; - return 65536 * Math.log(tileResolution) + - Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; - }, this), - /** - * @param {Array.<*>} element Element. - * @return {string} Key. - */ - function(element) { - return /** @type {ol.Tile} */ (element[0]).getKey(); - }); +/** + * @const + * @type {number} + */ +goog.webgl.VERTEX_SHADER = 0x8B31; - /** - * @private - * @type {ol.PostRenderFunction} - */ - this.loadNextTileTexture_ = goog.bind( - function(map, frameState) { - if (!this.tileTextureQueue_.isEmpty()) { - this.tileTextureQueue_.reprioritize(); - var element = this.tileTextureQueue_.dequeue(); - var tile = /** @type {ol.Tile} */ (element[0]); - var tileSize = /** @type {number} */ (element[3]); - var tileGutter = /** @type {number} */ (element[4]); - this.bindTileTexture( - tile, tileSize, tileGutter, goog.webgl.LINEAR, goog.webgl.LINEAR); - } - }, this); - /** - * @private - * @type {number} - */ - this.textureCacheFrameMarkerCount_ = 0; +/** + * @const + * @type {number} + */ +goog.webgl.MAX_VERTEX_ATTRIBS = 0x8869; - this.initializeGL_(); -}; -goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map); +/** + * @const + * @type {number} + */ +goog.webgl.MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; /** - * @param {ol.Tile} tile Tile. - * @param {number} tileSize Tile size. - * @param {number} tileGutter Tile gutter. - * @param {number} magFilter Mag filter. - * @param {number} minFilter Min filter. - */ -ol.renderer.webgl.Map.prototype.bindTileTexture = - function(tile, tileSize, tileGutter, magFilter, minFilter) { - var gl = this.getGL(); - var tileKey = tile.getKey(); - if (this.textureCache_.containsKey(tileKey)) { - var textureCacheEntry = this.textureCache_.get(tileKey); - goog.asserts.assert(!goog.isNull(textureCacheEntry)); - gl.bindTexture(goog.webgl.TEXTURE_2D, textureCacheEntry.texture); - if (textureCacheEntry.magFilter != magFilter) { - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter); - textureCacheEntry.magFilter = magFilter; - } - if (textureCacheEntry.minFilter != minFilter) { - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, minFilter); - textureCacheEntry.minFilter = minFilter; - } - } else { - var texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - if (tileGutter > 0) { - var clipTileCanvas = this.clipTileContext_.canvas; - var clipTileContext = this.clipTileContext_; - if (this.clipTileCanvasSize_ != tileSize) { - clipTileCanvas.width = tileSize; - clipTileCanvas.height = tileSize; - this.clipTileCanvasSize_ = tileSize; - } else { - clipTileContext.clearRect(0, 0, tileSize, tileSize); - } - clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, - tileSize, tileSize, 0, 0, tileSize, tileSize); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, - goog.webgl.RGBA, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, clipTileCanvas); - } else { - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, - goog.webgl.RGBA, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, tile.getImage()); - } - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, minFilter); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, - goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, - goog.webgl.CLAMP_TO_EDGE); - this.textureCache_.set(tileKey, { - texture: texture, - magFilter: magFilter, - minFilter: minFilter - }); - } -}; + * @const + * @type {number} + */ +goog.webgl.MAX_VARYING_VECTORS = 0x8DFC; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { - if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { - return new ol.renderer.webgl.ImageLayer(this, layer); - } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { - return new ol.renderer.webgl.TileLayer(this, layer); - } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { - return new ol.renderer.webgl.VectorLayer(this, layer); - } else { - goog.asserts.fail(); - return null; - } -}; +goog.webgl.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D; /** - * @param {ol.render.EventType} type Event type. - * @param {olx.FrameState} frameState Frame state. - * @private + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = - function(type, frameState) { - var map = this.getMap(); - if (map.hasListener(type)) { - var context = this.context_; +goog.webgl.MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; - var extent = frameState.extent; - var size = frameState.size; - var viewState = frameState.viewState; - var pixelRatio = frameState.pixelRatio; - var resolution = viewState.resolution; - var center = viewState.center; - var rotation = viewState.rotation; - var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio); +/** + * @const + * @type {number} + */ +goog.webgl.MAX_TEXTURE_IMAGE_UNITS = 0x8872; - var vectorContext = new ol.render.webgl.Immediate(context, - center, resolution, rotation, size, extent, pixelRatio); - var replayGroup = new ol.render.webgl.ReplayGroup(tolerance, extent); - var composeEvent = new ol.render.Event(type, map, vectorContext, - replayGroup, frameState, null, context); - map.dispatchEvent(composeEvent); - replayGroup.finish(context); - if (!replayGroup.isEmpty()) { - // use default color values - var opacity = 1; - var brightness = 0; - var contrast = 1; - var hue = 0; - var saturation = 1; - replayGroup.replay(context, center, resolution, rotation, size, - pixelRatio, opacity, brightness, contrast, hue, saturation, {}); - } - replayGroup.getDeleteResourcesFunction(context)(); +/** + * @const + * @type {number} + */ +goog.webgl.MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD; - vectorContext.flush(); - this.replayGroup = replayGroup; - } -}; + +/** + * @const + * @type {number} + */ +goog.webgl.SHADER_TYPE = 0x8B4F; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.disposeInternal = function() { - var gl = this.getGL(); - if (!gl.isContextLost()) { - this.textureCache_.forEach( - /** - * @param {?ol.renderer.webgl.TextureCacheEntry} textureCacheEntry - * Texture cache entry. - */ - function(textureCacheEntry) { - if (!goog.isNull(textureCacheEntry)) { - gl.deleteTexture(textureCacheEntry.texture); - } - }); - } - goog.dispose(this.context_); - goog.base(this, 'disposeInternal'); -}; +goog.webgl.DELETE_STATUS = 0x8B80; /** - * @param {ol.Map} map Map. - * @param {olx.FrameState} frameState Frame state. - * @private + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) { - var gl = this.getGL(); - var textureCacheEntry; - while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > - ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { - textureCacheEntry = this.textureCache_.peekLast(); - if (goog.isNull(textureCacheEntry)) { - if (+this.textureCache_.peekLastKey() == frameState.index) { - break; - } else { - --this.textureCacheFrameMarkerCount_; - } - } else { - gl.deleteTexture(textureCacheEntry.texture); - } - this.textureCache_.pop(); - } -}; +goog.webgl.LINK_STATUS = 0x8B82; /** - * @return {ol.webgl.Context} + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.getContext = function() { - return this.context_; -}; +goog.webgl.VALIDATE_STATUS = 0x8B83; /** - * @return {WebGLRenderingContext} GL. + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.getGL = function() { - return this.gl_; -}; +goog.webgl.ATTACHED_SHADERS = 0x8B85; /** - * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue. + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() { - return this.tileTextureQueue_; -}; +goog.webgl.ACTIVE_UNIFORMS = 0x8B86; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.getType = function() { - return ol.RendererType.WEBGL; -}; +goog.webgl.ACTIVE_ATTRIBUTES = 0x8B89; /** - * @param {goog.events.Event} event Event. - * @protected + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { - event.preventDefault(); - this.textureCache_.clear(); - this.textureCacheFrameMarkerCount_ = 0; - goog.object.forEach(this.getLayerRenderers(), - /** - * @param {ol.renderer.Layer} layerRenderer Layer renderer. - * @param {string} key Key. - * @param {Object.<string, ol.renderer.Layer>} object Object. - */ - function(layerRenderer, key, object) { - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer); - var webGLLayerRenderer = /** @type {ol.renderer.webgl.Layer} */ - (layerRenderer); - webGLLayerRenderer.handleWebGLContextLost(); - }); -}; +goog.webgl.SHADING_LANGUAGE_VERSION = 0x8B8C; /** - * @protected + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() { - this.initializeGL_(); - this.getMap().render(); -}; +goog.webgl.CURRENT_PROGRAM = 0x8B8D; /** - * @private + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.initializeGL_ = function() { - var gl = this.gl_; - gl.activeTexture(goog.webgl.TEXTURE0); - gl.blendFuncSeparate( - goog.webgl.SRC_ALPHA, goog.webgl.ONE_MINUS_SRC_ALPHA, - goog.webgl.ONE, goog.webgl.ONE_MINUS_SRC_ALPHA); - gl.disable(goog.webgl.CULL_FACE); - gl.disable(goog.webgl.DEPTH_TEST); - gl.disable(goog.webgl.SCISSOR_TEST); - gl.disable(goog.webgl.STENCIL_TEST); -}; +goog.webgl.NEVER = 0x0200; /** - * @param {ol.Tile} tile Tile. - * @return {boolean} Is tile texture loaded. + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { - return this.textureCache_.containsKey(tile.getKey()); -}; +goog.webgl.LESS = 0x0201; /** - * @private - * @type {goog.log.Logger} + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.logger_ = - goog.log.getLogger('ol.renderer.webgl.Map'); +goog.webgl.EQUAL = 0x0202; /** - * @inheritDoc + * @const + * @type {number} */ -ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { +goog.webgl.LEQUAL = 0x0203; - var context = this.getContext(); - var gl = this.getGL(); - if (gl.isContextLost()) { - return false; - } +/** + * @const + * @type {number} + */ +goog.webgl.GREATER = 0x0204; - if (goog.isNull(frameState)) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.canvas_, false); - this.renderedVisible_ = false; - } - return false; - } - this.focus_ = frameState.focus; +/** + * @const + * @type {number} + */ +goog.webgl.NOTEQUAL = 0x0205; - this.textureCache_.set((-frameState.index).toString(), null); - ++this.textureCacheFrameMarkerCount_; - /** @type {Array.<ol.layer.LayerState>} */ - var layerStatesToDraw = []; - var layerStatesArray = frameState.layerStatesArray; - var viewResolution = frameState.viewState.resolution; - var i, ii, layerRenderer, layerState; - for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { - layerState = layerStatesArray[i]; - if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && - layerState.sourceState == ol.source.State.READY) { - layerRenderer = this.getLayerRenderer(layerState.layer); - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer); - if (layerRenderer.prepareFrame(frameState, layerState, context)) { - layerStatesToDraw.push(layerState); - } - } - } +/** + * @const + * @type {number} + */ +goog.webgl.GEQUAL = 0x0206; - var width = frameState.size[0] * frameState.pixelRatio; - var height = frameState.size[1] * frameState.pixelRatio; - if (this.canvas_.width != width || this.canvas_.height != height) { - this.canvas_.width = width; - this.canvas_.height = height; - } - gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, null); +/** + * @const + * @type {number} + */ +goog.webgl.ALWAYS = 0x0207; - gl.clearColor(0, 0, 0, 0); - gl.clear(goog.webgl.COLOR_BUFFER_BIT); - gl.enable(goog.webgl.BLEND); - gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); - this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); +/** + * @const + * @type {number} + */ +goog.webgl.KEEP = 0x1E00; - for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { - layerState = layerStatesToDraw[i]; - layerRenderer = this.getLayerRenderer(layerState.layer); - goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer); - layerRenderer.composeFrame(frameState, layerState, context); - } - if (!this.renderedVisible_) { - goog.style.setElementShown(this.canvas_, true); - this.renderedVisible_ = true; - } +/** + * @const + * @type {number} + */ +goog.webgl.REPLACE = 0x1E01; - this.calculateMatrices2D(frameState); - if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > - ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { - frameState.postRenderFunctions.push(goog.bind(this.expireCache_, this)); - } +/** + * @const + * @type {number} + */ +goog.webgl.INCR = 0x1E02; - if (!this.tileTextureQueue_.isEmpty()) { - frameState.postRenderFunctions.push(this.loadNextTileTexture_); - frameState.animate = true; - } - this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); +/** + * @const + * @type {number} + */ +goog.webgl.DECR = 0x1E03; - this.scheduleRemoveUnusedLayerRenderers(frameState); - this.scheduleExpireIconCache(frameState); -}; +/** + * @const + * @type {number} + */ +goog.webgl.INVERT = 0x150A; -// FIXME recheck layer/map projection compatability when projection changes -// FIXME layer renderers should skip when they can't reproject -// FIXME add tilt and height? -goog.provide('ol.Map'); -goog.provide('ol.MapProperty'); +/** + * @const + * @type {number} + */ +goog.webgl.INCR_WRAP = 0x8507; -goog.require('goog.Uri.QueryData'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.async.AnimationDelay'); -goog.require('goog.async.nextTick'); -goog.require('goog.debug.Console'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.ViewportSizeMonitor'); -goog.require('goog.events'); -goog.require('goog.events.BrowserEvent'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventType'); -goog.require('goog.events.KeyHandler'); -goog.require('goog.events.KeyHandler.EventType'); -goog.require('goog.events.MouseWheelHandler'); -goog.require('goog.events.MouseWheelHandler.EventType'); -goog.require('goog.log'); -goog.require('goog.log.Level'); -goog.require('goog.object'); -goog.require('goog.style'); -goog.require('goog.vec.Mat4'); -goog.require('ol.Collection'); -goog.require('ol.CollectionEventType'); -goog.require('ol.MapBrowserEvent'); -goog.require('ol.MapBrowserEvent.EventType'); -goog.require('ol.MapBrowserEventHandler'); -goog.require('ol.MapEvent'); -goog.require('ol.MapEventType'); -goog.require('ol.Object'); -goog.require('ol.ObjectEvent'); -goog.require('ol.ObjectEventType'); -goog.require('ol.Pixel'); -goog.require('ol.PostRenderFunction'); -goog.require('ol.PreRenderFunction'); -goog.require('ol.RendererType'); -goog.require('ol.Size'); -goog.require('ol.TileQueue'); -goog.require('ol.View'); -goog.require('ol.ViewHint'); -goog.require('ol.control'); -goog.require('ol.extent'); -goog.require('ol.has'); -goog.require('ol.interaction'); -goog.require('ol.layer.Base'); -goog.require('ol.layer.Group'); -goog.require('ol.proj'); -goog.require('ol.proj.common'); -goog.require('ol.renderer.Map'); -goog.require('ol.renderer.canvas.Map'); -goog.require('ol.renderer.dom.Map'); -goog.require('ol.renderer.webgl.Map'); -goog.require('ol.structs.PriorityQueue'); -goog.require('ol.tilecoord'); -goog.require('ol.vec.Mat4'); + +/** + * @const + * @type {number} + */ +goog.webgl.DECR_WRAP = 0x8508; /** * @const - * @type {string} + * @type {number} */ -ol.OL3_URL = 'http://openlayers.org/'; +goog.webgl.VENDOR = 0x1F00; /** * @const - * @type {string} + * @type {number} */ -ol.OL3_LOGO_URL = 'data:image/png;base64,' + - 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' + - 'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' + - 'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' + - 'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' + - 'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' + - 'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' + - 'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' + - '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' + - 'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' + - 'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' + - 'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' + - 'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' + - 'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' + - 'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' + - 'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' + - 'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' + - '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' + - 'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' + - 'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' + - 'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' + - 'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC'; +goog.webgl.RENDERER = 0x1F01; /** - * @type {Array.<ol.RendererType>} + * @const + * @type {number} */ -ol.DEFAULT_RENDERER_TYPES = [ - ol.RendererType.CANVAS, - ol.RendererType.WEBGL, - ol.RendererType.DOM -]; +goog.webgl.VERSION = 0x1F02; /** - * @enum {string} + * @const + * @type {number} */ -ol.MapProperty = { - LAYERGROUP: 'layergroup', - SIZE: 'size', - TARGET: 'target', - VIEW: 'view' -}; +goog.webgl.NEAREST = 0x2600; +/** + * @const + * @type {number} + */ +goog.webgl.LINEAR = 0x2601; + /** - * @classdesc - * The map is the core component of OpenLayers. For a map to render, a view, - * one or more layers, and a target container are needed: - * - * var map = new ol.Map({ - * view: new ol.View({ - * center: [0, 0], - * zoom: 1 - * }), - * layers: [ - * new ol.layer.Tile({ - * source: new ol.source.MapQuest({layer: 'osm'}) - * }) - * ], - * target: 'map' - * }); - * - * The above snippet creates a map using a {@link ol.layer.Tile} to display - * {@link ol.source.MapQuest} OSM data and render it to a DOM element with the - * id `map`. - * - * The constructor places a viewport container (with CSS class name - * `ol-viewport`) in the target element (see `getViewport()`), and then two - * further elements within the viewport: one with CSS class name - * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with - * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent` - * option of {@link ol.Overlay} for the difference). The map itself is placed in - * a further element within the viewport, either DOM or Canvas, depending on the - * renderer. - * - * @constructor - * @extends {ol.Object} - * @param {olx.MapOptions} options Map options. - * @fires ol.MapBrowserEvent - * @fires ol.MapEvent - * @fires ol.render.Event#postcompose - * @fires ol.render.Event#precompose - * @api stable + * @const + * @type {number} */ -ol.Map = function(options) { +goog.webgl.NEAREST_MIPMAP_NEAREST = 0x2700; - goog.base(this); - var optionsInternal = ol.Map.createOptionsInternal(options); +/** + * @const + * @type {number} + */ +goog.webgl.LINEAR_MIPMAP_NEAREST = 0x2701; - /** - * @private - * @type {number} - */ - this.pixelRatio_ = goog.isDef(options.pixelRatio) ? - options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO; - /** - * @private - * @type {Object} - */ - this.logos_ = optionsInternal.logos; +/** + * @const + * @type {number} + */ +goog.webgl.NEAREST_MIPMAP_LINEAR = 0x2702; - /** - * @private - * @type {goog.async.AnimationDelay} - */ - this.animationDelay_ = - new goog.async.AnimationDelay(this.renderFrame_, undefined, this); - this.registerDisposable(this.animationDelay_); - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.coordinateToPixelMatrix_ = goog.vec.Mat4.createNumber(); +/** + * @const + * @type {number} + */ +goog.webgl.LINEAR_MIPMAP_LINEAR = 0x2703; + + +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_MAG_FILTER = 0x2800; + - /** - * @private - * @type {goog.vec.Mat4.Number} - */ - this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber(); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_MIN_FILTER = 0x2801; - /** - * @private - * @type {number} - */ - this.frameIndex_ = 0; - /** - * @private - * @type {?olx.FrameState} - */ - this.frameState_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_WRAP_S = 0x2802; - /** - * The extent at the previous 'moveend' event. - * @private - * @type {ol.Extent} - */ - this.previousExtent_ = ol.extent.createEmpty(); - /** - * @private - * @type {goog.events.Key} - */ - this.viewPropertyListenerKey_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_WRAP_T = 0x2803; - /** - * @private - * @type {Array.<goog.events.Key>} - */ - this.layerGroupPropertyListenerKeys_ = null; - /** - * @private - * @type {Element} - */ - this.viewport_ = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-viewport'); - this.viewport_.style.position = 'relative'; - this.viewport_.style.overflow = 'hidden'; - this.viewport_.style.width = '100%'; - this.viewport_.style.height = '100%'; - // prevent page zoom on IE >= 10 browsers - this.viewport_.style.msTouchAction = 'none'; - if (ol.has.TOUCH) { - this.viewport_.className = 'ol-touch'; - } +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_2D = 0x0DE1; - /** - * @private - * @type {Element} - */ - this.overlayContainer_ = goog.dom.createDom(goog.dom.TagName.DIV, - 'ol-overlaycontainer'); - goog.dom.appendChild(this.viewport_, this.overlayContainer_); - /** - * @private - * @type {Element} - */ - this.overlayContainerStopEvent_ = goog.dom.createDom(goog.dom.TagName.DIV, - 'ol-overlaycontainer-stopevent'); - goog.events.listen(this.overlayContainerStopEvent_, [ - goog.events.EventType.CLICK, - goog.events.EventType.DBLCLICK, - goog.events.EventType.MOUSEDOWN, - goog.events.EventType.TOUCHSTART, - goog.events.EventType.MSPOINTERDOWN, - ol.MapBrowserEvent.EventType.POINTERDOWN, - goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel' - ], goog.events.Event.stopPropagation); - goog.dom.appendChild(this.viewport_, this.overlayContainerStopEvent_); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE = 0x1702; - var mapBrowserEventHandler = new ol.MapBrowserEventHandler(this); - goog.events.listen(mapBrowserEventHandler, - goog.object.getValues(ol.MapBrowserEvent.EventType), - this.handleMapBrowserEvent, false, this); - this.registerDisposable(mapBrowserEventHandler); - /** - * @private - * @type {Element|Document} - */ - this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP = 0x8513; - /** - * @private - * @type {goog.events.KeyHandler} - */ - this.keyHandler_ = new goog.events.KeyHandler(); - goog.events.listen(this.keyHandler_, goog.events.KeyHandler.EventType.KEY, - this.handleBrowserEvent, false, this); - this.registerDisposable(this.keyHandler_); - var mouseWheelHandler = new goog.events.MouseWheelHandler(this.viewport_); - goog.events.listen(mouseWheelHandler, - goog.events.MouseWheelHandler.EventType.MOUSEWHEEL, - this.handleBrowserEvent, false, this); - this.registerDisposable(mouseWheelHandler); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_BINDING_CUBE_MAP = 0x8514; - /** - * @type {ol.Collection.<ol.control.Control>} - * @private - */ - this.controls_ = optionsInternal.controls; - /** - * @type {olx.DeviceOptions} - * @private - */ - this.deviceOptions_ = optionsInternal.deviceOptions; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; - /** - * @type {ol.Collection.<ol.interaction.Interaction>} - * @private - */ - this.interactions_ = optionsInternal.interactions; - /** - * @type {ol.Collection.<ol.Overlay>} - * @private - */ - this.overlays_ = optionsInternal.overlays; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; - /** - * @type {ol.renderer.Map} - * @private - */ - this.renderer_ = - new optionsInternal.rendererConstructor(this.viewport_, this); - this.registerDisposable(this.renderer_); - /** - * @private - */ - this.viewportSizeMonitor_ = new goog.dom.ViewportSizeMonitor(); - this.registerDisposable(this.viewportSizeMonitor_); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; - goog.events.listen(this.viewportSizeMonitor_, goog.events.EventType.RESIZE, - this.updateSize, false, this); - /** - * @private - * @type {ol.Coordinate} - */ - this.focus_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; - /** - * @private - * @type {Array.<ol.PreRenderFunction>} - */ - this.preRenderFunctions_ = []; - /** - * @private - * @type {Array.<ol.PostRenderFunction>} - */ - this.postRenderFunctions_ = []; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; - /** - * @private - * @type {ol.TileQueue} - */ - this.tileQueue_ = new ol.TileQueue( - goog.bind(this.getTilePriority, this), - goog.bind(this.handleTileChange_, this)); - /** - * Uids of features to skip at rendering time. - * @type {Object.<string, boolean>} - * @private - */ - this.skippedFeatureUids_ = {}; +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; - goog.events.listen( - this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP), - this.handleLayerGroupChanged_, false, this); - goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW), - this.handleViewChanged_, false, this); - goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE), - this.handleSizeChanged_, false, this); - goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET), - this.handleTargetChanged_, false, this); - // setProperties will trigger the rendering of the map if the map - // is "defined" already. - this.setProperties(optionsInternal.values); +/** + * @const + * @type {number} + */ +goog.webgl.MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; - this.controls_.forEach( - /** - * @param {ol.control.Control} control Control. - * @this {ol.Map} - */ - function(control) { - control.setMap(this); - }, this); - goog.events.listen(this.controls_, ol.CollectionEventType.ADD, - /** - * @param {ol.CollectionEvent} event Collection event. - */ - function(event) { - event.element.setMap(this); - }, false, this); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE0 = 0x84C0; - goog.events.listen(this.controls_, ol.CollectionEventType.REMOVE, - /** - * @param {ol.CollectionEvent} event Collection event. - */ - function(event) { - event.element.setMap(null); - }, false, this); - this.interactions_.forEach( - /** - * @param {ol.interaction.Interaction} interaction Interaction. - * @this {ol.Map} - */ - function(interaction) { - interaction.setMap(this); - }, this); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE1 = 0x84C1; - goog.events.listen(this.interactions_, ol.CollectionEventType.ADD, - /** - * @param {ol.CollectionEvent} event Collection event. - */ - function(event) { - event.element.setMap(this); - }, false, this); - goog.events.listen(this.interactions_, ol.CollectionEventType.REMOVE, - /** - * @param {ol.CollectionEvent} event Collection event. - */ - function(event) { - event.element.setMap(null); - }, false, this); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE2 = 0x84C2; - this.overlays_.forEach( - /** - * @param {ol.Overlay} overlay Overlay. - * @this {ol.Map} - */ - function(overlay) { - overlay.setMap(this); - }, this); - goog.events.listen(this.overlays_, ol.CollectionEventType.ADD, - /** - * @param {ol.CollectionEvent} event Collection event. - */ - function(event) { - event.element.setMap(this); - }, false, this); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE3 = 0x84C3; - goog.events.listen(this.overlays_, ol.CollectionEventType.REMOVE, - /** - * @param {ol.CollectionEvent} event Collection event. - */ - function(event) { - event.element.setMap(null); - }, false, this); -}; -goog.inherits(ol.Map, ol.Object); +/** + * @const + * @type {number} + */ +goog.webgl.TEXTURE4 = 0x84C4; /** - * Add the given control to the map. - * @param {ol.control.Control} control Control. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.addControl = function(control) { - var controls = this.getControls(); - goog.asserts.assert(goog.isDef(controls)); - controls.push(control); -}; +goog.webgl.TEXTURE5 = 0x84C5; /** - * Add the given interaction to the map. - * @param {ol.interaction.Interaction} interaction Interaction to add. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.addInteraction = function(interaction) { - var interactions = this.getInteractions(); - goog.asserts.assert(goog.isDef(interactions)); - interactions.push(interaction); -}; +goog.webgl.TEXTURE6 = 0x84C6; /** - * Adds the given layer to the top of this map. - * @param {ol.layer.Base} layer Layer. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.addLayer = function(layer) { - var layers = this.getLayerGroup().getLayers(); - layers.push(layer); -}; +goog.webgl.TEXTURE7 = 0x84C7; /** - * Add the given overlay to the map. - * @param {ol.Overlay} overlay Overlay. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.addOverlay = function(overlay) { - var overlays = this.getOverlays(); - goog.asserts.assert(goog.isDef(overlays)); - overlays.push(overlay); -}; +goog.webgl.TEXTURE8 = 0x84C8; /** - * Add functions to be called before rendering. This can be used for attaching - * animations before updating the map's view. The {@link ol.animation} - * namespace provides several static methods for creating prerender functions. - * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions. - * @api + * @const + * @type {number} */ -ol.Map.prototype.beforeRender = function(var_args) { - this.render(); - Array.prototype.push.apply(this.preRenderFunctions_, arguments); -}; +goog.webgl.TEXTURE9 = 0x84C9; /** - * @param {ol.PreRenderFunction} preRenderFunction Pre-render function. - * @return {boolean} Whether the preRenderFunction has been found and removed. + * @const + * @type {number} */ -ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) { - return goog.array.remove(this.preRenderFunctions_, preRenderFunction); -}; +goog.webgl.TEXTURE10 = 0x84CA; /** - * - * @inheritDoc + * @const + * @type {number} */ -ol.Map.prototype.disposeInternal = function() { - goog.dom.removeNode(this.viewport_); - goog.base(this, 'disposeInternal'); -}; +goog.webgl.TEXTURE11 = 0x84CB; /** - * Detect features that intersect a pixel on the viewport, and execute a - * callback with each intersecting feature. Layers included in the detection can - * be configured through `opt_layerFilter`. Feature overlays will always be - * included in the detection. - * @param {ol.Pixel} pixel Pixel. - * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature - * callback. If the detected feature is not on a layer, but on a - * {@link ol.FeatureOverlay}, then the 2nd argument to this function will - * be `null`. To stop detection, callback functions can return a truthy - * value. - * @param {S=} opt_this Value to use as `this` when executing `callback`. - * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer - * filter function, only layers which are visible and for which this - * function returns `true` will be tested for features. By default, all - * visible layers will be tested. Feature overlays will always be tested. - * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`. - * @return {T|undefined} Callback result, i.e. the return value of last - * callback execution, or the first truthy callback return value. - * @template S,T,U - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.forEachFeatureAtPixel = - function(pixel, callback, opt_this, opt_layerFilter, opt_this2) { - if (goog.isNull(this.frameState_)) { - return; - } - var coordinate = this.getCoordinateFromPixel(pixel); - var thisArg = goog.isDef(opt_this) ? opt_this : null; - var layerFilter = goog.isDef(opt_layerFilter) ? - opt_layerFilter : goog.functions.TRUE; - var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null; - return this.renderer_.forEachFeatureAtPixel( - coordinate, this.frameState_, callback, thisArg, - layerFilter, thisArg2); -}; +goog.webgl.TEXTURE12 = 0x84CC; /** - * Returns the geographical coordinate for a browser event. - * @param {Event} event Event. - * @return {ol.Coordinate} Coordinate. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getEventCoordinate = function(event) { - return this.getCoordinateFromPixel(this.getEventPixel(event)); -}; +goog.webgl.TEXTURE13 = 0x84CD; /** - * Returns the map pixel position for a browser event relative to the viewport. - * @param {Event} event Event. - * @return {ol.Pixel} Pixel. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getEventPixel = function(event) { - // Use the offsetX and offsetY values if available. - // See http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsetx and - // http://www.w3.org/TR/cssom-view/#dom-mouseevent-offsety - if (goog.isDef(event.offsetX) && goog.isDef(event.offsetY)) { - return [event.offsetX, event.offsetY]; - } else if (goog.isDef(event.changedTouches)) { - // offsetX and offsetY are not defined for Touch Event - // - // goog.style.getRelativePosition is based on event.targetTouches, - // but touchend and touchcancel events have no targetTouches when - // the last finger is removed from the screen. - // So we ourselves compute the position of touch events. - // See https://github.com/google/closure-library/pull/323 - var touch = event.changedTouches[0]; - var viewportPosition = goog.style.getClientPosition(this.viewport_); - return [ - touch.clientX - viewportPosition.x, - touch.clientY - viewportPosition.y - ]; - } else { - // Compute offsetX and offsetY values for browsers that don't implement - // cssom-view specification - var eventPosition = goog.style.getRelativePosition(event, this.viewport_); - return [eventPosition.x, eventPosition.y]; - } -}; +goog.webgl.TEXTURE14 = 0x84CE; /** - * Get the target in which this map is rendered. - * Note that this returns what is entered as an option or in setTarget: - * if that was an element, it returns an element; if a string, it returns that. - * @return {Element|string|undefined} The Element or id of the Element that the - * map is rendered in. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getTarget = function() { - return /** @type {Element|string|undefined} */ ( - this.get(ol.MapProperty.TARGET)); -}; -goog.exportProperty( - ol.Map.prototype, - 'getTarget', - ol.Map.prototype.getTarget); +goog.webgl.TEXTURE15 = 0x84CF; /** - * Get the DOM element into which this map is rendered. In contrast to - * `getTarget` this method always return an `Element`, or `null` if the - * map has no target. - * @return {Element} The element that the map is rendered in. - * @api + * @const + * @type {number} */ -ol.Map.prototype.getTargetElement = function() { - var target = this.getTarget(); - return goog.isDef(target) ? goog.dom.getElement(target) : null; -}; +goog.webgl.TEXTURE16 = 0x84D0; /** - * @param {ol.Pixel} pixel Pixel. - * @return {ol.Coordinate} Coordinate. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getCoordinateFromPixel = function(pixel) { - var frameState = this.frameState_; - if (goog.isNull(frameState)) { - return null; - } else { - var vec2 = pixel.slice(); - return ol.vec.Mat4.multVec2(frameState.pixelToCoordinateMatrix, vec2, vec2); - } -}; +goog.webgl.TEXTURE17 = 0x84D1; /** - * @return {ol.Collection.<ol.control.Control>} Controls. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getControls = function() { - return this.controls_; -}; +goog.webgl.TEXTURE18 = 0x84D2; /** - * @return {ol.Collection.<ol.Overlay>} Overlays. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getOverlays = function() { - return this.overlays_; -}; +goog.webgl.TEXTURE19 = 0x84D3; /** - * Gets the collection of {@link ol.interaction.Interaction} instances - * associated with this map. Modifying this collection changes the interactions - * associated with the map. - * - * Interactions are used for e.g. pan, zoom and rotate. - * @return {ol.Collection.<ol.interaction.Interaction>} Interactions. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getInteractions = function() { - return this.interactions_; -}; +goog.webgl.TEXTURE20 = 0x84D4; /** - * Get the layergroup associated with this map. - * @return {ol.layer.Group} A layer group containing the layers in this map. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getLayerGroup = function() { - return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP)); -}; -goog.exportProperty( - ol.Map.prototype, - 'getLayerGroup', - ol.Map.prototype.getLayerGroup); +goog.webgl.TEXTURE21 = 0x84D5; /** - * Get the collection of layers associated with this map. - * @return {!ol.Collection.<ol.layer.Base>} Layers. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getLayers = function() { - var layers = this.getLayerGroup().getLayers(); - return layers; -}; +goog.webgl.TEXTURE22 = 0x84D6; /** - * @param {ol.Coordinate} coordinate Coordinate. - * @return {ol.Pixel} Pixel. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getPixelFromCoordinate = function(coordinate) { - var frameState = this.frameState_; - if (goog.isNull(frameState)) { - return null; - } else { - var vec2 = coordinate.slice(0, 2); - return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2); - } -}; +goog.webgl.TEXTURE23 = 0x84D7; /** - * Get the map renderer. - * @return {ol.renderer.Map} Renderer + * @const + * @type {number} */ -ol.Map.prototype.getRenderer = function() { - return this.renderer_; -}; +goog.webgl.TEXTURE24 = 0x84D8; /** - * Get the size of this map. - * @return {ol.Size|undefined} The size in pixels of the map in the DOM. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getSize = function() { - return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE)); -}; -goog.exportProperty( - ol.Map.prototype, - 'getSize', - ol.Map.prototype.getSize); +goog.webgl.TEXTURE25 = 0x84D9; /** - * Get the view associated with this map. A view manages properties such as - * center and resolution. - * @return {ol.View} The view that controls this map. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getView = function() { - return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW)); -}; -goog.exportProperty( - ol.Map.prototype, - 'getView', - ol.Map.prototype.getView); +goog.webgl.TEXTURE26 = 0x84DA; /** - * @return {Element} Viewport. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.getViewport = function() { - return this.viewport_; -}; +goog.webgl.TEXTURE27 = 0x84DB; /** - * @return {Element} The map's overlay container. Elements added to this - * container will let mousedown and touchstart events through to the map, so - * clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent} - * events. + * @const + * @type {number} */ -ol.Map.prototype.getOverlayContainer = function() { - return this.overlayContainer_; -}; +goog.webgl.TEXTURE28 = 0x84DC; /** - * @return {Element} The map's overlay container. Elements added to this - * container won't let mousedown and touchstart events through to the map, so - * clicks and gestures on an overlay don't trigger any - * {@link ol.MapBrowserEvent}. + * @const + * @type {number} */ -ol.Map.prototype.getOverlayContainerStopEvent = function() { - return this.overlayContainerStopEvent_; -}; +goog.webgl.TEXTURE29 = 0x84DD; /** - * @param {ol.Tile} tile Tile. - * @param {string} tileSourceKey Tile source key. - * @param {ol.Coordinate} tileCenter Tile center. - * @param {number} tileResolution Tile resolution. - * @return {number} Tile priority. + * @const + * @type {number} */ -ol.Map.prototype.getTilePriority = - function(tile, tileSourceKey, tileCenter, tileResolution) { - // Filter out tiles at higher zoom levels than the current zoom level, or that - // are outside the visible extent. - var frameState = this.frameState_; - if (goog.isNull(frameState) || !(tileSourceKey in frameState.wantedTiles)) { - return ol.structs.PriorityQueue.DROP; - } - var coordKey = ol.tilecoord.toString(tile.tileCoord); - if (!frameState.wantedTiles[tileSourceKey][coordKey]) { - return ol.structs.PriorityQueue.DROP; - } - // Prioritize the highest zoom level tiles closest to the focus. - // Tiles at higher zoom levels are prioritized using Math.log(tileResolution). - // Within a zoom level, tiles are prioritized by the distance in pixels - // between the center of the tile and the focus. The factor of 65536 means - // that the prioritization should behave as desired for tiles up to - // 65536 * Math.log(2) = 45426 pixels from the focus. - var deltaX = tileCenter[0] - frameState.focus[0]; - var deltaY = tileCenter[1] - frameState.focus[1]; - return 65536 * Math.log(tileResolution) + - Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; -}; +goog.webgl.TEXTURE30 = 0x84DE; /** - * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @param {string=} opt_type Type. + * @const + * @type {number} */ -ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) { - var type = opt_type || browserEvent.type; - var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent); - this.handleMapBrowserEvent(mapBrowserEvent); -}; +goog.webgl.TEXTURE31 = 0x84DF; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle. + * @const + * @type {number} */ -ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { - if (goog.isNull(this.frameState_)) { - // With no view defined, we cannot translate pixels into geographical - // coordinates so interactions cannot be used. - return; - } - this.focus_ = mapBrowserEvent.coordinate; - mapBrowserEvent.frameState = this.frameState_; - var interactions = this.getInteractions(); - goog.asserts.assert(goog.isDef(interactions)); - var interactionsArray = interactions.getArray(); - var i; - if (this.dispatchEvent(mapBrowserEvent) !== false) { - for (i = interactionsArray.length - 1; i >= 0; i--) { - var interaction = interactionsArray[i]; - if (!interaction.getActive()) { - continue; - } - var cont = interaction.handleEvent(mapBrowserEvent); - if (!cont) { - break; - } - } - } -}; +goog.webgl.ACTIVE_TEXTURE = 0x84E0; /** - * @protected + * @const + * @type {number} */ -ol.Map.prototype.handlePostRender = function() { - - var frameState = this.frameState_; +goog.webgl.REPEAT = 0x2901; - // Manage the tile queue - // Image loads are expensive and a limited resource, so try to use them - // efficiently: - // * When the view is static we allow a large number of parallel tile loads - // to complete the frame as quickly as possible. - // * When animating or interacting, image loads can cause janks, so we reduce - // the maximum number of loads per frame and limit the number of parallel - // tile loads to remain reactive to view changes and to reduce the chance of - // loading tiles that will quickly disappear from view. - var tileQueue = this.tileQueue_; - if (!tileQueue.isEmpty()) { - var maxTotalLoading = 16; - var maxNewLoads = maxTotalLoading; - var tileSourceCount = 0; - if (!goog.isNull(frameState)) { - var hints = frameState.viewHints; - var deviceOptions = this.deviceOptions_; - if (hints[ol.ViewHint.ANIMATING]) { - maxTotalLoading = deviceOptions.loadTilesWhileAnimating === false ? - 0 : 8; - maxNewLoads = 2; - } - if (hints[ol.ViewHint.INTERACTING]) { - maxTotalLoading = deviceOptions.loadTilesWhileInteracting === false ? - 0 : 8; - maxNewLoads = 2; - } - tileSourceCount = goog.object.getCount(frameState.wantedTiles); - } - maxTotalLoading *= tileSourceCount; - maxNewLoads *= tileSourceCount; - if (tileQueue.getTilesLoading() < maxTotalLoading) { - tileQueue.reprioritize(); // FIXME only call if view has changed - tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads); - } - } - var postRenderFunctions = this.postRenderFunctions_; - var i, ii; - for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) { - postRenderFunctions[i](this, frameState); - } - postRenderFunctions.length = 0; -}; +/** + * @const + * @type {number} + */ +goog.webgl.CLAMP_TO_EDGE = 0x812F; /** - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleSizeChanged_ = function() { - this.render(); -}; +goog.webgl.MIRRORED_REPEAT = 0x8370; /** - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleTargetChanged_ = function() { - // target may be undefined, null, a string or an Element. - // If it's a string we convert it to an Element before proceeding. - // If it's not now an Element we remove the viewport from the DOM. - // If it's an Element we append the viewport element to it. +goog.webgl.FLOAT_VEC2 = 0x8B50; - var target = this.getTarget(); - /** - * @type {Element} - */ - var targetElement = goog.isDef(target) ? - goog.dom.getElement(target) : null; +/** + * @const + * @type {number} + */ +goog.webgl.FLOAT_VEC3 = 0x8B51; - this.keyHandler_.detach(); - if (goog.isNull(targetElement)) { - goog.dom.removeNode(this.viewport_); - } else { - goog.dom.appendChild(targetElement, this.viewport_); +/** + * @const + * @type {number} + */ +goog.webgl.FLOAT_VEC4 = 0x8B52; - var keyboardEventTarget = goog.isNull(this.keyboardEventTarget_) ? - targetElement : this.keyboardEventTarget_; - this.keyHandler_.attach(keyboardEventTarget); - } - this.updateSize(); - // updateSize calls setSize, so no need to call this.render - // ourselves here. -}; +/** + * @const + * @type {number} + */ +goog.webgl.INT_VEC2 = 0x8B53; /** - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleTileChange_ = function() { - this.render(); -}; +goog.webgl.INT_VEC3 = 0x8B54; /** - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleViewPropertyChanged_ = function() { - this.render(); -}; +goog.webgl.INT_VEC4 = 0x8B55; /** - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleViewChanged_ = function() { - if (!goog.isNull(this.viewPropertyListenerKey_)) { - goog.events.unlistenByKey(this.viewPropertyListenerKey_); - this.viewPropertyListenerKey_ = null; - } - var view = this.getView(); - if (!goog.isNull(view)) { - this.viewPropertyListenerKey_ = goog.events.listen( - view, ol.ObjectEventType.PROPERTYCHANGE, - this.handleViewPropertyChanged_, false, this); - } - this.render(); -}; +goog.webgl.BOOL = 0x8B56; /** - * @param {goog.events.Event} event Event. - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleLayerGroupMemberChanged_ = function(event) { - goog.asserts.assertInstanceof(event, goog.events.Event); - this.render(); -}; +goog.webgl.BOOL_VEC2 = 0x8B57; /** - * @param {ol.ObjectEvent} event Event. - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) { - goog.asserts.assertInstanceof(event, ol.ObjectEvent); - this.render(); -}; +goog.webgl.BOOL_VEC3 = 0x8B58; /** - * @private + * @const + * @type {number} */ -ol.Map.prototype.handleLayerGroupChanged_ = function() { - if (!goog.isNull(this.layerGroupPropertyListenerKeys_)) { - var length = this.layerGroupPropertyListenerKeys_.length; - for (var i = 0; i < length; ++i) { - goog.events.unlistenByKey(this.layerGroupPropertyListenerKeys_[i]); - } - this.layerGroupPropertyListenerKeys_ = null; - } - var layerGroup = this.getLayerGroup(); - if (goog.isDefAndNotNull(layerGroup)) { - this.layerGroupPropertyListenerKeys_ = [ - goog.events.listen( - layerGroup, ol.ObjectEventType.PROPERTYCHANGE, - this.handleLayerGroupPropertyChanged_, false, this), - goog.events.listen( - layerGroup, goog.events.EventType.CHANGE, - this.handleLayerGroupMemberChanged_, false, this) - ]; - } - this.render(); -}; +goog.webgl.BOOL_VEC4 = 0x8B59; /** - * Returns `true` if the map is defined, `false` otherwise. The map is defined - * if it is contained in `document`, visible, has non-zero height and width, and - * has a defined view. - * @return {boolean} Is defined. + * @const + * @type {number} */ -ol.Map.prototype.isDef = function() { - if (!goog.dom.contains(document, this.viewport_)) { - return false; - } - if (!goog.style.isElementShown(this.viewport_)) { - return false; - } - var size = this.getSize(); - if (!goog.isDefAndNotNull(size) || size[0] <= 0 || size[1] <= 0) { - return false; - } - var view = this.getView(); - if (goog.isNull(view) || !view.isDef()) { - return false; - } - return true; -}; +goog.webgl.FLOAT_MAT2 = 0x8B5A; /** - * @return {boolean} Is rendered. + * @const + * @type {number} */ -ol.Map.prototype.isRendered = function() { - return !goog.isNull(this.frameState_); -}; +goog.webgl.FLOAT_MAT3 = 0x8B5B; /** - * Requests an immediate render in a synchronous manner. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.renderSync = function() { - this.animationDelay_.fire(); -}; +goog.webgl.FLOAT_MAT4 = 0x8B5C; /** - * Requests a render frame; rendering will effectively occur at the next browser - * animation frame. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.render = function() { - if (!this.animationDelay_.isActive()) { - this.animationDelay_.start(); - } -}; +goog.webgl.SAMPLER_2D = 0x8B5E; /** - * Remove the given control from the map. - * @param {ol.control.Control} control Control. - * @return {ol.control.Control|undefined} The removed control (or undefined - * if the control was not found). - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.removeControl = function(control) { - var controls = this.getControls(); - goog.asserts.assert(goog.isDef(controls)); - if (goog.isDef(controls.remove(control))) { - return control; - } - return undefined; -}; +goog.webgl.SAMPLER_CUBE = 0x8B60; /** - * Remove the given interaction from the map. - * @param {ol.interaction.Interaction} interaction Interaction to remove. - * @return {ol.interaction.Interaction|undefined} The removed interaction (or - * undefined if the interaction was not found). - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.removeInteraction = function(interaction) { - var removed; - var interactions = this.getInteractions(); - goog.asserts.assert(goog.isDef(interactions)); - if (goog.isDef(interactions.remove(interaction))) { - removed = interaction; - } - return removed; -}; +goog.webgl.VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622; /** - * Removes the given layer from the map. - * @param {ol.layer.Base} layer Layer. - * @return {ol.layer.Base|undefined} The removed layer (or undefined if the - * layer was not found). - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.removeLayer = function(layer) { - var layers = this.getLayerGroup().getLayers(); - return layers.remove(layer); -}; +goog.webgl.VERTEX_ATTRIB_ARRAY_SIZE = 0x8623; /** - * Remove the given overlay from the map. - * @param {ol.Overlay} overlay Overlay. - * @return {ol.Overlay|undefined} The removed overlay (or undefined - * if the overlay was not found). - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.removeOverlay = function(overlay) { - var overlays = this.getOverlays(); - goog.asserts.assert(goog.isDef(overlays)); - if (goog.isDef(overlays.remove(overlay))) { - return overlay; - } - return undefined; -}; +goog.webgl.VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624; /** - * @param {number} time Time. - * @private + * @const + * @type {number} */ -ol.Map.prototype.renderFrame_ = function(time) { +goog.webgl.VERTEX_ATTRIB_ARRAY_TYPE = 0x8625; - var i, ii, viewState; - /** - * Check whether a size has non-zero width and height. Note that this - * function is here because the compiler doesn't recognize that size is - * defined in the frameState assignment below when the same code is inline in - * the condition below. The compiler inlines this function itself, so the - * resulting code is the same. - * - * @param {ol.Size} size The size to test. - * @return {boolean} Has non-zero width and height. - */ - function hasArea(size) { - return size[0] > 0 && size[1] > 0; - } +/** + * @const + * @type {number} + */ +goog.webgl.VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A; - var size = this.getSize(); - var view = this.getView(); - /** @type {?olx.FrameState} */ - var frameState = null; - if (goog.isDef(size) && hasArea(size) && - !goog.isNull(view) && view.isDef()) { - var viewHints = view.getHints(); - var layerStatesArray = this.getLayerGroup().getLayerStatesArray(); - var layerStates = {}; - for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { - layerStates[goog.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; - } - viewState = view.getState(); - frameState = /** @type {olx.FrameState} */ ({ - animate: false, - attributions: {}, - coordinateToPixelMatrix: this.coordinateToPixelMatrix_, - extent: null, - focus: goog.isNull(this.focus_) ? viewState.center : this.focus_, - index: this.frameIndex_++, - layerStates: layerStates, - layerStatesArray: layerStatesArray, - logos: goog.object.clone(this.logos_), - pixelRatio: this.pixelRatio_, - pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_, - postRenderFunctions: [], - size: size, - skippedFeatureUids: this.skippedFeatureUids_, - tileQueue: this.tileQueue_, - time: time, - usedTiles: {}, - viewState: viewState, - viewHints: viewHints, - wantedTiles: {} - }); - } - if (!goog.isNull(frameState)) { - var preRenderFunctions = this.preRenderFunctions_; - var n = 0, preRenderFunction; - for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) { - preRenderFunction = preRenderFunctions[i]; - if (preRenderFunction(this, frameState)) { - preRenderFunctions[n++] = preRenderFunction; - } - } - preRenderFunctions.length = n; +/** + * @const + * @type {number} + */ +goog.webgl.VERTEX_ATTRIB_ARRAY_POINTER = 0x8645; - frameState.extent = ol.extent.getForViewAndSize(viewState.center, - viewState.resolution, viewState.rotation, frameState.size); - } - this.frameState_ = frameState; - this.renderer_.renderFrame(frameState); +/** + * @const + * @type {number} + */ +goog.webgl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F; - if (!goog.isNull(frameState)) { - if (frameState.animate) { - this.render(); - } - Array.prototype.push.apply( - this.postRenderFunctions_, frameState.postRenderFunctions); - var idle = this.preRenderFunctions_.length === 0 && - !frameState.viewHints[ol.ViewHint.ANIMATING] && - !frameState.viewHints[ol.ViewHint.INTERACTING] && - !ol.extent.equals(frameState.extent, this.previousExtent_); +/** + * @const + * @type {number} + */ +goog.webgl.COMPILE_STATUS = 0x8B81; - if (idle) { - this.dispatchEvent( - new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState)); - ol.extent.clone(frameState.extent, this.previousExtent_); - } - } - this.dispatchEvent( - new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState)); +/** + * @const + * @type {number} + */ +goog.webgl.LOW_FLOAT = 0x8DF0; - goog.async.nextTick(this.handlePostRender, this); -}; +/** + * @const + * @type {number} + */ +goog.webgl.MEDIUM_FLOAT = 0x8DF1; /** - * Sets the layergroup of this map. - * @param {ol.layer.Group} layerGroup A layer group containing the layers in - * this map. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.setLayerGroup = function(layerGroup) { - this.set(ol.MapProperty.LAYERGROUP, layerGroup); -}; -goog.exportProperty( - ol.Map.prototype, - 'setLayerGroup', - ol.Map.prototype.setLayerGroup); +goog.webgl.HIGH_FLOAT = 0x8DF2; /** - * Set the size of this map. - * @param {ol.Size|undefined} size The size in pixels of the map in the DOM. - * @observable - * @api + * @const + * @type {number} */ -ol.Map.prototype.setSize = function(size) { - this.set(ol.MapProperty.SIZE, size); -}; -goog.exportProperty( - ol.Map.prototype, - 'setSize', - ol.Map.prototype.setSize); +goog.webgl.LOW_INT = 0x8DF3; /** - * Set the target element to render this map into. - * @param {Element|string|undefined} target The Element or id of the Element - * that the map is rendered in. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.setTarget = function(target) { - this.set(ol.MapProperty.TARGET, target); -}; -goog.exportProperty( - ol.Map.prototype, - 'setTarget', - ol.Map.prototype.setTarget); +goog.webgl.MEDIUM_INT = 0x8DF4; /** - * Set the view for this map. - * @param {ol.View} view The view that controls this map. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.setView = function(view) { - this.set(ol.MapProperty.VIEW, view); -}; -goog.exportProperty( - ol.Map.prototype, - 'setView', - ol.Map.prototype.setView); +goog.webgl.HIGH_INT = 0x8DF5; /** - * @param {ol.Feature} feature Feature. + * @const + * @type {number} */ -ol.Map.prototype.skipFeature = function(feature) { - var featureUid = goog.getUid(feature).toString(); - this.skippedFeatureUids_[featureUid] = true; - this.render(); -}; +goog.webgl.FRAMEBUFFER = 0x8D40; /** - * Force a recalculation of the map viewport size. This should be called when - * third-party code changes the size of the map viewport. - * @api stable + * @const + * @type {number} */ -ol.Map.prototype.updateSize = function() { - var target = this.getTarget(); +goog.webgl.RENDERBUFFER = 0x8D41; - /** - * @type {Element} - */ - var targetElement = goog.isDef(target) ? goog.dom.getElement(target) : null; - if (goog.isNull(targetElement)) { - this.setSize(undefined); - } else { - var size = goog.style.getContentBoxSize(targetElement); - this.setSize([size.width, size.height]); - } -}; +/** + * @const + * @type {number} + */ +goog.webgl.RGBA4 = 0x8056; /** - * @param {ol.Feature} feature Feature. + * @const + * @type {number} */ -ol.Map.prototype.unskipFeature = function(feature) { - var featureUid = goog.getUid(feature).toString(); - delete this.skippedFeatureUids_[featureUid]; - this.render(); -}; +goog.webgl.RGB5_A1 = 0x8057; /** - * @typedef {{controls: ol.Collection.<ol.control.Control>, - * deviceOptions: olx.DeviceOptions, - * interactions: ol.Collection.<ol.interaction.Interaction>, - * keyboardEventTarget: (Element|Document), - * logos: Object, - * overlays: ol.Collection.<ol.Overlay>, - * rendererConstructor: - * function(new: ol.renderer.Map, Element, ol.Map), - * values: Object.<string, *>}} + * @const + * @type {number} */ -ol.MapOptionsInternal; +goog.webgl.RGB565 = 0x8D62; /** - * @param {olx.MapOptions} options Map options. - * @return {ol.MapOptionsInternal} Internal map options. + * @const + * @type {number} */ -ol.Map.createOptionsInternal = function(options) { +goog.webgl.DEPTH_COMPONENT16 = 0x81A5; - /** - * @type {Element|Document} - */ - var keyboardEventTarget = null; - if (goog.isDef(options.keyboardEventTarget)) { - // cannot use goog.dom.getElement because its argument cannot be - // of type Document - keyboardEventTarget = goog.isString(options.keyboardEventTarget) ? - document.getElementById(options.keyboardEventTarget) : - options.keyboardEventTarget; - } - /** - * @type {Object.<string, *>} - */ - var values = {}; +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_INDEX = 0x1901; - var logos = {}; - if (!goog.isDef(options.logo) || - (goog.isBoolean(options.logo) && options.logo)) { - logos[ol.OL3_LOGO_URL] = ol.OL3_URL; - } else { - var logo = options.logo; - if (goog.isString(logo)) { - logos[logo] = ''; - } else if (goog.isObject(logo)) { - goog.asserts.assertString(logo.href); - goog.asserts.assertString(logo.src); - logos[logo.src] = logo.href; - } - } - var layerGroup = (options.layers instanceof ol.layer.Group) ? - options.layers : new ol.layer.Group({layers: options.layers}); - values[ol.MapProperty.LAYERGROUP] = layerGroup; +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_INDEX8 = 0x8D48; - values[ol.MapProperty.TARGET] = options.target; - values[ol.MapProperty.VIEW] = goog.isDef(options.view) ? - options.view : new ol.View(); +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_STENCIL = 0x84F9; - /** - * @type {function(new: ol.renderer.Map, Element, ol.Map)} - */ - var rendererConstructor = ol.renderer.Map; - /** - * @type {Array.<ol.RendererType>} - */ - var rendererTypes; - if (goog.isDef(options.renderer)) { - if (goog.isArray(options.renderer)) { - rendererTypes = options.renderer; - } else if (goog.isString(options.renderer)) { - rendererTypes = [options.renderer]; - } else { - goog.asserts.fail('Incorrect format for renderer option'); - } - } else { - rendererTypes = ol.DEFAULT_RENDERER_TYPES; - } +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_WIDTH = 0x8D42; - var i, ii; - for (i = 0, ii = rendererTypes.length; i < ii; ++i) { - /** @type {ol.RendererType} */ - var rendererType = rendererTypes[i]; - if (ol.ENABLE_CANVAS && rendererType == ol.RendererType.CANVAS) { - if (ol.has.CANVAS) { - rendererConstructor = ol.renderer.canvas.Map; - break; - } - } else if (ol.ENABLE_DOM && rendererType == ol.RendererType.DOM) { - if (ol.has.DOM) { - rendererConstructor = ol.renderer.dom.Map; - break; - } - } else if (ol.ENABLE_WEBGL && rendererType == ol.RendererType.WEBGL) { - if (ol.has.WEBGL) { - rendererConstructor = ol.renderer.webgl.Map; - break; - } - } - } - var controls; - if (goog.isDef(options.controls)) { - if (goog.isArray(options.controls)) { - controls = new ol.Collection(goog.array.clone(options.controls)); - } else { - goog.asserts.assertInstanceof(options.controls, ol.Collection); - controls = options.controls; - } - } else { - controls = ol.control.defaults(); - } +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_HEIGHT = 0x8D43; - var deviceOptions = goog.isDef(options.deviceOptions) ? - options.deviceOptions : /** @type {olx.DeviceOptions} */ ({}); - var interactions; - if (goog.isDef(options.interactions)) { - if (goog.isArray(options.interactions)) { - interactions = new ol.Collection(goog.array.clone(options.interactions)); - } else { - goog.asserts.assertInstanceof(options.interactions, ol.Collection); - interactions = options.interactions; - } - } else { - interactions = ol.interaction.defaults(); - } +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_INTERNAL_FORMAT = 0x8D44; - var overlays; - if (goog.isDef(options.overlays)) { - if (goog.isArray(options.overlays)) { - overlays = new ol.Collection(goog.array.clone(options.overlays)); - } else { - goog.asserts.assertInstanceof(options.overlays, ol.Collection); - overlays = options.overlays; - } - } else { - overlays = new ol.Collection(); - } - return { - controls: controls, - deviceOptions: deviceOptions, - interactions: interactions, - keyboardEventTarget: keyboardEventTarget, - logos: logos, - overlays: overlays, - rendererConstructor: rendererConstructor, - values: values - }; +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_RED_SIZE = 0x8D50; -}; +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_GREEN_SIZE = 0x8D51; -ol.proj.common.add(); +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_BLUE_SIZE = 0x8D52; -if (goog.DEBUG) { - (function() { - goog.debug.Console.autoInstall(); - var logger = goog.log.getLogger('ol'); - logger.setLevel(goog.log.Level.FINEST); - })(); -} -goog.provide('ol.Overlay'); -goog.provide('ol.OverlayPositioning'); -goog.provide('ol.OverlayProperty'); +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_ALPHA_SIZE = 0x8D53; -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.style'); -goog.require('ol.Coordinate'); -goog.require('ol.Map'); -goog.require('ol.MapEventType'); -goog.require('ol.Object'); + +/** + * @const + * @type {number} + */ +goog.webgl.RENDERBUFFER_DEPTH_SIZE = 0x8D54; /** - * @enum {string} + * @const + * @type {number} */ -ol.OverlayProperty = { - ELEMENT: 'element', - MAP: 'map', - OFFSET: 'offset', - POSITION: 'position', - POSITIONING: 'positioning' -}; +goog.webgl.RENDERBUFFER_STENCIL_SIZE = 0x8D55; /** - * Overlay position: `'bottom-left'`, `'bottom-center'`, `'bottom-right'`, - * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`, - * `'top-center'`, `'top-right'` - * @enum {string} - * @api stable + * @const + * @type {number} */ -ol.OverlayPositioning = { - BOTTOM_LEFT: 'bottom-left', - BOTTOM_CENTER: 'bottom-center', - BOTTOM_RIGHT: 'bottom-right', - CENTER_LEFT: 'center-left', - CENTER_CENTER: 'center-center', - CENTER_RIGHT: 'center-right', - TOP_LEFT: 'top-left', - TOP_CENTER: 'top-center', - TOP_RIGHT: 'top-right' -}; +goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0; + +/** + * @const + * @type {number} + */ +goog.webgl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1; /** - * @classdesc - * Like {@link ol.control.Control}, Overlays are visible widgets. - * Unlike Controls, they are not in a fixed position on the screen, but are tied - * to a geographical coordinate, so panning the map will move an Overlay but not - * a Control. - * - * Example: - * - * var popup = new ol.Overlay({ - * element: document.getElementById('popup') - * }); - * popup.setPosition(coordinate); - * map.addOverlay(popup); - * - * @constructor - * @extends {ol.Object} - * @param {olx.OverlayOptions} options Overlay options. - * @api stable + * @const + * @type {number} */ -ol.Overlay = function(options) { +goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2; - goog.base(this); - /** - * @private - * @type {boolean} - */ - this.insertFirst_ = goog.isDef(options.insertFirst) ? - options.insertFirst : true; +/** + * @const + * @type {number} + */ +goog.webgl.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3; - /** - * @private - * @type {boolean} - */ - this.stopEvent_ = goog.isDef(options.stopEvent) ? options.stopEvent : true; - /** - * @private - * @type {Element} - */ - this.element_ = goog.dom.createElement(goog.dom.TagName.DIV); - this.element_.style.position = 'absolute'; +/** + * @const + * @type {number} + */ +goog.webgl.COLOR_ATTACHMENT0 = 0x8CE0; - /** - * @private - * @type {{bottom_: string, - * left_: string, - * right_: string, - * top_: string, - * visible: boolean}} - */ - this.rendered_ = { - bottom_: '', - left_: '', - right_: '', - top_: '', - visible: true - }; - /** - * @private - * @type {goog.events.Key} - */ - this.mapPostrenderListenerKey_ = null; +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_ATTACHMENT = 0x8D00; - goog.events.listen( - this, ol.Object.getChangeEventType(ol.OverlayProperty.ELEMENT), - this.handleElementChanged, false, this); - goog.events.listen( - this, ol.Object.getChangeEventType(ol.OverlayProperty.MAP), - this.handleMapChanged, false, this); +/** + * @const + * @type {number} + */ +goog.webgl.STENCIL_ATTACHMENT = 0x8D20; - goog.events.listen( - this, ol.Object.getChangeEventType(ol.OverlayProperty.OFFSET), - this.handleOffsetChanged, false, this); - goog.events.listen( - this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITION), - this.handlePositionChanged, false, this); +/** + * @const + * @type {number} + */ +goog.webgl.DEPTH_STENCIL_ATTACHMENT = 0x821A; - goog.events.listen( - this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING), - this.handlePositioningChanged, false, this); - if (goog.isDef(options.element)) { - this.setElement(options.element); - } +/** + * @const + * @type {number} + */ +goog.webgl.NONE = 0; - this.setOffset(goog.isDef(options.offset) ? options.offset : [0, 0]); - this.setPositioning(goog.isDef(options.positioning) ? - /** @type {ol.OverlayPositioning} */ (options.positioning) : - ol.OverlayPositioning.TOP_LEFT); +/** + * @const + * @type {number} + */ +goog.webgl.FRAMEBUFFER_COMPLETE = 0x8CD5; - if (goog.isDef(options.position)) { - this.setPosition(options.position); - } -}; -goog.inherits(ol.Overlay, ol.Object); +/** + * @const + * @type {number} + */ +goog.webgl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6; /** - * Get the DOM element of this overlay. - * @return {Element|undefined} The Element containing the overlay. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Overlay.prototype.getElement = function() { - return /** @type {Element|undefined} */ ( - this.get(ol.OverlayProperty.ELEMENT)); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'getElement', - ol.Overlay.prototype.getElement); +goog.webgl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7; /** - * Get the map associated with this overlay. - * @return {ol.Map|undefined} The map that the overlay is part of. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Overlay.prototype.getMap = function() { - return /** @type {ol.Map|undefined} */ ( - this.get(ol.OverlayProperty.MAP)); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'getMap', - ol.Overlay.prototype.getMap); +goog.webgl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9; /** - * Get the offset of this overlay. - * @return {Array.<number>} The offset. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Overlay.prototype.getOffset = function() { - return /** @type {Array.<number>} */ ( - this.get(ol.OverlayProperty.OFFSET)); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'getOffset', - ol.Overlay.prototype.getOffset); +goog.webgl.FRAMEBUFFER_UNSUPPORTED = 0x8CDD; /** - * Get the current position of this overlay. - * @return {ol.Coordinate|undefined} The spatial point that the overlay is - * anchored at. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Overlay.prototype.getPosition = function() { - return /** @type {ol.Coordinate|undefined} */ ( - this.get(ol.OverlayProperty.POSITION)); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'getPosition', - ol.Overlay.prototype.getPosition); +goog.webgl.FRAMEBUFFER_BINDING = 0x8CA6; /** - * Get the current positioning of this overlay. - * @return {ol.OverlayPositioning} How the overlay is positioned - * relative to its point on the map. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Overlay.prototype.getPositioning = function() { - return /** @type {ol.OverlayPositioning} */ ( - this.get(ol.OverlayProperty.POSITIONING)); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'getPositioning', - ol.Overlay.prototype.getPositioning); +goog.webgl.RENDERBUFFER_BINDING = 0x8CA7; /** - * @protected + * @const + * @type {number} */ -ol.Overlay.prototype.handleElementChanged = function() { - goog.dom.removeChildren(this.element_); - var element = this.getElement(); - if (goog.isDefAndNotNull(element)) { - goog.dom.append(/** @type {!Node} */ (this.element_), element); - } -}; +goog.webgl.MAX_RENDERBUFFER_SIZE = 0x84E8; /** - * @protected + * @const + * @type {number} */ -ol.Overlay.prototype.handleMapChanged = function() { - if (!goog.isNull(this.mapPostrenderListenerKey_)) { - goog.dom.removeNode(this.element_); - goog.events.unlistenByKey(this.mapPostrenderListenerKey_); - this.mapPostrenderListenerKey_ = null; - } - var map = this.getMap(); - if (goog.isDefAndNotNull(map)) { - this.mapPostrenderListenerKey_ = goog.events.listen(map, - ol.MapEventType.POSTRENDER, this.render, false, this); - this.updatePixelPosition_(); - var container = this.stopEvent_ ? - map.getOverlayContainerStopEvent() : map.getOverlayContainer(); - if (this.insertFirst_) { - goog.dom.insertChildAt(/** @type {!Element} */ ( - container), this.element_, 0); - } else { - goog.dom.append(/** @type {!Node} */ (container), this.element_); - } - } -}; +goog.webgl.INVALID_FRAMEBUFFER_OPERATION = 0x0506; /** - * @protected + * @const + * @type {number} */ -ol.Overlay.prototype.render = function() { - this.updatePixelPosition_(); -}; +goog.webgl.UNPACK_FLIP_Y_WEBGL = 0x9240; /** - * @protected + * @const + * @type {number} */ -ol.Overlay.prototype.handleOffsetChanged = function() { - this.updatePixelPosition_(); -}; +goog.webgl.UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; /** - * @protected + * @const + * @type {number} */ -ol.Overlay.prototype.handlePositionChanged = function() { - this.updatePixelPosition_(); -}; +goog.webgl.CONTEXT_LOST_WEBGL = 0x9242; /** - * @protected + * @const + * @type {number} */ -ol.Overlay.prototype.handlePositioningChanged = function() { - this.updatePixelPosition_(); -}; +goog.webgl.UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243; /** - * Set the DOM element to be associated with this overlay. - * @param {Element|undefined} element The Element containing the overlay. - * @observable - * @api stable + * @const + * @type {number} */ -ol.Overlay.prototype.setElement = function(element) { - this.set(ol.OverlayProperty.ELEMENT, element); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'setElement', - ol.Overlay.prototype.setElement); +goog.webgl.BROWSER_DEFAULT_WEBGL = 0x9244; /** - * Set the map to be associated with this overlay. - * @param {ol.Map|undefined} map The map that the overlay is part of. - * @observable - * @api stable + * From the OES_texture_half_float extension. + * http://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/ + * @const + * @type {number} */ -ol.Overlay.prototype.setMap = function(map) { - this.set(ol.OverlayProperty.MAP, map); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'setMap', - ol.Overlay.prototype.setMap); +goog.webgl.HALF_FLOAT_OES = 0x8D61; /** - * Set the offset for this overlay. - * @param {Array.<number>} offset Offset. - * @observable - * @api stable + * From the OES_standard_derivatives extension. + * http://www.khronos.org/registry/webgl/extensions/OES_standard_derivatives/ + * @const + * @type {number} */ -ol.Overlay.prototype.setOffset = function(offset) { - this.set(ol.OverlayProperty.OFFSET, offset); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'setOffset', - ol.Overlay.prototype.setOffset); +goog.webgl.FRAGMENT_SHADER_DERIVATIVE_HINT_OES = 0x8B8B; /** - * Set the position for this overlay. - * @param {ol.Coordinate|undefined} position The spatial point that the overlay - * is anchored at. - * @observable - * @api stable + * From the OES_vertex_array_object extension. + * http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/ + * @const + * @type {number} */ -ol.Overlay.prototype.setPosition = function(position) { - this.set(ol.OverlayProperty.POSITION, position); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'setPosition', - ol.Overlay.prototype.setPosition); +goog.webgl.VERTEX_ARRAY_BINDING_OES = 0x85B5; /** - * Set the positioning for this overlay. - * @param {ol.OverlayPositioning} positioning how the overlay is - * positioned relative to its point on the map. - * @observable - * @api stable + * From the WEBGL_debug_renderer_info extension. + * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/ + * @const + * @type {number} */ -ol.Overlay.prototype.setPositioning = function(positioning) { - this.set(ol.OverlayProperty.POSITIONING, positioning); -}; -goog.exportProperty( - ol.Overlay.prototype, - 'setPositioning', - ol.Overlay.prototype.setPositioning); +goog.webgl.UNMASKED_VENDOR_WEBGL = 0x9245; /** - * @private + * From the WEBGL_debug_renderer_info extension. + * http://www.khronos.org/registry/webgl/extensions/WEBGL_debug_renderer_info/ + * @const + * @type {number} */ -ol.Overlay.prototype.updatePixelPosition_ = function() { +goog.webgl.UNMASKED_RENDERER_WEBGL = 0x9246; - var map = this.getMap(); - var position = this.getPosition(); - if (!goog.isDef(map) || !map.isRendered() || !goog.isDef(position)) { - if (this.rendered_.visible) { - goog.style.setElementShown(this.element_, false); - this.rendered_.visible = false; - } - return; - } - var pixel = map.getPixelFromCoordinate(position); - goog.asserts.assert(!goog.isNull(pixel)); - var mapSize = map.getSize(); - goog.asserts.assert(goog.isDef(mapSize)); - var style = this.element_.style; - var offset = this.getOffset(); - goog.asserts.assert(goog.isArray(offset)); - var positioning = this.getPositioning(); - goog.asserts.assert(goog.isDef(positioning)); +/** + * From the WEBGL_compressed_texture_s3tc extension. + * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ + * @const + * @type {number} + */ +goog.webgl.COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; - var offsetX = offset[0]; - var offsetY = offset[1]; - if (positioning == ol.OverlayPositioning.BOTTOM_RIGHT || - positioning == ol.OverlayPositioning.CENTER_RIGHT || - positioning == ol.OverlayPositioning.TOP_RIGHT) { - if (this.rendered_.left_ !== '') { - this.rendered_.left_ = style.left = ''; - } - var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px'; - if (this.rendered_.right_ != right) { - this.rendered_.right_ = style.right = right; - } - } else { - if (this.rendered_.right_ !== '') { - this.rendered_.right_ = style.right = ''; - } - if (positioning == ol.OverlayPositioning.BOTTOM_CENTER || - positioning == ol.OverlayPositioning.CENTER_CENTER || - positioning == ol.OverlayPositioning.TOP_CENTER) { - offsetX -= goog.style.getSize(this.element_).width / 2; - } - var left = Math.round(pixel[0] + offsetX) + 'px'; - if (this.rendered_.left_ != left) { - this.rendered_.left_ = style.left = left; - } - } - if (positioning == ol.OverlayPositioning.BOTTOM_LEFT || - positioning == ol.OverlayPositioning.BOTTOM_CENTER || - positioning == ol.OverlayPositioning.BOTTOM_RIGHT) { - if (this.rendered_.top_ !== '') { - this.rendered_.top_ = style.top = ''; - } - var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px'; - if (this.rendered_.bottom_ != bottom) { - this.rendered_.bottom_ = style.bottom = bottom; - } - } else { - if (this.rendered_.bottom_ !== '') { - this.rendered_.bottom_ = style.bottom = ''; - } - if (positioning == ol.OverlayPositioning.CENTER_LEFT || - positioning == ol.OverlayPositioning.CENTER_CENTER || - positioning == ol.OverlayPositioning.CENTER_RIGHT) { - offsetY -= goog.style.getSize(this.element_).height / 2; - } - var top = Math.round(pixel[1] + offsetY) + 'px'; - if (this.rendered_.top_ != top) { - this.rendered_.top_ = style.top = top; - } - } - if (!this.rendered_.visible) { - goog.style.setElementShown(this.element_, true); - this.rendered_.visible = true; - } +/** + * From the WEBGL_compressed_texture_s3tc extension. + * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ + * @const + * @type {number} + */ +goog.webgl.COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; -}; -goog.provide('ol.control.OverviewMap'); +/** + * From the WEBGL_compressed_texture_s3tc extension. + * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ + * @const + * @type {number} + */ +goog.webgl.COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.classlist'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.math.Size'); -goog.require('goog.style'); -goog.require('ol.Collection'); -goog.require('ol.Map'); -goog.require('ol.MapEventType'); -goog.require('ol.Object'); -goog.require('ol.Overlay'); -goog.require('ol.OverlayPositioning'); -goog.require('ol.View'); -goog.require('ol.control.Control'); -goog.require('ol.coordinate'); -goog.require('ol.css'); -goog.require('ol.extent'); +/** + * From the WEBGL_compressed_texture_s3tc extension. + * http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ + * @const + * @type {number} + */ +goog.webgl.COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; /** - * Create a new control with a map acting as an overview map for an other - * defined map. - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options. - * @api + * From the EXT_texture_filter_anisotropic extension. + * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ + * @const + * @type {number} */ -ol.control.OverviewMap = function(opt_options) { +goog.webgl.TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE; - var options = goog.isDef(opt_options) ? opt_options : {}; - /** - * @type {boolean} - * @private - */ - this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true; +/** + * From the EXT_texture_filter_anisotropic extension. + * http://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotropic/ + * @const + * @type {number} + */ +goog.webgl.MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF; - /** - * @private - * @type {boolean} - */ - this.collapsible_ = goog.isDef(options.collapsible) ? - options.collapsible : true; +goog.provide('ol.webgl.shader'); - if (!this.collapsible_) { - this.collapsed_ = false; - } +goog.require('goog.functions'); +goog.require('goog.webgl'); +goog.require('ol.webgl'); - var className = goog.isDef(options.className) ? - options.className : 'ol-overviewmap'; - var tipLabel = goog.isDef(options.tipLabel) ? - options.tipLabel : 'Overview map'; - /** - * @private - * @type {string} - */ - this.collapseLabel_ = goog.isDef(options.collapseLabel) ? - options.collapseLabel : '\u00AB'; +/** + * @constructor + * @param {string} source Source. + * @struct + */ +ol.webgl.Shader = function(source) { /** * @private * @type {string} */ - this.label_ = goog.isDef(options.label) ? options.label : '\u00BB'; - var label = goog.dom.createDom(goog.dom.TagName.SPAN, {}, - (this.collapsible_ && !this.collapsed_) ? - this.collapseLabel_ : this.label_); + this.source_ = source; - /** - * @private - * @type {Element} - */ - this.labelSpan_ = label; - var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'type': 'button', - 'title': tipLabel - }, this.labelSpan_); +}; - goog.events.listen(button, goog.events.EventType.CLICK, - this.handleClick_, false, this); - goog.events.listen(button, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); +/** + * @return {number} Type. + */ +ol.webgl.Shader.prototype.getType = goog.abstractMethod; - var ovmapDiv = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-map'); - /** - * @type {ol.Map} - * @private - */ - this.ovmap_ = new ol.Map({ - controls: new ol.Collection(), - interactions: new ol.Collection(), - target: ovmapDiv - }); - var ovmap = this.ovmap_; +/** + * @return {string} Source. + */ +ol.webgl.Shader.prototype.getSource = function() { + return this.source_; +}; - if (goog.isDef(options.layers)) { - options.layers.forEach( - /** - * @param {ol.layer.Layer} layer Layer. - */ - function(layer) { - ovmap.addLayer(layer); - }, this); - } - var box = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-box'); +/** + * @return {boolean} Is animated? + */ +ol.webgl.Shader.prototype.isAnimated = goog.functions.FALSE; - /** - * @type {ol.Overlay} - * @private - */ - this.boxOverlay_ = new ol.Overlay({ - position: [0, 0], - positioning: ol.OverlayPositioning.BOTTOM_LEFT, - element: box - }); - this.ovmap_.addOverlay(this.boxOverlay_); - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL + - (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + - (this.collapsible_ ? '' : ' ol-uncollapsible'); - var element = goog.dom.createDom(goog.dom.TagName.DIV, - cssClasses, ovmapDiv, button); - var render = goog.isDef(options.render) ? - options.render : ol.control.OverviewMap.render; +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.shader.Fragment = function(source) { + goog.base(this, source); +}; +goog.inherits(ol.webgl.shader.Fragment, ol.webgl.Shader); - goog.base(this, { - element: element, - render: render, - target: options.target - }); + +/** + * @inheritDoc + */ +ol.webgl.shader.Fragment.prototype.getType = function() { + return goog.webgl.FRAGMENT_SHADER; +}; + + + +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.shader.Vertex = function(source) { + goog.base(this, source); }; -goog.inherits(ol.control.OverviewMap, ol.control.Control); +goog.inherits(ol.webgl.shader.Vertex, ol.webgl.Shader); /** * @inheritDoc - * @api */ -ol.control.OverviewMap.prototype.setMap = function(map) { - var currentMap = this.getMap(); +ol.webgl.shader.Vertex.prototype.getType = function() { + return goog.webgl.VERTEX_SHADER; +}; - if (goog.isNull(map) && !goog.isNull(currentMap)) { - goog.events.unlisten( - currentMap, ol.Object.getChangeEventType(ol.MapProperty.VIEW), - this.handleViewChanged_, false, this); - } +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.imagereplay.shader.Color'); - goog.base(this, 'setMap', map); +goog.require('ol.webgl.shader'); - if (!goog.isNull(map)) { - // if no layers were set for the overviewmap map, then bind with - // those in the main map - if (this.ovmap_.getLayers().getLength() === 0) { - this.ovmap_.bindTo(ol.MapProperty.LAYERGROUP, map); - } - // bind current map view, or any new one - this.bindView_(); +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct + */ +ol.render.webgl.imagereplay.shader.ColorFragment = function() { + goog.base(this, ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE); +}; +goog.inherits(ol.render.webgl.imagereplay.shader.ColorFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorFragment); - goog.events.listen( - map, ol.Object.getChangeEventType(ol.MapProperty.VIEW), - this.handleViewChanged_, false, this); - this.ovmap_.updateSize(); - this.resetExtent_(); - } -}; +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n}\n'; /** - * Bind some actions to the main map view. - * @private + * @const + * @type {string} */ -ol.control.OverviewMap.prototype.bindView_ = function() { - var map = this.getMap(); - var view = map.getView(); +ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}'; - // if the map does not have a view, we can't act upon it - if (goog.isNull(view)) { - return; - } - // FIXME - the overviewmap view rotation currently follows the one used - // by the main map view. We could support box rotation instead. The choice - // between the 2 modes would be made in a single option - this.ovmap_.getView().bindTo(ol.ViewProperty.ROTATION, view); -}; +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.shader.ColorFragment.SOURCE = goog.DEBUG ? + ol.render.webgl.imagereplay.shader.ColorFragment.DEBUG_SOURCE : + ol.render.webgl.imagereplay.shader.ColorFragment.OPTIMIZED_SOURCE; + /** - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.OverviewMap} - * @api + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct */ -ol.control.OverviewMap.render = function(mapEvent) { - this.validateExtent_(); - this.updateBox_(); +ol.render.webgl.imagereplay.shader.ColorVertex = function() { + goog.base(this, ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE); }; +goog.inherits(ol.render.webgl.imagereplay.shader.ColorVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.ColorVertex); /** - * Called on main map view changed. - * @param {goog.events.Event} event Event. - * @private + * @const + * @type {string} */ -ol.control.OverviewMap.prototype.handleViewChanged_ = function(event) { - this.bindView_(); -}; +ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; /** - * Reset the overview map extent if the box size (width or - * height) is less than the size of the overview map size times minRatio - * or is greater than the size of the overview size times maxRatio. - * - * If the map extent was not reset, the box size can fits in the defined - * ratio sizes. This method then checks if is contained inside the overview - * map current extent. If not, recenter the overview map to the current - * main map center location. - * @private + * @const + * @type {string} */ -ol.control.OverviewMap.prototype.validateExtent_ = function() { - var map = this.getMap(); - var ovmap = this.ovmap_; +ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}'; - if (!map.isRendered() || !ovmap.isRendered()) { - return; - } - var mapSize = map.getSize(); - goog.asserts.assertArray(mapSize); +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.shader.ColorVertex.SOURCE = goog.DEBUG ? + ol.render.webgl.imagereplay.shader.ColorVertex.DEBUG_SOURCE : + ol.render.webgl.imagereplay.shader.ColorVertex.OPTIMIZED_SOURCE; - var view = map.getView(); - goog.asserts.assert(goog.isDef(view)); - var extent = view.calculateExtent(mapSize); - var ovmapSize = ovmap.getSize(); - goog.asserts.assertArray(ovmapSize); - var ovview = ovmap.getView(); - goog.asserts.assert(goog.isDef(ovview)); - var ovextent = ovview.calculateExtent(ovmapSize); +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.imagereplay.shader.Color.Locations = function(gl, program) { - var topLeftPixel = - ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent)); - var bottomRightPixel = - ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent)); - var boxSize = new goog.math.Size( - Math.abs(topLeftPixel[0] - bottomRightPixel[0]), - Math.abs(topLeftPixel[1] - bottomRightPixel[1])); + /** + * @type {WebGLUniformLocation} + */ + this.u_colorMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_colorMatrix' : 'k'); - var ovmapWidth = ovmapSize[0]; - var ovmapHeight = ovmapSize[1]; + /** + * @type {WebGLUniformLocation} + */ + this.u_image = gl.getUniformLocation( + program, goog.DEBUG ? 'u_image' : 'm'); - if (boxSize.width < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO || - boxSize.height < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO || - boxSize.width > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO || - boxSize.height > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) { - this.resetExtent_(); - } else if (!ol.extent.containsExtent(ovextent, extent)) { - this.recenter_(); - } -}; + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i'); -/** - * Reset the overview map extent to half calculated min and max ratio times - * the extent of the main map. - * @private - */ -ol.control.OverviewMap.prototype.resetExtent_ = function() { - if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) { - return; - } + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, goog.DEBUG ? 'u_opacity' : 'l'); - var map = this.getMap(); - var ovmap = this.ovmap_; + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_projectionMatrix' : 'h'); - var mapSize = map.getSize(); - goog.asserts.assertArray(mapSize); + /** + * @type {number} + */ + this.a_offsets = gl.getAttribLocation( + program, goog.DEBUG ? 'a_offsets' : 'e'); - var view = map.getView(); - goog.asserts.assert(goog.isDef(view)); - var extent = view.calculateExtent(mapSize); + /** + * @type {number} + */ + this.a_opacity = gl.getAttribLocation( + program, goog.DEBUG ? 'a_opacity' : 'f'); - var ovmapSize = ovmap.getSize(); - goog.asserts.assertArray(ovmapSize); + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'c'); - var ovview = ovmap.getView(); - goog.asserts.assert(goog.isDef(ovview)); + /** + * @type {number} + */ + this.a_rotateWithView = gl.getAttribLocation( + program, goog.DEBUG ? 'a_rotateWithView' : 'g'); - // get how many times the current map overview could hold different - // box sizes using the min and max ratio, pick the step in the middle used - // to calculate the extent from the main map to set it to the overview map, - var steps = Math.log( - ol.OVERVIEWMAP_MAX_RATIO / ol.OVERVIEWMAP_MIN_RATIO) / Math.LN2; - var ratio = 1 / (Math.pow(2, steps / 2) * ol.OVERVIEWMAP_MIN_RATIO); - ol.extent.scaleFromCenter(extent, ratio); - ovview.fitExtent(extent, ovmapSize); + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, goog.DEBUG ? 'a_texCoord' : 'd'); }; +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.imagereplay.shader.Default'); -/** - * Set the center of the overview map to the map center without changing its - * resolution. - * @private - */ -ol.control.OverviewMap.prototype.recenter_ = function() { - var map = this.getMap(); - var ovmap = this.ovmap_; +goog.require('ol.webgl.shader'); - var view = map.getView(); - goog.asserts.assert(goog.isDef(view)); - var ovview = ovmap.getView(); - goog.asserts.assert(goog.isDef(ovview)); - ovview.setCenter(view.getCenter()); +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct + */ +ol.render.webgl.imagereplay.shader.DefaultFragment = function() { + goog.base(this, ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE); }; +goog.inherits(ol.render.webgl.imagereplay.shader.DefaultFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultFragment); /** - * Update the box using the main map extent - * @private + * @const + * @type {string} */ -ol.control.OverviewMap.prototype.updateBox_ = function() { - var map = this.getMap(); - var ovmap = this.ovmap_; - - if (!map.isRendered() || !ovmap.isRendered()) { - return; - } - - var mapSize = map.getSize(); - goog.asserts.assertArray(mapSize); +ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - var view = map.getView(); - goog.asserts.assert(goog.isDef(view)); - var ovview = ovmap.getView(); - goog.asserts.assert(goog.isDef(ovview)); +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - var ovmapSize = ovmap.getSize(); - goog.asserts.assertArray(ovmapSize); - var rotation = view.getRotation(); - goog.asserts.assert(goog.isDef(rotation)); +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? + ol.render.webgl.imagereplay.shader.DefaultFragment.DEBUG_SOURCE : + ol.render.webgl.imagereplay.shader.DefaultFragment.OPTIMIZED_SOURCE; - var overlay = this.boxOverlay_; - var box = this.boxOverlay_.getElement(); - var extent = view.calculateExtent(mapSize); - var ovresolution = ovview.getResolution(); - var bottomLeft = ol.extent.getBottomLeft(extent); - var topRight = ol.extent.getTopRight(extent); - // set position using bottom left coordinates - var rotateBottomLeft = this.calculateCoordinateRotate_(rotation, bottomLeft); - overlay.setPosition(rotateBottomLeft); - // set box size calculated from map extent size and overview map resolution - if (goog.isDefAndNotNull(box)) { - var boxWidth = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution); - var boxHeight = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution); - goog.style.setBorderBoxSize(box, new goog.math.Size( - boxWidth, boxHeight)); - } +/** + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct + */ +ol.render.webgl.imagereplay.shader.DefaultVertex = function() { + goog.base(this, ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE); }; +goog.inherits(ol.render.webgl.imagereplay.shader.DefaultVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.render.webgl.imagereplay.shader.DefaultVertex); /** - * @param {number} rotation Target rotation. - * @param {ol.Coordinate} coordinate Coordinate. - * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor. - * @private + * @const + * @type {string} */ -ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function( - rotation, coordinate) { - var coordinateRotate; - - var map = this.getMap(); - var view = map.getView(); - goog.asserts.assert(goog.isDef(view)); +ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; - var currentCenter = view.getCenter(); - if (goog.isDef(currentCenter)) { - coordinateRotate = [ - coordinate[0] - currentCenter[0], - coordinate[1] - currentCenter[1] - ]; - ol.coordinate.rotate(coordinateRotate, rotation); - ol.coordinate.add(coordinateRotate, currentCenter); - } - return coordinateRotate; -}; +/** + * @const + * @type {string} + */ +ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}'; /** - * @param {goog.events.BrowserEvent} event The event to handle - * @private + * @const + * @type {string} */ -ol.control.OverviewMap.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.handleToggle_(); -}; +ol.render.webgl.imagereplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? + ol.render.webgl.imagereplay.shader.DefaultVertex.DEBUG_SOURCE : + ol.render.webgl.imagereplay.shader.DefaultVertex.OPTIMIZED_SOURCE; + /** - * @private + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct */ -ol.control.OverviewMap.prototype.handleToggle_ = function() { - goog.dom.classlist.toggle(this.element, 'ol-collapsed'); - goog.dom.setTextContent(this.labelSpan_, - (this.collapsed_) ? this.collapseLabel_ : this.label_); - this.collapsed_ = !this.collapsed_; +ol.render.webgl.imagereplay.shader.Default.Locations = function(gl, program) { - // manage overview map if it had not been rendered before and control - // is expanded - var ovmap = this.ovmap_; - if (!this.collapsed_ && !ovmap.isRendered()) { - ovmap.updateSize(); - this.resetExtent_(); - goog.events.listenOnce(ovmap, ol.MapEventType.POSTRENDER, - function(event) { - this.updateBox_(); - }, - false, this); - } + /** + * @type {WebGLUniformLocation} + */ + this.u_image = gl.getUniformLocation( + program, goog.DEBUG ? 'u_image' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, goog.DEBUG ? 'u_opacity' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_projectionMatrix' : 'h'); + + /** + * @type {number} + */ + this.a_offsets = gl.getAttribLocation( + program, goog.DEBUG ? 'a_offsets' : 'e'); + + /** + * @type {number} + */ + this.a_opacity = gl.getAttribLocation( + program, goog.DEBUG ? 'a_opacity' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'c'); + + /** + * @type {number} + */ + this.a_rotateWithView = gl.getAttribLocation( + program, goog.DEBUG ? 'a_rotateWithView' : 'g'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, goog.DEBUG ? 'a_texCoord' : 'd'); }; +goog.provide('ol.webgl.Buffer'); + +goog.require('goog.array'); +goog.require('goog.webgl'); +goog.require('ol'); + /** - * @return {boolean} True if the widget is collapsible. - * @api stable + * @enum {number} */ -ol.control.OverviewMap.prototype.getCollapsible = function() { - return this.collapsible_; +ol.webgl.BufferUsage = { + STATIC_DRAW: goog.webgl.STATIC_DRAW, + STREAM_DRAW: goog.webgl.STREAM_DRAW, + DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW }; + /** - * @param {boolean} collapsible True if the widget is collapsible. - * @api stable + * @constructor + * @param {Array.<number>=} opt_arr Array. + * @param {number=} opt_usage Usage. + * @struct */ -ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) { - if (this.collapsible_ === collapsible) { - return; - } - this.collapsible_ = collapsible; - goog.dom.classlist.toggle(this.element, 'ol-uncollapsible'); - if (!collapsible && this.collapsed_) { - this.handleToggle_(); - } +ol.webgl.Buffer = function(opt_arr, opt_usage) { + + /** + * @private + * @type {Array.<number>} + */ + this.arr_ = goog.isDef(opt_arr) ? opt_arr : []; + + /** + * @private + * @type {number} + */ + this.usage_ = goog.isDef(opt_usage) ? + opt_usage : ol.webgl.BufferUsage.STATIC_DRAW; + }; /** - * @param {boolean} collapsed True if the widget is collapsed. - * @api stable + * @return {Array.<number>} Array. */ -ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) { - if (!this.collapsible_ || this.collapsed_ === collapsed) { - return; - } - this.handleToggle_(); +ol.webgl.Buffer.prototype.getArray = function() { + return this.arr_; }; /** - * @return {boolean} True if the widget is collapsed. - * @api stable + * @return {number} Usage. */ -ol.control.OverviewMap.prototype.getCollapsed = function() { - return this.collapsed_; +ol.webgl.Buffer.prototype.getUsage = function() { + return this.usage_; }; -goog.provide('ol.control.ScaleLine'); -goog.provide('ol.control.ScaleLineProperty'); -goog.provide('ol.control.ScaleLineUnits'); +goog.provide('ol.webgl.Context'); +goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); goog.require('goog.events'); -goog.require('goog.math'); -goog.require('goog.style'); -goog.require('ol.Object'); -goog.require('ol.TransformFunction'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.proj'); -goog.require('ol.proj.Units'); -goog.require('ol.sphere.NORMAL'); - - -/** - * @enum {string} - */ -ol.control.ScaleLineProperty = { - UNITS: 'units' -}; +goog.require('goog.log'); +goog.require('goog.object'); +goog.require('ol'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.WebGLContextEventType'); /** - * Units for the scale line. Supported values are `'degrees'`, `'imperial'`, - * `'nautical'`, `'metric'`, `'us'`. - * @enum {string} - * @api stable + * @typedef {{buf: ol.webgl.Buffer, + * buffer: WebGLBuffer}} */ -ol.control.ScaleLineUnits = { - DEGREES: 'degrees', - IMPERIAL: 'imperial', - NAUTICAL: 'nautical', - METRIC: 'metric', - US: 'us' -}; +ol.webgl.BufferCacheEntry; /** * @classdesc - * A control displaying rough x-axis distances, calculated for the center of the - * viewport. - * No scale line will be shown when the x-axis distance cannot be calculated in - * the view projection (e.g. at or beyond the poles in EPSG:4326). - * By default the scale line will show in the bottom left portion of the map, - * but this can be changed by using the css selector `.ol-scale-line`. + * A WebGL context for accessing low-level WebGL capabilities. * * @constructor - * @extends {ol.control.Control} - * @param {olx.control.ScaleLineOptions=} opt_options Scale line options. - * @api stable + * @extends {goog.events.EventTarget} + * @param {HTMLCanvasElement} canvas Canvas. + * @param {WebGLRenderingContext} gl GL. + * @api */ -ol.control.ScaleLine = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; +ol.webgl.Context = function(canvas, gl) { - var className = goog.isDef(options.className) ? - options.className : 'ol-scale-line'; + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = canvas; /** * @private - * @type {Element} + * @type {WebGLRenderingContext} */ - this.innerElement_ = goog.dom.createDom(goog.dom.TagName.DIV, - className + '-inner'); + this.gl_ = gl; /** * @private - * @type {Element} + * @type {Object.<number, ol.webgl.BufferCacheEntry>} */ - this.element_ = goog.dom.createDom(goog.dom.TagName.DIV, - className + ' ' + ol.css.CLASS_UNSELECTABLE, this.innerElement_); + this.bufferCache_ = {}; /** * @private - * @type {?olx.ViewState} + * @type {Object.<number, WebGLShader>} */ - this.viewState_ = null; + this.shaderCache_ = {}; /** * @private - * @type {number} + * @type {Object.<string, WebGLProgram>} */ - this.minWidth_ = goog.isDef(options.minWidth) ? options.minWidth : 64; + this.programCache_ = {}; /** * @private - * @type {boolean} + * @type {WebGLProgram} */ - this.renderedVisible_ = false; + this.currentProgram_ = null; /** * @private - * @type {number|undefined} + * @type {WebGLFramebuffer} */ - this.renderedWidth_ = undefined; + this.hitDetectionFramebuffer_ = null; /** * @private - * @type {string} + * @type {WebGLTexture} */ - this.renderedHTML_ = ''; + this.hitDetectionTexture_ = null; /** * @private - * @type {?ol.TransformFunction} + * @type {WebGLRenderbuffer} */ - this.toEPSG4326_ = null; + this.hitDetectionRenderbuffer_ = null; - var render = goog.isDef(options.render) ? - options.render : ol.control.ScaleLine.render; + /** + * @type {boolean} + */ + this.hasOESElementIndexUint = goog.array.contains( + ol.WEBGL_EXTENSIONS, 'OES_element_index_uint'); - goog.base(this, { - element: this.element_, - render: render, - target: options.target - }); + // use the OES_element_index_uint extension if available + if (this.hasOESElementIndexUint) { + var ext = gl.getExtension('OES_element_index_uint'); + goog.asserts.assert(!goog.isNull(ext), + 'Failed to get extension "OES_element_index_uint"'); + } - goog.events.listen( - this, ol.Object.getChangeEventType(ol.control.ScaleLineProperty.UNITS), - this.handleUnitsChanged_, false, this); + goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST, + this.handleWebGLContextLost, false, this); + goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED, + this.handleWebGLContextRestored, false, this); + +}; - this.setUnits(/** @type {ol.control.ScaleLineUnits} */ (options.units) || - ol.control.ScaleLineUnits.METRIC); +/** + * Just bind the buffer if it's in the cache. Otherwise create + * the WebGL buffer, bind it, populate it, and add an entry to + * the cache. + * @param {number} target Target. + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.bindBuffer = function(target, buf) { + var gl = this.getGL(); + var arr = buf.getArray(); + var bufferKey = goog.getUid(buf); + if (bufferKey in this.bufferCache_) { + var bufferCacheEntry = this.bufferCache_[bufferKey]; + gl.bindBuffer(target, bufferCacheEntry.buffer); + } else { + var buffer = gl.createBuffer(); + gl.bindBuffer(target, buffer); + goog.asserts.assert(target == goog.webgl.ARRAY_BUFFER || + target == goog.webgl.ELEMENT_ARRAY_BUFFER, + 'target is supposed to be an ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER'); + var /** @type {ArrayBufferView} */ arrayBuffer; + if (target == goog.webgl.ARRAY_BUFFER) { + arrayBuffer = new Float32Array(arr); + } else if (target == goog.webgl.ELEMENT_ARRAY_BUFFER) { + arrayBuffer = this.hasOESElementIndexUint ? + new Uint32Array(arr) : new Uint16Array(arr); + } else { + goog.asserts.fail(); + } + gl.bufferData(target, arrayBuffer, buf.getUsage()); + this.bufferCache_[bufferKey] = { + buf: buf, + buffer: buffer + }; + } }; -goog.inherits(ol.control.ScaleLine, ol.control.Control); /** - * @const - * @type {Array.<number>} + * @param {ol.webgl.Buffer} buf Buffer. */ -ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5]; +ol.webgl.Context.prototype.deleteBuffer = function(buf) { + var gl = this.getGL(); + var bufferKey = goog.getUid(buf); + goog.asserts.assert(bufferKey in this.bufferCache_, + 'attempted to delete uncached buffer'); + var bufferCacheEntry = this.bufferCache_[bufferKey]; + if (!gl.isContextLost()) { + gl.deleteBuffer(bufferCacheEntry.buffer); + } + delete this.bufferCache_[bufferKey]; +}; /** - * @return {ol.control.ScaleLineUnits|undefined} The units to use in the scale - * line. - * @observable - * @api stable + * @inheritDoc */ -ol.control.ScaleLine.prototype.getUnits = function() { - return /** @type {ol.control.ScaleLineUnits|undefined} */ ( - this.get(ol.control.ScaleLineProperty.UNITS)); +ol.webgl.Context.prototype.disposeInternal = function() { + var gl = this.getGL(); + if (!gl.isContextLost()) { + goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) { + gl.deleteBuffer(bufferCacheEntry.buffer); + }); + goog.object.forEach(this.programCache_, function(program) { + gl.deleteProgram(program); + }); + goog.object.forEach(this.shaderCache_, function(shader) { + gl.deleteShader(shader); + }); + // delete objects for hit-detection + gl.deleteFramebuffer(this.hitDetectionFramebuffer_); + gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_); + gl.deleteTexture(this.hitDetectionTexture_); + } }; -goog.exportProperty( - ol.control.ScaleLine.prototype, - 'getUnits', - ol.control.ScaleLine.prototype.getUnits); /** - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.ScaleLine} + * @return {HTMLCanvasElement} Canvas. + */ +ol.webgl.Context.prototype.getCanvas = function() { + return this.canvas_; +}; + + +/** + * Get the WebGL rendering context + * @return {WebGLRenderingContext} The rendering context. * @api */ -ol.control.ScaleLine.render = function(mapEvent) { - var frameState = mapEvent.frameState; - if (goog.isNull(frameState)) { - this.viewState_ = null; +ol.webgl.Context.prototype.getGL = function() { + return this.gl_; +}; + + +/** + * Get the frame buffer for hit detection. + * @return {WebGLFramebuffer} The hit detection frame buffer. + * @api + */ +ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() { + if (goog.isNull(this.hitDetectionFramebuffer_)) { + this.initHitDetectionFramebuffer_(); + } + return this.hitDetectionFramebuffer_; +}; + + +/** + * Get shader from the cache if it's in the cache. Otherwise, create + * the WebGL shader, compile it, and add entry to cache. + * @param {ol.webgl.Shader} shaderObject Shader object. + * @return {WebGLShader} Shader. + */ +ol.webgl.Context.prototype.getShader = function(shaderObject) { + var shaderKey = goog.getUid(shaderObject); + if (shaderKey in this.shaderCache_) { + return this.shaderCache_[shaderKey]; } else { - this.viewState_ = frameState.viewState; + var gl = this.getGL(); + var shader = gl.createShader(shaderObject.getType()); + gl.shaderSource(shader, shaderObject.getSource()); + gl.compileShader(shader); + if (goog.DEBUG) { + if (!gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) && + !gl.isContextLost()) { + goog.log.error(this.logger_, gl.getShaderInfoLog(shader)); + } + } + goog.asserts.assert( + gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) || + gl.isContextLost(), + 'illegal state, shader not compiled or context lost'); + this.shaderCache_[shaderKey] = shader; + return shader; } - this.updateElement_(); }; /** - * @private + * Get the program from the cache if it's in the cache. Otherwise create + * the WebGL program, attach the shaders to it, and add an entry to the + * cache. + * @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader. + * @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader. + * @return {WebGLProgram} Program. */ -ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() { - this.updateElement_(); +ol.webgl.Context.prototype.getProgram = function( + fragmentShaderObject, vertexShaderObject) { + var programKey = + goog.getUid(fragmentShaderObject) + '/' + goog.getUid(vertexShaderObject); + if (programKey in this.programCache_) { + return this.programCache_[programKey]; + } else { + var gl = this.getGL(); + var program = gl.createProgram(); + gl.attachShader(program, this.getShader(fragmentShaderObject)); + gl.attachShader(program, this.getShader(vertexShaderObject)); + gl.linkProgram(program); + if (goog.DEBUG) { + if (!gl.getProgramParameter(program, goog.webgl.LINK_STATUS) && + !gl.isContextLost()) { + goog.log.error(this.logger_, gl.getProgramInfoLog(program)); + } + } + goog.asserts.assert( + gl.getProgramParameter(program, goog.webgl.LINK_STATUS) || + gl.isContextLost(), + 'illegal state, shader not linked or context lost'); + this.programCache_[programKey] = program; + return program; + } }; /** - * @param {ol.control.ScaleLineUnits} units The units to use in the scale line. - * @observable - * @api stable + * FIXME empy description for jsdoc */ -ol.control.ScaleLine.prototype.setUnits = function(units) { - this.set(ol.control.ScaleLineProperty.UNITS, units); +ol.webgl.Context.prototype.handleWebGLContextLost = function() { + goog.object.clear(this.bufferCache_); + goog.object.clear(this.shaderCache_); + goog.object.clear(this.programCache_); + this.currentProgram_ = null; + this.hitDetectionFramebuffer_ = null; + this.hitDetectionTexture_ = null; + this.hitDetectionRenderbuffer_ = null; }; -goog.exportProperty( - ol.control.ScaleLine.prototype, - 'setUnits', - ol.control.ScaleLine.prototype.setUnits); /** - * @private + * FIXME empy description for jsdoc */ -ol.control.ScaleLine.prototype.updateElement_ = function() { - var viewState = this.viewState_; +ol.webgl.Context.prototype.handleWebGLContextRestored = function() { +}; - if (goog.isNull(viewState)) { - if (this.renderedVisible_) { - goog.style.setElementShown(this.element_, false); - this.renderedVisible_ = false; - } - return; - } - var center = viewState.center; - var projection = viewState.projection; - var pointResolution = - projection.getPointResolution(viewState.resolution, center); - var projectionUnits = projection.getUnits(); +/** + * Creates a 1x1 pixel framebuffer for the hit-detection. + * @private + */ +ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { + var gl = this.gl_; + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - var cosLatitude; - var units = this.getUnits(); - if (projectionUnits == ol.proj.Units.DEGREES && - (units == ol.control.ScaleLineUnits.METRIC || - units == ol.control.ScaleLineUnits.IMPERIAL || - units == ol.control.ScaleLineUnits.US || - units == ol.control.ScaleLineUnits.NAUTICAL)) { + var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1); + var renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, renderbuffer); - // Convert pointResolution from degrees to meters - this.toEPSG4326_ = null; - cosLatitude = Math.cos(goog.math.toRadians(center[1])); - pointResolution *= Math.PI * cosLatitude * ol.sphere.NORMAL.radius / 180; - projectionUnits = ol.proj.Units.METERS; + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } else if ((projectionUnits == ol.proj.Units.FEET || - projectionUnits == ol.proj.Units.METERS) && - units == ol.control.ScaleLineUnits.DEGREES) { + this.hitDetectionFramebuffer_ = framebuffer; + this.hitDetectionTexture_ = texture; + this.hitDetectionRenderbuffer_ = renderbuffer; +}; - // Convert pointResolution from meters or feet to degrees - if (goog.isNull(this.toEPSG4326_)) { - this.toEPSG4326_ = ol.proj.getTransformFromProjections( - projection, ol.proj.get('EPSG:4326')); - } - cosLatitude = Math.cos(goog.math.toRadians(this.toEPSG4326_(center)[1])); - var radius = ol.sphere.NORMAL.radius; - if (projectionUnits == ol.proj.Units.FEET) { - radius /= 0.3048; - } - pointResolution *= 180 / (Math.PI * cosLatitude * radius); - projectionUnits = ol.proj.Units.DEGREES; +/** + * Use a program. If the program is already in use, this will return `false`. + * @param {WebGLProgram} program Program. + * @return {boolean} Changed. + * @api + */ +ol.webgl.Context.prototype.useProgram = function(program) { + if (program == this.currentProgram_) { + return false; } else { - this.toEPSG4326_ = null; + var gl = this.getGL(); + gl.useProgram(program); + this.currentProgram_ = program; + return true; } +}; - goog.asserts.assert( - ((units == ol.control.ScaleLineUnits.METRIC || - units == ol.control.ScaleLineUnits.IMPERIAL || - units == ol.control.ScaleLineUnits.US || - units == ol.control.ScaleLineUnits.NAUTICAL) && - projectionUnits == ol.proj.Units.METERS) || - (units == ol.control.ScaleLineUnits.DEGREES && - projectionUnits == ol.proj.Units.DEGREES)); - var nominalCount = this.minWidth_ * pointResolution; - var suffix = ''; - if (units == ol.control.ScaleLineUnits.DEGREES) { - if (nominalCount < 1 / 60) { - suffix = '\u2033'; // seconds - pointResolution *= 3600; - } else if (nominalCount < 1) { - suffix = '\u2032'; // minutes - pointResolution *= 60; - } else { - suffix = '\u00b0'; // degrees - } - } else if (units == ol.control.ScaleLineUnits.IMPERIAL) { - if (nominalCount < 0.9144) { - suffix = 'in'; - pointResolution /= 0.0254; - } else if (nominalCount < 1609.344) { - suffix = 'ft'; - pointResolution /= 0.3048; - } else { - suffix = 'mi'; - pointResolution /= 1609.344; - } - } else if (units == ol.control.ScaleLineUnits.NAUTICAL) { - pointResolution /= 1852; - suffix = 'nm'; - } else if (units == ol.control.ScaleLineUnits.METRIC) { - if (nominalCount < 1) { - suffix = 'mm'; - pointResolution *= 1000; - } else if (nominalCount < 1000) { - suffix = 'm'; - } else { - suffix = 'km'; - pointResolution /= 1000; - } - } else if (units == ol.control.ScaleLineUnits.US) { - if (nominalCount < 0.9144) { - suffix = 'in'; - pointResolution *= 39.37; - } else if (nominalCount < 1609.344) { - suffix = 'ft'; - pointResolution /= 0.30480061; - } else { - suffix = 'mi'; - pointResolution /= 1609.3472; - } - } else { - goog.asserts.fail(); - } +/** + * @private + * @type {goog.log.Logger} + */ +ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context'); - var i = 3 * Math.floor( - Math.log(this.minWidth_ * pointResolution) / Math.log(10)); - var count, width; - while (true) { - count = ol.control.ScaleLine.LEADING_DIGITS[i % 3] * - Math.pow(10, Math.floor(i / 3)); - width = Math.round(count / pointResolution); - if (isNaN(width)) { - goog.style.setElementShown(this.element_, false); - this.renderedVisible_ = false; - return; - } else if (width >= this.minWidth_) { - break; - } - ++i; - } - var html = count + suffix; - if (this.renderedHTML_ != html) { - this.innerElement_.innerHTML = html; - this.renderedHTML_ = html; - } +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} + * @private + */ +ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - if (this.renderedWidth_ != width) { - this.innerElement_.style.width = width + 'px'; - this.renderedWidth_ = width; + if (goog.isDef(opt_wrapS)) { + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, opt_wrapS); } - - if (!this.renderedVisible_) { - goog.style.setElementShown(this.element_, true); - this.renderedVisible_ = true; + if (goog.isDef(opt_wrapT)) { + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, opt_wrapT); } -}; + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number} width Width. + * @param {number} height Height. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} + */ +ol.webgl.Context.createEmptyTexture = function( + gl, width, height, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} + */ +ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + + return texture; +}; + +goog.provide('ol.render.webgl.ImageReplay'); +goog.provide('ol.render.webgl.ReplayGroup'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.object'); +goog.require('goog.vec.Mat4'); +goog.require('ol.color.Matrix'); +goog.require('ol.extent'); +goog.require('ol.render.IReplayGroup'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.webgl.imagereplay.shader.Color'); +goog.require('ol.render.webgl.imagereplay.shader.Default'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); + + + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @protected + * @struct + */ +ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { + goog.base(this); + + /** + * @type {number|undefined} + * @private + */ + this.anchorX_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.anchorY_ = undefined; + + /** + * @private + * @type {ol.color.Matrix} + */ + this.colorMatrix_ = new ol.color.Matrix(); + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. To eliminate jitter caused by precision problems in the GPU + * we use the "Rendering Relative to Eye" technique described in the "3D + * Engine Design for Virtual Globes" book. + * @private + * @type {ol.Coordinate} + */ + this.origin_ = ol.extent.getCenter(maxExtent); + + /** + * @type {Array.<number>} + * @private + */ + this.groupIndices_ = []; + + /** + * @type {Array.<number>} + * @private + */ + this.hitDetectionGroupIndices_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.height_ = undefined; + + /** + * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} + * @private + */ + this.images_ = []; + + /** + * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} + * @private + */ + this.hitDetectionImages_ = []; + + /** + * @type {number|undefined} + * @private + */ + this.imageHeight_ = undefined; + + /** + * @type {number|undefined} + * @private + */ + this.imageWidth_ = undefined; -// Copyright 2005 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** + * @type {Array.<number>} + * @private + */ + this.indices_ = []; -/** - * @fileoverview Class to create objects which want to handle multiple events - * and have their listeners easily cleaned up via a dispose method. - * - * Example: - * <pre> - * function Something() { - * Something.base(this); - * - * ... set up object ... - * - * // Add event listeners - * this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar); - * this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand); - * this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse); - * this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover); - * this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover); - * } - * goog.inherits(Something, goog.events.EventHandler); - * - * Something.prototype.disposeInternal = function() { - * Something.base(this, 'disposeInternal'); - * goog.dom.removeNode(this.container); - * }; - * - * - * // Then elsewhere: - * - * var activeSomething = null; - * function openSomething() { - * activeSomething = new Something(); - * } - * - * function closeSomething() { - * if (activeSomething) { - * activeSomething.dispose(); // Remove event listeners - * activeSomething = null; - * } - * } - * </pre> - * - */ + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.indicesBuffer_ = null; -goog.provide('goog.events.EventHandler'); + /** + * @private + * @type {ol.render.webgl.imagereplay.shader.Color.Locations} + */ + this.colorLocations_ = null; -goog.require('goog.Disposable'); -goog.require('goog.events'); -goog.require('goog.object'); + /** + * @private + * @type {ol.render.webgl.imagereplay.shader.Default.Locations} + */ + this.defaultLocations_ = null; -goog.forwardDeclare('goog.events.EventWrapper'); + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.offsetRotateMatrix_ = goog.vec.Mat4.createNumberIdentity(); + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.offsetScaleMatrix_ = goog.vec.Mat4.createNumberIdentity(); -/** - * Super class for objects that want to easily manage a number of event - * listeners. It allows a short cut to listen and also provides a quick way - * to remove all events listeners belonging to this object. - * @param {SCOPE=} opt_scope Object in whose scope to call the listeners. - * @constructor - * @extends {goog.Disposable} - * @template SCOPE - */ -goog.events.EventHandler = function(opt_scope) { - goog.Disposable.call(this); - // TODO(mknichel): Rename this to this.scope_ and fix the classes in google3 - // that access this private variable. :( - this.handler_ = opt_scope; + /** + * @type {number|undefined} + * @private + */ + this.originX_ = undefined; /** - * Keys for events that are being listened to. - * @type {!Object<!goog.events.Key>} + * @type {number|undefined} * @private */ - this.keys_ = {}; -}; -goog.inherits(goog.events.EventHandler, goog.Disposable); + this.originY_ = undefined; + /** + * @type {!goog.vec.Mat4.Number} + * @private + */ + this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity(); -/** - * Utility array used to unify the cases of listening for an array of types - * and listening for a single event, without using recursion or allocating - * an array each time. - * @type {!Array<string>} - * @const - * @private - */ -goog.events.EventHandler.typeArray_ = []; + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; -/** - * Listen to an event on a Listenable. If the function is omitted then the - * EventHandler's handleEvent method will be used. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type to listen for or array of event types. - * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} - * opt_fn Optional callback function to be used as the listener or an object - * with handleEvent function. - * @param {boolean=} opt_capture Optional whether to use capture phase. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template EVENTOBJ - */ -goog.events.EventHandler.prototype.listen = function( - src, type, opt_fn, opt_capture) { - return this.listen_(src, type, opt_fn, opt_capture); -}; + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; + /** + * @type {Array.<WebGLTexture>} + * @private + */ + this.textures_ = []; -/** - * Listen to an event on a Listenable. If the function is omitted then the - * EventHandler's handleEvent method will be used. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type to listen for or array of event types. - * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| - * null|undefined} fn Optional callback function to be used as the - * listener or an object with handleEvent function. - * @param {boolean|undefined} capture Optional whether to use capture phase. - * @param {T} scope Object in whose scope to call the listener. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template T,EVENTOBJ - */ -goog.events.EventHandler.prototype.listenWithScope = function( - src, type, fn, capture, scope) { - // TODO(mknichel): Deprecate this function. - return this.listen_(src, type, fn, capture, scope); -}; + /** + * @type {Array.<WebGLTexture>} + * @private + */ + this.hitDetectionTextures_ = []; + /** + * @type {Array.<number>} + * @private + */ + this.vertices_ = []; -/** - * Listen to an event on a Listenable. If the function is omitted then the - * EventHandler's handleEvent method will be used. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type to listen for or array of event types. - * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn - * Optional callback function to be used as the listener or an object with - * handleEvent function. - * @param {boolean=} opt_capture Optional whether to use capture phase. - * @param {Object=} opt_scope Object in whose scope to call the listener. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template EVENTOBJ - * @private - */ -goog.events.EventHandler.prototype.listen_ = function(src, type, opt_fn, - opt_capture, - opt_scope) { - if (!goog.isArray(type)) { - if (type) { - goog.events.EventHandler.typeArray_[0] = type.toString(); - } - type = goog.events.EventHandler.typeArray_; - } - for (var i = 0; i < type.length; i++) { - var listenerObj = goog.events.listen( - src, type[i], opt_fn || this.handleEvent, - opt_capture || false, - opt_scope || this.handler_ || this); + /** + * @type {ol.webgl.Buffer} + * @private + */ + this.verticesBuffer_ = null; - if (!listenerObj) { - // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT - // (goog.events.CaptureSimulationMode) in IE8-, it will return null - // value. - return this; - } + /** + * Start index per feature (the index). + * @type {Array.<number>} + * @private + */ + this.startIndices_ = []; - var key = listenerObj.key; - this.keys_[key] = listenerObj; - } + /** + * Start index per feature (the feature). + * @type {Array.<ol.Feature>} + * @private + */ + this.startIndicesFeature_ = []; - return this; + /** + * @type {number|undefined} + * @private + */ + this.width_ = undefined; }; +goog.inherits(ol.render.webgl.ImageReplay, ol.render.VectorContext); /** - * Listen to an event on a Listenable. If the function is omitted, then the - * EventHandler's handleEvent method will be used. After the event has fired the - * event listener is removed from the target. If an array of event types is - * provided, each event type will be listened to once. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type to listen for or array of event types. - * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn - * Optional callback function to be used as the listener or an object with - * handleEvent function. - * @param {boolean=} opt_capture Optional whether to use capture phase. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template EVENTOBJ + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. */ -goog.events.EventHandler.prototype.listenOnce = function( - src, type, opt_fn, opt_capture) { - return this.listenOnce_(src, type, opt_fn, opt_capture); +ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = + function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other ImageReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + goog.asserts.assert(!goog.isNull(this.verticesBuffer_), + 'verticesBuffer must not be null'); + goog.asserts.assert(!goog.isNull(this.indicesBuffer_), + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer_; + var indicesBuffer = this.indicesBuffer_; + var textures = this.textures_; + var hitDetectionTextures = this.hitDetectionTextures_; + var gl = context.getGL(); + return function() { + if (!gl.isContextLost()) { + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.deleteTexture(textures[i]); + } + for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) { + gl.deleteTexture(hitDetectionTextures[i]); + } + } + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; }; /** - * Listen to an event on a Listenable. If the function is omitted, then the - * EventHandler's handleEvent method will be used. After the event has fired the - * event listener is removed from the target. If an array of event types is - * provided, each event type will be listened to once. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type to listen for or array of event types. - * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| - * null|undefined} fn Optional callback function to be used as the - * listener or an object with handleEvent function. - * @param {boolean|undefined} capture Optional whether to use capture phase. - * @param {T} scope Object in whose scope to call the listener. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template T,EVENTOBJ + * @inheritDoc */ -goog.events.EventHandler.prototype.listenOnceWithScope = function( - src, type, fn, capture, scope) { - // TODO(mknichel): Deprecate this function. - return this.listenOnce_(src, type, fn, capture, scope); -}; +ol.render.webgl.ImageReplay.prototype.drawAsync = goog.abstractMethod; /** - * Listen to an event on a Listenable. If the function is omitted, then the - * EventHandler's handleEvent method will be used. After the event has fired - * the event listener is removed from the target. If an array of event types is - * provided, each event type will be listened to once. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type to listen for or array of event types. - * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn - * Optional callback function to be used as the listener or an object with - * handleEvent function. - * @param {boolean=} opt_capture Optional whether to use capture phase. - * @param {Object=} opt_scope Object in whose scope to call the listener. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template EVENTOBJ + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} My end. * @private */ -goog.events.EventHandler.prototype.listenOnce_ = function( - src, type, opt_fn, opt_capture, opt_scope) { - if (goog.isArray(type)) { - for (var i = 0; i < type.length; i++) { - this.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope); - } - } else { - var listenerObj = goog.events.listenOnce( - src, type, opt_fn || this.handleEvent, opt_capture, - opt_scope || this.handler_ || this); - if (!listenerObj) { - // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT - // (goog.events.CaptureSimulationMode) in IE8-, it will return null - // value. - return this; - } +ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = + function(flatCoordinates, offset, end, stride) { + goog.asserts.assert(goog.isDef(this.anchorX_), 'anchorX is defined'); + goog.asserts.assert(goog.isDef(this.anchorY_), 'anchorY is defined'); + goog.asserts.assert(goog.isDef(this.height_), 'height is defined'); + goog.asserts.assert(goog.isDef(this.imageHeight_), 'imageHeight is defined'); + goog.asserts.assert(goog.isDef(this.imageWidth_), 'imageWidth is defined'); + goog.asserts.assert(goog.isDef(this.opacity_), 'opacity is defined'); + goog.asserts.assert(goog.isDef(this.originX_), 'originX is defined'); + goog.asserts.assert(goog.isDef(this.originY_), 'originY is defined'); + goog.asserts.assert(goog.isDef(this.rotateWithView_), + 'rotateWithView is defined'); + goog.asserts.assert(goog.isDef(this.rotation_), 'rotation is defined'); + goog.asserts.assert(goog.isDef(this.scale_), 'scale is defined'); + goog.asserts.assert(goog.isDef(this.width_), 'width is defined'); + var anchorX = this.anchorX_; + var anchorY = this.anchorY_; + var height = this.height_; + var imageHeight = this.imageHeight_; + var imageWidth = this.imageWidth_; + var opacity = this.opacity_; + var originX = this.originX_; + var originY = this.originY_; + var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; + var rotation = this.rotation_; + var scale = this.scale_; + var width = this.width_; + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + var numIndices = this.indices_.length; + var numVertices = this.vertices_.length; + var i, n, offsetX, offsetY, x, y; + for (i = offset; i < end; i += stride) { + x = flatCoordinates[i] - this.origin_[0]; + y = flatCoordinates[i + 1] - this.origin_[1]; - var key = listenerObj.key; - this.keys_[key] = listenerObj; + // There are 4 vertices per [x, y] point, one for each corner of the + // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if + // WebGL supported Geometry Shaders (which can emit new vertices), but that + // is not currently the case. + // + // And each vertex includes 8 values: the x and y coordinates, the x and + // y offsets used to calculate the position of the corner, the u and + // v texture coordinates for the corner, the opacity, and whether the + // the image should be rotated with the view (rotateWithView). + + n = numVertices / 8; + + // bottom-left corner + offsetX = -scale * anchorX; + offsetY = -scale * (height - anchorY); + this.vertices_[numVertices++] = x; + this.vertices_[numVertices++] = y; + this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices_[numVertices++] = originX / imageWidth; + this.vertices_[numVertices++] = (originY + height) / imageHeight; + this.vertices_[numVertices++] = opacity; + this.vertices_[numVertices++] = rotateWithView; + + // bottom-right corner + offsetX = scale * (width - anchorX); + offsetY = -scale * (height - anchorY); + this.vertices_[numVertices++] = x; + this.vertices_[numVertices++] = y; + this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices_[numVertices++] = (originX + width) / imageWidth; + this.vertices_[numVertices++] = (originY + height) / imageHeight; + this.vertices_[numVertices++] = opacity; + this.vertices_[numVertices++] = rotateWithView; + + // top-right corner + offsetX = scale * (width - anchorX); + offsetY = scale * anchorY; + this.vertices_[numVertices++] = x; + this.vertices_[numVertices++] = y; + this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices_[numVertices++] = (originX + width) / imageWidth; + this.vertices_[numVertices++] = originY / imageHeight; + this.vertices_[numVertices++] = opacity; + this.vertices_[numVertices++] = rotateWithView; + + // top-left corner + offsetX = -scale * anchorX; + offsetY = scale * anchorY; + this.vertices_[numVertices++] = x; + this.vertices_[numVertices++] = y; + this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices_[numVertices++] = originX / imageWidth; + this.vertices_[numVertices++] = originY / imageHeight; + this.vertices_[numVertices++] = opacity; + this.vertices_[numVertices++] = rotateWithView; + + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 1; + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n; + this.indices_[numIndices++] = n + 2; + this.indices_[numIndices++] = n + 3; } - return this; + return numVertices; }; /** - * Adds an event listener with a specific event wrapper on a DOM Node or an - * object that has implemented {@link goog.events.EventTarget}. A listener can - * only be added once to an object. - * - * @param {EventTarget|goog.events.EventTarget} src The node to listen to - * events on. - * @param {goog.events.EventWrapper} wrapper Event wrapper to use. - * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener - * Callback method, or an object with a handleEvent function. - * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to - * false). - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. + * @inheritDoc */ -goog.events.EventHandler.prototype.listenWithWrapper = function( - src, wrapper, listener, opt_capt) { - // TODO(mknichel): Remove the opt_scope from this function and then - // templatize it. - return this.listenWithWrapper_(src, wrapper, listener, opt_capt); +ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry = + function(multiPointGeometry, feature) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); }; /** - * Adds an event listener with a specific event wrapper on a DOM Node or an - * object that has implemented {@link goog.events.EventTarget}. A listener can - * only be added once to an object. - * - * @param {EventTarget|goog.events.EventTarget} src The node to listen to - * events on. - * @param {goog.events.EventWrapper} wrapper Event wrapper to use. - * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null} - * listener Optional callback function to be used as the - * listener or an object with handleEvent function. - * @param {boolean|undefined} capture Optional whether to use capture phase. - * @param {T} scope Object in whose scope to call the listener. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @template T + * @inheritDoc */ -goog.events.EventHandler.prototype.listenWithWrapperAndScope = function( - src, wrapper, listener, capture, scope) { - // TODO(mknichel): Deprecate this function. - return this.listenWithWrapper_(src, wrapper, listener, capture, scope); +ol.render.webgl.ImageReplay.prototype.drawPointGeometry = + function(pointGeometry, feature) { + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); }; /** - * Adds an event listener with a specific event wrapper on a DOM Node or an - * object that has implemented {@link goog.events.EventTarget}. A listener can - * only be added once to an object. - * - * @param {EventTarget|goog.events.EventTarget} src The node to listen to - * events on. - * @param {goog.events.EventWrapper} wrapper Event wrapper to use. - * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback - * method, or an object with a handleEvent function. - * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to - * false). - * @param {Object=} opt_scope Element in whose scope to call the listener. - * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for - * chaining of calls. - * @private + * @param {ol.webgl.Context} context Context. */ -goog.events.EventHandler.prototype.listenWithWrapper_ = function( - src, wrapper, listener, opt_capt, opt_scope) { - wrapper.listen(src, listener, opt_capt, opt_scope || this.handler_ || this, - this); - return this; +ol.render.webgl.ImageReplay.prototype.finish = function(context) { + var gl = context.getGL(); + + this.groupIndices_.push(this.indices_.length); + goog.asserts.assert(this.images_.length === this.groupIndices_.length, + 'number of images and groupIndices match'); + this.hitDetectionGroupIndices_.push(this.indices_.length); + goog.asserts.assert(this.hitDetectionImages_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionImages and hitDetectionGroupIndices match'); + + // create, bind, and populate the vertices buffer + this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + var indices = this.indices_; + var bits = context.hasOESElementIndexUint ? 32 : 16; + goog.asserts.assert(indices[indices.length - 1] < Math.pow(2, bits), + 'Too large element index detected [%s] (OES_element_index_uint "%s")', + indices[indices.length - 1], context.hasOESElementIndexUint); + + // create, bind, and populate the indices buffer + this.indicesBuffer_ = new ol.webgl.Buffer(indices); + context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + // create textures + /** @type {Object.<string, WebGLTexture>} */ + var texturePerImage = {}; + + this.createTextures_(this.textures_, this.images_, texturePerImage, gl); + goog.asserts.assert(this.textures_.length === this.groupIndices_.length, + 'number of textures and groupIndices match'); + + this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, + texturePerImage, gl); + goog.asserts.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionTextures and hitDetectionGroupIndices match'); + + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.height_ = undefined; + this.images_ = null; + this.hitDetectionImages_ = null; + this.imageHeight_ = undefined; + this.imageWidth_ = undefined; + this.indices_ = null; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.scale_ = undefined; + this.vertices_ = null; + this.width_ = undefined; }; /** - * @return {number} Number of listeners registered by this handler. + * @private + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images + * Images. + * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. */ -goog.events.EventHandler.prototype.getListenerCount = function() { - var count = 0; - for (var key in this.keys_) { - if (Object.prototype.hasOwnProperty.call(this.keys_, key)) { - count++; +ol.render.webgl.ImageReplay.prototype.createTextures_ = + function(textures, images, texturePerImage, gl) { + goog.asserts.assert(textures.length === 0, + 'upon creation, textures is empty'); + + var texture, image, uid, i; + var ii = images.length; + for (i = 0; i < ii; ++i) { + image = images[i]; + + uid = goog.getUid(image).toString(); + if (goog.object.containsKey(texturePerImage, uid)) { + texture = texturePerImage[uid]; + } else { + texture = ol.webgl.Context.createTexture( + gl, image, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE); + texturePerImage[uid] = texture; } + textures[i] = texture; } - return count; }; /** - * Unlistens on an event. - * @param {goog.events.ListenableType} src Event source. - * @param {string|Array<string>| - * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} - * type Event type or array of event types to unlisten to. - * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn - * Optional callback function to be used as the listener or an object with - * handleEvent function. - * @param {boolean=} opt_capture Optional whether to use capture phase. - * @param {Object=} opt_scope Object in whose scope to call the listener. - * @return {!goog.events.EventHandler} This object, allowing for chaining of - * calls. - * @template EVENTOBJ + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T */ -goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn, - opt_capture, - opt_scope) { - if (goog.isArray(type)) { - for (var i = 0; i < type.length; i++) { - this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope); - } +ol.render.webgl.ImageReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + + // bind the vertices buffer + goog.asserts.assert(!goog.isNull(this.verticesBuffer_), + 'verticesBuffer must not be null'); + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.verticesBuffer_); + + // bind the indices buffer + goog.asserts.assert(!goog.isNull(this.indicesBuffer_), + 'indecesBuffer must not be null'); + context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + var useColor = brightness || contrast != 1 || hue || saturation != 1; + + // get the program + var fragmentShader, vertexShader; + if (useColor) { + fragmentShader = + ol.render.webgl.imagereplay.shader.ColorFragment.getInstance(); + vertexShader = + ol.render.webgl.imagereplay.shader.ColorVertex.getInstance(); } else { - var listener = goog.events.getListener(src, type, - opt_fn || this.handleEvent, - opt_capture, opt_scope || this.handler_ || this); + fragmentShader = + ol.render.webgl.imagereplay.shader.DefaultFragment.getInstance(); + vertexShader = + ol.render.webgl.imagereplay.shader.DefaultVertex.getInstance(); + } + var program = context.getProgram(fragmentShader, vertexShader); - if (listener) { - goog.events.unlistenByKey(listener); - delete this.keys_[listener.key]; + // get the locations + var locations; + if (useColor) { + if (goog.isNull(this.colorLocations_)) { + locations = + new ol.render.webgl.imagereplay.shader.Color.Locations(gl, program); + this.colorLocations_ = locations; + } else { + locations = this.colorLocations_; + } + } else { + if (goog.isNull(this.defaultLocations_)) { + locations = + new ol.render.webgl.imagereplay.shader.Default.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; } } - return this; -}; + // use the program (FIXME: use the return value) + context.useProgram(program); + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, goog.webgl.FLOAT, + false, 32, 0); -/** - * Removes an event listener which was added with listenWithWrapper(). - * - * @param {EventTarget|goog.events.EventTarget} src The target to stop - * listening to events on. - * @param {goog.events.EventWrapper} wrapper Event wrapper to use. - * @param {function(?):?|{handleEvent:function(?):?}|null} listener The - * listener function to remove. - * @param {boolean=} opt_capt In DOM-compliant browsers, this determines - * whether the listener is fired during the capture or bubble phase of the - * event. - * @param {Object=} opt_scope Element in whose scope to call the listener. - * @return {!goog.events.EventHandler} This object, allowing for chaining of - * calls. - */ -goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper, - listener, opt_capt, opt_scope) { - wrapper.unlisten(src, listener, opt_capt, - opt_scope || this.handler_ || this, this); - return this; -}; + gl.enableVertexAttribArray(locations.a_offsets); + gl.vertexAttribPointer(locations.a_offsets, 2, goog.webgl.FLOAT, + false, 32, 8); + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer(locations.a_texCoord, 2, goog.webgl.FLOAT, + false, 32, 16); -/** - * Unlistens to all events. - */ -goog.events.EventHandler.prototype.removeAll = function() { - goog.object.forEach(this.keys_, goog.events.unlistenByKey); - this.keys_ = {}; -}; + gl.enableVertexAttribArray(locations.a_opacity); + gl.vertexAttribPointer(locations.a_opacity, 1, goog.webgl.FLOAT, + false, 32, 24); + gl.enableVertexAttribArray(locations.a_rotateWithView); + gl.vertexAttribPointer(locations.a_rotateWithView, 1, goog.webgl.FLOAT, + false, 32, 28); -/** - * Disposes of this EventHandler and removes all listeners that it registered. - * @override - * @protected - */ -goog.events.EventHandler.prototype.disposeInternal = function() { - goog.events.EventHandler.superClass_.disposeInternal.call(this); - this.removeAll(); -}; + // set the "uniform" values + var projectionMatrix = this.projectionMatrix_; + ol.vec.Mat4.makeTransform2D(projectionMatrix, + 0.0, 0.0, + 2 / (resolution * size[0]), + 2 / (resolution * size[1]), + -rotation, + -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1])); + var offsetScaleMatrix = this.offsetScaleMatrix_; + goog.vec.Mat4.makeScale(offsetScaleMatrix, 2 / size[0], 2 / size[1], 1); -/** - * Default event handler - * @param {goog.events.Event} e Event object. - */ -goog.events.EventHandler.prototype.handleEvent = function(e) { - throw Error('EventHandler.handleEvent not implemented'); -}; + var offsetRotateMatrix = this.offsetRotateMatrix_; + goog.vec.Mat4.makeIdentity(offsetRotateMatrix); + if (rotation !== 0) { + goog.vec.Mat4.rotateZ(offsetRotateMatrix, -rotation); + } -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, projectionMatrix); + gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, offsetScaleMatrix); + gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, + offsetRotateMatrix); + gl.uniform1f(locations.u_opacity, opacity); + if (useColor) { + gl.uniformMatrix4fv(locations.u_colorMatrix, false, + this.colorMatrix_.getMatrix(brightness, contrast, hue, saturation)); + } -/** - * @fileoverview Bidi utility functions. - * - */ + // draw! + var result; + if (!goog.isDef(featureCallback)) { + this.drawReplay_(gl, context, skippedFeaturesHash, + this.textures_, this.groupIndices_); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } -goog.provide('goog.style.bidi'); + // disable the vertex attrib arrays + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_opacity); + gl.disableVertexAttribArray(locations.a_rotateWithView); -goog.require('goog.dom'); -goog.require('goog.style'); -goog.require('goog.userAgent'); + return result; +}; /** - * Returns the normalized scrollLeft position for a scrolled element. - * @param {Element} element The scrolled element. - * @return {number} The number of pixels the element is scrolled. 0 indicates - * that the element is not scrolled at all (which, in general, is the - * left-most position in ltr and the right-most position in rtl). - */ -goog.style.bidi.getScrollLeft = function(element) { - var isRtl = goog.style.isRightToLeft(element); - if (isRtl && goog.userAgent.GECKO) { - // ScrollLeft starts at 0 and then goes negative as the element is scrolled - // towards the left. - return -element.scrollLeft; - } else if (isRtl && - !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) { - // ScrollLeft starts at the maximum positive value and decreases towards - // 0 as the element is scrolled towards the left. However, for overflow - // visible, there is no scrollLeft and the value always stays correctly at 0 - var overflowX = goog.style.getComputedOverflowX(element); - if (overflowX == 'visible') { - return element.scrollLeft; - } else { - return element.scrollWidth - element.clientWidth - element.scrollLeft; + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<number>} groupIndices Texture group indices. + */ +ol.render.webgl.ImageReplay.prototype.drawReplay_ = + function(gl, context, skippedFeaturesHash, textures, groupIndices) { + goog.asserts.assert(textures.length === groupIndices.length, + 'number of textures and groupIndeces match'); + var elementType = context.hasOESElementIndexUint ? + goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + if (!goog.object.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_( + gl, skippedFeaturesHash, textures, groupIndices, + elementType, elementSize); + } else { + var i, ii, start; + for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, textures[i]); + var end = groupIndices[i]; + this.drawElements_(gl, start, end, elementType, elementSize); + start = end; } } - // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and - // increases as the element is scrolled away from the start. - return element.scrollLeft; }; /** - * Returns the "offsetStart" of an element, analagous to offsetLeft but - * normalized for right-to-left environments and various browser - * inconsistencies. This value returned can always be passed to setScrollOffset - * to scroll to an element's left edge in a left-to-right offsetParent or - * right edge in a right-to-left offsetParent. + * Draw the replay while paying attention to skipped features. * - * For example, here offsetStart is 10px in an LTR environment and 5px in RTL: + * This functions creates groups of features that can be drawn to together, + * so that the number of `drawElements` calls is minimized. * - * <pre> - * | xxxxxxxxxx | - * ^^^^^^^^^^ ^^^^ ^^^^^ - * 10px elem 5px - * </pre> + * For example given the following texture groups: * - * If an element is positioned before the start of its offsetParent, the - * startOffset may be negative. This can be used with setScrollOffset to - * reliably scroll to an element: + * Group 1: A B C + * Group 2: D [E] F G * - * <pre> - * var scrollOffset = goog.style.bidi.getOffsetStart(element); - * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset); - * </pre> + * If feature E should be skipped, the following `drawElements` calls will be + * made: * - * @see setScrollOffset + * drawElements with feature A, B and C + * drawElements with feature D + * drawElements with feature F and G * - * @param {Element} element The element for which we need to determine the - * offsetStart position. - * @return {number} The offsetStart for that element. + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {Array.<WebGLTexture>} textures Textures. + * @param {Array.<number>} groupIndices Texture group indices. + * @param {number} elementType Element type. + * @param {number} elementSize Element Size. */ -goog.style.bidi.getOffsetStart = function(element) { - var offsetLeftForReal = element.offsetLeft; +ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = + function(gl, skippedFeaturesHash, textures, groupIndices, + elementType, elementSize) { + var featureIndex = 0; - // The element might not have an offsetParent. - // For example, the node might not be attached to the DOM tree, - // and position:fixed children do not have an offset parent. - // Just try to do the best we can with what we have. - var bestParent = element.offsetParent; + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, textures[i]); + var groupStart = (i > 0) ? groupIndices[i - 1] : 0; + var groupEnd = groupIndices[i]; + + var start = groupStart; + var end = groupStart; + while (featureIndex < this.startIndices_.length && + this.startIndices_[featureIndex] <= groupEnd) { + var feature = this.startIndicesFeature_[featureIndex]; + + var featureUid = goog.getUid(feature).toString(); + if (goog.isDef(skippedFeaturesHash[featureUid])) { + // feature should be skipped + if (start !== end) { + // draw the features so far + this.drawElements_(gl, start, end, elementType, elementSize); + } + // continue with the next feature + start = (featureIndex === this.startIndices_.length - 1) ? + groupEnd : this.startIndices_[featureIndex + 1]; + end = start; + } else { + // the feature is not skipped, augment the end index + end = (featureIndex === this.startIndices_.length - 1) ? + groupEnd : this.startIndices_[featureIndex + 1]; + } + featureIndex++; + } - if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') { - bestParent = goog.dom.getOwnerDocument(element).documentElement; + if (start !== end) { + // draw the remaining features (in case there was no skipped feature + // in this texture group, all features of a group are drawn together) + this.drawElements_(gl, start, end, elementType, elementSize); + } } +}; - // Just give up in this case. - if (!bestParent) { - return offsetLeftForReal; - } - if (goog.userAgent.GECKO) { - // When calculating an element's offsetLeft, Firefox erroneously subtracts - // the border width from the actual distance. So we need to add it back. - var borderWidths = goog.style.getBorderBox(bestParent); - offsetLeftForReal += borderWidths.left; - } else if (goog.userAgent.isDocumentModeOrHigher(8) && - !goog.userAgent.isDocumentModeOrHigher(9)) { - // When calculating an element's offsetLeft, IE8/9-Standards Mode - // erroneously adds the border width to the actual distance. So we need to - // subtract it. - var borderWidths = goog.style.getBorderBox(bestParent); - offsetLeftForReal -= borderWidths.left; - } +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {number} start Start index. + * @param {number} end End index. + * @param {number} elementType Element type. + * @param {number} elementSize Element Size. + */ +ol.render.webgl.ImageReplay.prototype.drawElements_ = function( + gl, start, end, elementType, elementSize) { + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); +}; - if (goog.style.isRightToLeft(bestParent)) { - // Right edge of the element relative to the left edge of its parent. - var elementRightOffset = offsetLeftForReal + element.offsetWidth; - // Distance from the parent's right edge to the element's right edge. - return bestParent.clientWidth - elementRightOffset; +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = + function(gl, context, skippedFeaturesHash, featureCallback, oneByOne, + opt_hitExtent) { + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll_(gl, context, + skippedFeaturesHash, featureCallback); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne_(gl, context, + skippedFeaturesHash, featureCallback, opt_hitExtent); } - - return offsetLeftForReal; }; /** - * Sets the element's scrollLeft attribute so it is correctly scrolled by - * offsetStart pixels. This takes into account whether the element is RTL and - * the nuances of different browsers. To scroll to the "beginning" of an - * element use getOffsetStart to obtain the element's offsetStart value and then - * pass the value to setScrollOffset. - * @see getOffsetStart - * @param {Element} element The element to set scrollLeft on. - * @param {number} offsetStart The number of pixels to scroll the element. - * If this value is < 0, 0 is used. + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T */ -goog.style.bidi.setScrollOffset = function(element, offsetStart) { - offsetStart = Math.max(offsetStart, 0); - // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to - // the number of pixels to scroll. - // Otherwise, in RTL, we need to account for different browser behavior. - if (!goog.style.isRightToLeft(element)) { - element.scrollLeft = offsetStart; - } else if (goog.userAgent.GECKO) { - // Negative scroll-left positions in RTL. - element.scrollLeft = -offsetStart; - } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) { - // Take the current scrollLeft value and move to the right by the - // offsetStart to get to the left edge of the element, and then by - // the clientWidth of the element to get to the right edge. - element.scrollLeft = - element.scrollWidth - offsetStart - element.clientWidth; +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = + function(gl, context, skippedFeaturesHash, featureCallback) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawReplay_(gl, context, skippedFeaturesHash, + this.hitDetectionTextures_, this.hitDetectionGroupIndices_); + + var result = featureCallback(null); + if (result) { + return result; } else { - element.scrollLeft = offsetStart; + return undefined; } }; /** - * Sets the element's left style attribute in LTR or right style attribute in - * RTL. Also clears the left attribute in RTL and the right attribute in LTR. - * @param {Element} elem The element to position. - * @param {number} left The left position in LTR; will be set as right in RTL. - * @param {?number} top The top position. If null only the left/right is set. - * @param {boolean} isRtl Whether we are in RTL mode. + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T */ -goog.style.bidi.setPosition = function(elem, left, top, isRtl) { - if (!goog.isNull(top)) { - elem.style.top = top + 'px'; +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = + function(gl, context, skippedFeaturesHash, featureCallback, + opt_hitExtent) { + goog.asserts.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length, + 'number of hitDetectionTextures and hitDetectionGroupIndices match'); + var elementType = context.hasOESElementIndexUint ? + goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var i, groupStart, start, end, feature, featureUid; + var featureIndex = this.startIndices_.length - 1; + for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); + groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; + end = this.hitDetectionGroupIndices_[i]; + + // draw all features for this texture group + while (featureIndex >= 0 && + this.startIndices_[featureIndex] >= groupStart) { + start = this.startIndices_[featureIndex]; + feature = this.startIndicesFeature_[featureIndex]; + featureUid = goog.getUid(feature).toString(); + + if (!goog.isDef(skippedFeaturesHash[featureUid]) && + (!goog.isDef(opt_hitExtent) || ol.extent.intersects( + opt_hitExtent, feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements_(gl, start, end, elementType, elementSize); + + var result = featureCallback(feature); + if (result) { + return result; + } + } + + end = start; + featureIndex--; + } } - if (isRtl) { - elem.style.right = left + 'px'; - elem.style.left = ''; + return undefined; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = goog.abstractMethod; + + +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { + var anchor = imageStyle.getAnchor(); + var image = imageStyle.getImage(1); + var imageSize = imageStyle.getImageSize(); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + var hitDetectionImageSize = imageStyle.getHitDetectionImageSize(); + var opacity = imageStyle.getOpacity(); + var origin = imageStyle.getOrigin(); + var rotateWithView = imageStyle.getRotateWithView(); + var rotation = imageStyle.getRotation(); + var size = imageStyle.getSize(); + var scale = imageStyle.getScale(); + goog.asserts.assert(!goog.isNull(anchor), 'imageStyle anchor is not null'); + goog.asserts.assert(!goog.isNull(image), 'imageStyle image is not null'); + goog.asserts.assert(!goog.isNull(imageSize), + 'imageStyle imageSize is not null'); + goog.asserts.assert(!goog.isNull(hitDetectionImage), + 'imageStyle hitDetectionImage is not null'); + goog.asserts.assert(!goog.isNull(hitDetectionImageSize), + 'imageStyle hitDetectionImageSize is not null'); + goog.asserts.assert(goog.isDef(opacity), 'imageStyle opacity is defined'); + goog.asserts.assert(!goog.isNull(origin), 'imageStyle origin is not null'); + goog.asserts.assert(goog.isDef(rotateWithView), + 'imageStyle rotateWithView is defined'); + goog.asserts.assert(goog.isDef(rotation), 'imageStyle rotation is defined'); + goog.asserts.assert(!goog.isNull(size), 'imageStyle size is not null'); + goog.asserts.assert(goog.isDef(scale), 'imageStyle scale is defined'); + + var currentImage; + if (this.images_.length === 0) { + this.images_.push(image); } else { - elem.style.left = left + 'px'; - elem.style.right = ''; + currentImage = this.images_[this.images_.length - 1]; + if (goog.getUid(currentImage) != goog.getUid(image)) { + this.groupIndices_.push(this.indices_.length); + goog.asserts.assert(this.groupIndices_.length === this.images_.length, + 'number of groupIndices and images match'); + this.images_.push(image); + } + } + + if (this.hitDetectionImages_.length === 0) { + this.hitDetectionImages_.push(hitDetectionImage); + } else { + currentImage = + this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; + if (goog.getUid(currentImage) != goog.getUid(hitDetectionImage)) { + this.hitDetectionGroupIndices_.push(this.indices_.length); + goog.asserts.assert(this.hitDetectionGroupIndices_.length === + this.hitDetectionImages_.length, + 'number of hitDetectionGroupIndices and hitDetectionImages match'); + this.hitDetectionImages_.push(hitDetectionImage); + } } + + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.height_ = size[1]; + this.imageHeight_ = imageSize[1]; + this.imageWidth_ = imageSize[0]; + this.opacity_ = opacity; + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotation_ = rotation; + this.rotateWithView_ = rotateWithView; + this.scale_ = scale; + this.width_ = size[0]; }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** - * @fileoverview Drag Utilities. - * - * Provides extensible functionality for drag & drop behaviour. - * - * @see ../demos/drag.html - * @see ../demos/dragger.html + * @constructor + * @implements {ol.render.IReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. + * @struct */ +ol.render.webgl.ReplayGroup = function( + tolerance, maxExtent, opt_renderBuffer) { + /** + * @type {ol.Extent} + * @private + */ + this.maxExtent_ = maxExtent; -goog.provide('goog.fx.DragEvent'); -goog.provide('goog.fx.Dragger'); -goog.provide('goog.fx.Dragger.EventType'); + /** + * @type {number} + * @private + */ + this.tolerance_ = tolerance; -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventHandler'); -goog.require('goog.events.EventTarget'); -goog.require('goog.events.EventType'); -goog.require('goog.math.Coordinate'); -goog.require('goog.math.Rect'); -goog.require('goog.style'); -goog.require('goog.style.bidi'); -goog.require('goog.userAgent'); + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * ImageReplay only is supported at this point. + * @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>} + * @private + */ + this.replays_ = {}; +}; /** - * A class that allows mouse or touch-based dragging (moving) of an element - * - * @param {Element} target The element that will be dragged. - * @param {Element=} opt_handle An optional handle to control the drag, if null - * the target is used. - * @param {goog.math.Rect=} opt_limits Object containing left, top, width, - * and height. - * - * @extends {goog.events.EventTarget} - * @constructor + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. */ -goog.fx.Dragger = function(target, opt_handle, opt_limits) { - goog.events.EventTarget.call(this); - this.target = target; - this.handle = opt_handle || target; - this.limits = opt_limits || new goog.math.Rect(NaN, NaN, NaN, NaN); - - this.document_ = goog.dom.getOwnerDocument(target); - this.eventHandler_ = new goog.events.EventHandler(this); - this.registerDisposable(this.eventHandler_); - - // Add listener. Do not use the event handler here since the event handler is - // used for listeners added and removed during the drag operation. - goog.events.listen(this.handle, - [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN], - this.startDrag, false, this); +ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = + function(context) { + var functions = []; + var replayKey; + for (replayKey in this.replays_) { + functions.push( + this.replays_[replayKey].getDeleteResourcesFunction(context)); + } + return goog.functions.sequence.apply(null, functions); }; -goog.inherits(goog.fx.Dragger, goog.events.EventTarget); -// Dragger is meant to be extended, but defines most properties on its -// prototype, thus making it unsuitable for sealing. -goog.tagUnsealableClass(goog.fx.Dragger); /** - * Whether setCapture is supported by the browser. - * @type {boolean} - * @private + * @param {ol.webgl.Context} context Context. */ -goog.fx.Dragger.HAS_SET_CAPTURE_ = - // IE and Gecko after 1.9.3 has setCapture - // WebKit does not yet: https://bugs.webkit.org/show_bug.cgi?id=27330 - goog.userAgent.IE || - goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.3'); +ol.render.webgl.ReplayGroup.prototype.finish = function(context) { + var replayKey; + for (replayKey in this.replays_) { + this.replays_[replayKey].finish(context); + } +}; /** - * Creates copy of node being dragged. This is a utility function to be used - * wherever it is inappropriate for the original source to follow the mouse - * cursor itself. - * - * @param {Element} sourceEl Element to copy. - * @return {!Element} The clone of {@code sourceEl}. + * @inheritDoc */ -goog.fx.Dragger.cloneNode = function(sourceEl) { - var clonedEl = /** @type {Element} */ (sourceEl.cloneNode(true)), - origTexts = sourceEl.getElementsByTagName('textarea'), - dragTexts = clonedEl.getElementsByTagName('textarea'); - // Cloning does not copy the current value of textarea elements, so correct - // this manually. - for (var i = 0; i < origTexts.length; i++) { - dragTexts[i].value = origTexts[i].value; - } - switch (sourceEl.tagName.toLowerCase()) { - case 'tr': - return goog.dom.createDom( - 'table', null, goog.dom.createDom('tbody', null, clonedEl)); - case 'td': - case 'th': - return goog.dom.createDom( - 'table', null, goog.dom.createDom('tbody', null, goog.dom.createDom( - 'tr', null, clonedEl))); - case 'textarea': - clonedEl.value = sourceEl.value; - default: - return clonedEl; +ol.render.webgl.ReplayGroup.prototype.getReplay = + function(zIndex, replayType) { + var replay = this.replays_[replayType]; + if (!goog.isDef(replay)) { + var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType]; + goog.asserts.assert(goog.isDef(constructor), + replayType + + ' constructor missing from ol.render.webgl.BATCH_CONSTRUCTORS_'); + replay = new constructor(this.tolerance_, this.maxExtent_); + this.replays_[replayType] = replay; } + return replay; }; /** - * Constants for event names. - * @enum {string} + * @inheritDoc */ -goog.fx.Dragger.EventType = { - // The drag action was canceled before the START event. Possible reasons: - // disabled dragger, dragging with the right mouse button or releasing the - // button before reaching the hysteresis distance. - EARLY_CANCEL: 'earlycancel', - START: 'start', - BEFOREDRAG: 'beforedrag', - DRAG: 'drag', - END: 'end' +ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { + return goog.object.isEmpty(this.replays_); }; /** - * Reference to drag target element. - * @type {Element} + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. */ -goog.fx.Dragger.prototype.target; +ol.render.webgl.ReplayGroup.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { + var i, ii, replay, result; + for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) { + replay = this.replays_[ol.render.REPLAY_ORDER[i]]; + if (goog.isDef(replay)) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + undefined, false); + } + } +}; /** - * Reference to the handler that initiates the drag. - * @type {Element} + * @private + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T */ -goog.fx.Dragger.prototype.handle; +ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var i, replay, result; + for (i = ol.render.REPLAY_ORDER.length - 1; i >= 0; --i) { + replay = this.replays_[ol.render.REPLAY_ORDER[i]]; + if (goog.isDef(replay)) { + result = replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); + if (result) { + return result; + } + } + } + return undefined; +}; /** - * Object representing the limits of the drag region. - * @type {goog.math.Rect} + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T */ -goog.fx.Dragger.prototype.limits; +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); -/** - * Whether the element is rendered right-to-left. We initialize this lazily. - * @type {boolean|undefined}} - * @private - */ -goog.fx.Dragger.prototype.rightToLeft_; + /** + * @type {ol.Extent} + */ + var hitExtent; + if (goog.isDef(this.renderBuffer_)) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(coordinate), + resolution * this.renderBuffer_); + } + return this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, + pixelRatio, opacity, brightness, contrast, hue, saturation, + skippedFeaturesHash, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); -/** - * Current x position of mouse or touch relative to viewport. - * @type {number} - */ -goog.fx.Dragger.prototype.clientX = 0; + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + } + }, true, hitExtent); +}; /** - * Current y position of mouse or touch relative to viewport. - * @type {number} + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @return {boolean} Is there a feature at the given coordinate? */ -goog.fx.Dragger.prototype.clientY = 0; +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + var hasFeature = this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, + pixelRatio, opacity, brightness, contrast, hue, saturation, + skippedFeaturesHash, + /** + * @param {ol.Feature} feature Feature. + * @return {boolean} Is there a feature? + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + return imageData[3] > 0; + }, false); -/** - * Current x position of mouse or touch relative to screen. Deprecated because - * it doesn't take into affect zoom level or pixel density. - * @type {number} - * @deprecated Consider switching to clientX instead. - */ -goog.fx.Dragger.prototype.screenX = 0; + return goog.isDef(hasFeature); +}; /** - * Current y position of mouse or touch relative to screen. Deprecated because - * it doesn't take into affect zoom level or pixel density. - * @type {number} - * @deprecated Consider switching to clientY instead. + * @const + * @private + * @type {Object.<ol.render.ReplayType, + * function(new: ol.render.webgl.ImageReplay, number, + * ol.Extent)>} */ -goog.fx.Dragger.prototype.screenY = 0; +ol.render.webgl.BATCH_CONSTRUCTORS_ = { + 'Image': ol.render.webgl.ImageReplay +}; /** - * The x position where the first mousedown or touchstart occurred. - * @type {number} + * @const + * @private + * @type {Array.<number>} */ -goog.fx.Dragger.prototype.startX = 0; +ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1]; +goog.provide('ol.render.webgl.Immediate'); +goog.require('goog.array'); +goog.require('goog.object'); +goog.require('ol.extent'); +goog.require('ol.render.VectorContext'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.ReplayGroup'); -/** - * The y position where the first mousedown or touchstart occurred. - * @type {number} - */ -goog.fx.Dragger.prototype.startY = 0; /** - * Current x position of drag relative to target's parent. - * @type {number} + * @constructor + * @extends {ol.render.VectorContext} + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {ol.Extent} extent Extent. + * @param {number} pixelRatio Pixel ratio. + * @struct */ -goog.fx.Dragger.prototype.deltaX = 0; +ol.render.webgl.Immediate = function(context, + center, resolution, rotation, size, extent, pixelRatio) { + goog.base(this); + /** + * @private + */ + this.context_ = context; -/** - * Current y position of drag relative to target's parent. - * @type {number} - */ -goog.fx.Dragger.prototype.deltaY = 0; + /** + * @private + */ + this.center_ = center; + /** + * @private + */ + this.extent_ = extent; -/** - * The current page scroll value. - * @type {goog.math.Coordinate} - */ -goog.fx.Dragger.prototype.pageScroll; + /** + * @private + */ + this.pixelRatio_ = pixelRatio; + /** + * @private + */ + this.size_ = size; -/** - * Whether dragging is currently enabled. - * @type {boolean} - * @private - */ -goog.fx.Dragger.prototype.enabled_ = true; + /** + * @private + */ + this.rotation_ = rotation; + /** + * @private + */ + this.resolution_ = resolution; -/** - * Whether object is currently being dragged. - * @type {boolean} - * @private - */ -goog.fx.Dragger.prototype.dragging_ = false; + /** + * @private + * @type {ol.style.Image} + */ + this.imageStyle_ = null; + + /** + * @private + * @type {Object.<string, + * Array.<function(ol.render.webgl.Immediate)>>} + */ + this.callbacksByZIndex_ = {}; +}; +goog.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); /** - * The amount of distance, in pixels, after which a mousedown or touchstart is - * considered a drag. - * @type {number} - * @private + * FIXME: empty description for jsdoc */ -goog.fx.Dragger.prototype.hysteresisDistanceSquared_ = 0; +ol.render.webgl.Immediate.prototype.flush = function() { + /** @type {Array.<number>} */ + var zs = goog.array.map(goog.object.getKeys(this.callbacksByZIndex_), Number); + goog.array.sort(zs); + var i, ii, callbacks, j, jj; + for (i = 0, ii = zs.length; i < ii; ++i) { + callbacks = this.callbacksByZIndex_[zs[i].toString()]; + for (j = 0, jj = callbacks.length; j < jj; ++j) { + callbacks[j](this); + } + } +}; /** - * Timestamp of when the mousedown or touchstart occurred. - * @type {number} - * @private + * @param {number} zIndex Z index. + * @param {function(ol.render.webgl.Immediate)} callback Callback. + * @api */ -goog.fx.Dragger.prototype.mouseDownTime_ = 0; +ol.render.webgl.Immediate.prototype.drawAsync = function(zIndex, callback) { + var zIndexKey = zIndex.toString(); + var callbacks = this.callbacksByZIndex_[zIndexKey]; + if (goog.isDef(callbacks)) { + callbacks.push(callback); + } else { + this.callbacksByZIndex_[zIndexKey] = [callback]; + } +}; /** - * Reference to a document object to use for the events. - * @type {Document} - * @private + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.document_; +ol.render.webgl.Immediate.prototype.drawCircleGeometry = + function(circleGeometry, data) { +}; /** - * The SCROLL event target used to make drag element follow scrolling. - * @type {EventTarget} - * @private + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.scrollTarget_; +ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!goog.isDefAndNotNull(geometry) || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + var zIndex = style.getZIndex(); + if (!goog.isDef(zIndex)) { + zIndex = 0; + } + this.drawAsync(zIndex, function(render) { + render.setFillStrokeStyle(style.getFill(), style.getStroke()); + render.setImageStyle(style.getImage()); + render.setTextStyle(style.getText()); + var type = geometry.getType(); + var renderGeometry = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_[type]; + // Do not assert since all kinds of geometries are not handled yet. + // In spite, render what we support. + if (renderGeometry) { + renderGeometry.call(render, geometry, null); + } + }); +}; /** - * Whether IE drag events cancelling is on. - * @type {boolean} - * @private + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.ieDragStartCancellingOn_ = false; +ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry = + function(geometryCollectionGeometry, data) { + var geometries = geometryCollectionGeometry.getGeometriesArray(); + var renderers = ol.render.webgl.Immediate.GEOMETRY_RENDERERS_; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + var geometry = geometries[i]; + var geometryRenderer = renderers[geometry.getType()]; + // Do not assert since all kinds of geometries are not handled yet. + // In order to support hierarchies, delegate instead what we can to + // valid renderers. + if (geometryRenderer) { + geometryRenderer.call(this, geometry, data); + } + } +}; /** - * Whether the dragger implements the changes described in http://b/6324964, - * making it truly RTL. This is a temporary flag to allow clients to transition - * to the new behavior at their convenience. At some point it will be the - * default. - * @type {boolean} - * @private + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.useRightPositioningForRtl_ = false; +ol.render.webgl.Immediate.prototype.drawPointGeometry = + function(pointGeometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawPointGeometry(pointGeometry, data); + replay.finish(context); + // default colors + var opacity = 1; + var brightness = 0; + var contrast = 1; + var hue = 0; + var saturation = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, brightness, + contrast, hue, saturation, skippedFeatures, featureCallback, oneByOne); + replay.getDeleteResourcesFunction(context)(); +}; /** - * Turns on/off true RTL behavior. This should be called immediately after - * construction. This is a temporary flag to allow clients to transition - * to the new component at their convenience. At some point true will be the - * default. - * @param {boolean} useRightPositioningForRtl True if "right" should be used for - * positioning, false if "left" should be used for positioning. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.enableRightPositioningForRtl = - function(useRightPositioningForRtl) { - this.useRightPositioningForRtl_ = useRightPositioningForRtl; +ol.render.webgl.Immediate.prototype.drawLineStringGeometry = + function(lineStringGeometry, data) { }; /** - * Returns the event handler, intended for subclass use. - * @return {!goog.events.EventHandler<T>} The event handler. - * @this T - * @template T + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.getHandler = function() { - // TODO(user): templated "this" values currently result in "this" being - // "unknown" in the body of the function. - var self = /** @type {goog.fx.Dragger} */ (this); - return self.eventHandler_; +ol.render.webgl.Immediate.prototype.drawMultiLineStringGeometry = + function(multiLineStringGeometry, data) { }; /** - * Sets (or reset) the Drag limits after a Dragger is created. - * @param {goog.math.Rect?} limits Object containing left, top, width, - * height for new Dragger limits. If target is right-to-left and - * enableRightPositioningForRtl(true) is called, then rect is interpreted as - * right, top, width, and height. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.setLimits = function(limits) { - this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN); +ol.render.webgl.Immediate.prototype.drawMultiPointGeometry = + function(multiPointGeometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawMultiPointGeometry(multiPointGeometry, data); + replay.finish(context); + // default colors + var opacity = 1; + var brightness = 0; + var contrast = 1; + var hue = 0; + var saturation = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, brightness, + contrast, hue, saturation, skippedFeatures, featureCallback, oneByOne); + replay.getDeleteResourcesFunction(context)(); }; /** - * Sets the distance the user has to drag the element before a drag operation is - * started. - * @param {number} distance The number of pixels after which a mousedown and - * move is considered a drag. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.setHysteresis = function(distance) { - this.hysteresisDistanceSquared_ = Math.pow(distance, 2); +ol.render.webgl.Immediate.prototype.drawMultiPolygonGeometry = + function(multiPolygonGeometry, data) { }; /** - * Gets the distance the user has to drag the element before a drag operation is - * started. - * @return {number} distance The number of pixels after which a mousedown and - * move is considered a drag. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.getHysteresis = function() { - return Math.sqrt(this.hysteresisDistanceSquared_); +ol.render.webgl.Immediate.prototype.drawPolygonGeometry = + function(polygonGeometry, data) { }; /** - * Sets the SCROLL event target to make drag element follow scrolling. - * - * @param {EventTarget} scrollTarget The event target that dispatches SCROLL - * events. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) { - this.scrollTarget_ = scrollTarget; +ol.render.webgl.Immediate.prototype.drawText = + function(flatCoordinates, offset, end, stride, geometry, data) { }; /** - * Enables cancelling of built-in IE drag events. - * @param {boolean} cancelIeDragStart Whether to enable cancelling of IE - * dragstart event. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) { - this.ieDragStartCancellingOn_ = cancelIeDragStart; +ol.render.webgl.Immediate.prototype.setFillStrokeStyle = + function(fillStyle, strokeStyle) { }; /** - * @return {boolean} Whether the dragger is enabled. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.getEnabled = function() { - return this.enabled_; +ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { + this.imageStyle_ = imageStyle; }; /** - * Set whether dragger is enabled - * @param {boolean} enabled Whether dragger is enabled. + * @inheritDoc + * @api */ -goog.fx.Dragger.prototype.setEnabled = function(enabled) { - this.enabled_ = enabled; -}; - - -/** @override */ -goog.fx.Dragger.prototype.disposeInternal = function() { - goog.fx.Dragger.superClass_.disposeInternal.call(this); - goog.events.unlisten(this.handle, - [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN], - this.startDrag, false, this); - this.cleanUpAfterDragging_(); - - this.target = null; - this.handle = null; +ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) { }; /** - * Whether the DOM element being manipulated is rendered right-to-left. - * @return {boolean} True if the DOM element is rendered right-to-left, false - * otherwise. + * @const * @private + * @type {Object.<ol.geom.GeometryType, + * function(this: ol.render.webgl.Immediate, ol.geom.Geometry, + * Object)>} */ -goog.fx.Dragger.prototype.isRightToLeft_ = function() { - if (!goog.isDef(this.rightToLeft_)) { - this.rightToLeft_ = goog.style.isRightToLeft(this.target); - } - return this.rightToLeft_; +ol.render.webgl.Immediate.GEOMETRY_RENDERERS_ = { + 'Point': ol.render.webgl.Immediate.prototype.drawPointGeometry, + 'MultiPoint': ol.render.webgl.Immediate.prototype.drawMultiPointGeometry, + 'GeometryCollection': + ol.render.webgl.Immediate.prototype.drawGeometryCollectionGeometry }; +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.map.shader.Color'); -/** - * Event handler that is used to start the drag - * @param {goog.events.BrowserEvent} e Event object. - */ -goog.fx.Dragger.prototype.startDrag = function(e) { - var isMouseDown = e.type == goog.events.EventType.MOUSEDOWN; - - // Dragger.startDrag() can be called by AbstractDragDrop with a mousemove - // event and IE does not report pressed mouse buttons on mousemove. Also, - // it does not make sense to check for the button if the user is already - // dragging. +goog.require('ol.webgl.shader'); - if (this.enabled_ && !this.dragging_ && - (!isMouseDown || e.isMouseActionButton())) { - this.maybeReinitTouchEvent_(e); - if (this.hysteresisDistanceSquared_ == 0) { - if (this.fireDragStart_(e)) { - this.dragging_ = true; - e.preventDefault(); - } else { - // If the start drag is cancelled, don't setup for a drag. - return; - } - } else { - // Need to preventDefault for hysteresis to prevent page getting selected. - e.preventDefault(); - } - this.setupDragHandlers(); - this.clientX = this.startX = e.clientX; - this.clientY = this.startY = e.clientY; - this.screenX = e.screenX; - this.screenY = e.screenY; - this.computeInitialPosition(); - this.pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll(); - this.mouseDownTime_ = goog.now(); - } else { - this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL); - } +/** + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct + */ +ol.renderer.webgl.map.shader.ColorFragment = function() { + goog.base(this, ol.renderer.webgl.map.shader.ColorFragment.SOURCE); }; +goog.inherits(ol.renderer.webgl.map.shader.ColorFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorFragment); /** - * Sets up event handlers when dragging starts. - * @protected + * @const + * @type {string} */ -goog.fx.Dragger.prototype.setupDragHandlers = function() { - var doc = this.document_; - var docEl = doc.documentElement; - // Use bubbling when we have setCapture since we got reports that IE has - // problems with the capturing events in combination with setCapture. - var useCapture = !goog.fx.Dragger.HAS_SET_CAPTURE_; - - this.eventHandler_.listen(doc, - [goog.events.EventType.TOUCHMOVE, goog.events.EventType.MOUSEMOVE], - this.handleMove_, useCapture); - this.eventHandler_.listen(doc, - [goog.events.EventType.TOUCHEND, goog.events.EventType.MOUSEUP], - this.endDrag, useCapture); - - if (goog.fx.Dragger.HAS_SET_CAPTURE_) { - docEl.setCapture(false); - this.eventHandler_.listen(docEl, - goog.events.EventType.LOSECAPTURE, - this.endDrag); - } else { - // Make sure we stop the dragging if the window loses focus. - // Don't use capture in this listener because we only want to end the drag - // if the actual window loses focus. Since blur events do not bubble we use - // a bubbling listener on the window. - this.eventHandler_.listen(goog.dom.getWindow(doc), - goog.events.EventType.BLUR, - this.endDrag); - } +ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\n// @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp\nuniform mat4 u_colorMatrix;\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = (u_colorMatrix * vec4(texColor.rgb, 1.)).rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; - if (goog.userAgent.IE && this.ieDragStartCancellingOn_) { - // Cancel IE's 'ondragstart' event. - this.eventHandler_.listen(doc, goog.events.EventType.DRAGSTART, - goog.events.Event.preventDefault); - } - if (this.scrollTarget_) { - this.eventHandler_.listen(this.scrollTarget_, goog.events.EventType.SCROLL, - this.onScroll_, useCapture); - } -}; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}'; /** - * Fires a goog.fx.Dragger.EventType.START event. - * @param {goog.events.BrowserEvent} e Browser event that triggered the drag. - * @return {boolean} False iff preventDefault was called on the DragEvent. - * @private + * @const + * @type {string} */ -goog.fx.Dragger.prototype.fireDragStart_ = function(e) { - return this.dispatchEvent(new goog.fx.DragEvent( - goog.fx.Dragger.EventType.START, this, e.clientX, e.clientY, e)); -}; +ol.renderer.webgl.map.shader.ColorFragment.SOURCE = goog.DEBUG ? + ol.renderer.webgl.map.shader.ColorFragment.DEBUG_SOURCE : + ol.renderer.webgl.map.shader.ColorFragment.OPTIMIZED_SOURCE; + /** - * Unregisters the event handlers that are only active during dragging, and - * releases mouse capture. - * @private + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct */ -goog.fx.Dragger.prototype.cleanUpAfterDragging_ = function() { - this.eventHandler_.removeAll(); - if (goog.fx.Dragger.HAS_SET_CAPTURE_) { - this.document_.releaseCapture(); - } +ol.renderer.webgl.map.shader.ColorVertex = function() { + goog.base(this, ol.renderer.webgl.map.shader.ColorVertex.SOURCE); }; +goog.inherits(ol.renderer.webgl.map.shader.ColorVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.renderer.webgl.map.shader.ColorVertex); /** - * Event handler that is used to end the drag. - * @param {goog.events.BrowserEvent} e Event object. - * @param {boolean=} opt_dragCanceled Whether the drag has been canceled. + * @const + * @type {string} */ -goog.fx.Dragger.prototype.endDrag = function(e, opt_dragCanceled) { - this.cleanUpAfterDragging_(); - - if (this.dragging_) { - this.maybeReinitTouchEvent_(e); - this.dragging_ = false; - - var x = this.limitX(this.deltaX); - var y = this.limitY(this.deltaY); - var dragCanceled = opt_dragCanceled || - e.type == goog.events.EventType.TOUCHCANCEL; - this.dispatchEvent(new goog.fx.DragEvent( - goog.fx.Dragger.EventType.END, this, e.clientX, e.clientY, e, x, y, - dragCanceled)); - } else { - this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL); - } -}; +ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; /** - * Event handler that is used to end the drag by cancelling it. - * @param {goog.events.BrowserEvent} e Event object. + * @const + * @type {string} */ -goog.fx.Dragger.prototype.endDragCancel = function(e) { - this.endDrag(e, true); -}; +ol.renderer.webgl.map.shader.ColorVertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; /** - * Re-initializes the event with the first target touch event or, in the case - * of a stop event, the last changed touch. - * @param {goog.events.BrowserEvent} e A TOUCH... event. - * @private + * @const + * @type {string} */ -goog.fx.Dragger.prototype.maybeReinitTouchEvent_ = function(e) { - var type = e.type; +ol.renderer.webgl.map.shader.ColorVertex.SOURCE = goog.DEBUG ? + ol.renderer.webgl.map.shader.ColorVertex.DEBUG_SOURCE : + ol.renderer.webgl.map.shader.ColorVertex.OPTIMIZED_SOURCE; - if (type == goog.events.EventType.TOUCHSTART || - type == goog.events.EventType.TOUCHMOVE) { - e.init(e.getBrowserEvent().targetTouches[0], e.currentTarget); - } else if (type == goog.events.EventType.TOUCHEND || - type == goog.events.EventType.TOUCHCANCEL) { - e.init(e.getBrowserEvent().changedTouches[0], e.currentTarget); - } -}; /** - * Event handler that is used on mouse / touch move to update the drag - * @param {goog.events.BrowserEvent} e Event object. - * @private + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct */ -goog.fx.Dragger.prototype.handleMove_ = function(e) { - if (this.enabled_) { - this.maybeReinitTouchEvent_(e); - // dx in right-to-left cases is relative to the right. - var sign = this.useRightPositioningForRtl_ && - this.isRightToLeft_() ? -1 : 1; - var dx = sign * (e.clientX - this.clientX); - var dy = e.clientY - this.clientY; - this.clientX = e.clientX; - this.clientY = e.clientY; - this.screenX = e.screenX; - this.screenY = e.screenY; +ol.renderer.webgl.map.shader.Color.Locations = function(gl, program) { - if (!this.dragging_) { - var diffX = this.startX - this.clientX; - var diffY = this.startY - this.clientY; - var distance = diffX * diffX + diffY * diffY; - if (distance > this.hysteresisDistanceSquared_) { - if (this.fireDragStart_(e)) { - this.dragging_ = true; - } else { - // DragListGroup disposes of the dragger if BEFOREDRAGSTART is - // canceled. - if (!this.isDisposed()) { - this.endDrag(e); - } - return; - } - } - } + /** + * @type {WebGLUniformLocation} + */ + this.u_colorMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_colorMatrix' : 'f'); - var pos = this.calculatePosition_(dx, dy); - var x = pos.x; - var y = pos.y; + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, goog.DEBUG ? 'u_opacity' : 'g'); - if (this.dragging_) { + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_projectionMatrix' : 'e'); - var rv = this.dispatchEvent(new goog.fx.DragEvent( - goog.fx.Dragger.EventType.BEFOREDRAG, this, e.clientX, e.clientY, - e, x, y)); + /** + * @type {WebGLUniformLocation} + */ + this.u_texCoordMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_texCoordMatrix' : 'd'); - // Only do the defaultAction and dispatch drag event if predrag didn't - // prevent default - if (rv) { - this.doDrag(e, x, y, false); - e.preventDefault(); - } - } - } -}; + /** + * @type {WebGLUniformLocation} + */ + this.u_texture = gl.getUniformLocation( + program, goog.DEBUG ? 'u_texture' : 'h'); + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'b'); -/** - * Calculates the drag position. - * - * @param {number} dx The horizontal movement delta. - * @param {number} dy The vertical movement delta. - * @return {!goog.math.Coordinate} The newly calculated drag element position. - * @private - */ -goog.fx.Dragger.prototype.calculatePosition_ = function(dx, dy) { - // Update the position for any change in body scrolling - var pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll(); - dx += pageScroll.x - this.pageScroll.x; - dy += pageScroll.y - this.pageScroll.y; - this.pageScroll = pageScroll; + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, goog.DEBUG ? 'a_texCoord' : 'c'); +}; - this.deltaX += dx; - this.deltaY += dy; +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.map.shader.Default'); + +goog.require('ol.webgl.shader'); - var x = this.limitX(this.deltaX); - var y = this.limitY(this.deltaY); - return new goog.math.Coordinate(x, y); -}; /** - * Event handler for scroll target scrolling. - * @param {goog.events.BrowserEvent} e The event. - * @private + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct */ -goog.fx.Dragger.prototype.onScroll_ = function(e) { - var pos = this.calculatePosition_(0, 0); - e.clientX = this.clientX; - e.clientY = this.clientY; - this.doDrag(e, pos.x, pos.y, true); +ol.renderer.webgl.map.shader.DefaultFragment = function() { + goog.base(this, ol.renderer.webgl.map.shader.DefaultFragment.SOURCE); }; +goog.inherits(ol.renderer.webgl.map.shader.DefaultFragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultFragment); /** - * @param {goog.events.BrowserEvent} e The closure object - * representing the browser event that caused a drag event. - * @param {number} x The new horizontal position for the drag element. - * @param {number} y The new vertical position for the drag element. - * @param {boolean} dragFromScroll Whether dragging was caused by scrolling - * the associated scroll target. - * @protected + * @const + * @type {string} */ -goog.fx.Dragger.prototype.doDrag = function(e, x, y, dragFromScroll) { - this.defaultAction(x, y); - this.dispatchEvent(new goog.fx.DragEvent( - goog.fx.Dragger.EventType.DRAG, this, e.clientX, e.clientY, e, x, y)); -}; +ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; /** - * Returns the 'real' x after limits are applied (allows for some - * limits to be undefined). - * @param {number} x X-coordinate to limit. - * @return {number} The 'real' X-coordinate after limits are applied. + * @const + * @type {string} */ -goog.fx.Dragger.prototype.limitX = function(x) { - var rect = this.limits; - var left = !isNaN(rect.left) ? rect.left : null; - var width = !isNaN(rect.width) ? rect.width : 0; - var maxX = left != null ? left + width : Infinity; - var minX = left != null ? left : -Infinity; - return Math.min(maxX, Math.max(minX, x)); -}; +ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}'; /** - * Returns the 'real' y after limits are applied (allows for some - * limits to be undefined). - * @param {number} y Y-coordinate to limit. - * @return {number} The 'real' Y-coordinate after limits are applied. + * @const + * @type {string} */ -goog.fx.Dragger.prototype.limitY = function(y) { - var rect = this.limits; - var top = !isNaN(rect.top) ? rect.top : null; - var height = !isNaN(rect.height) ? rect.height : 0; - var maxY = top != null ? top + height : Infinity; - var minY = top != null ? top : -Infinity; - return Math.min(maxY, Math.max(minY, y)); -}; +ol.renderer.webgl.map.shader.DefaultFragment.SOURCE = goog.DEBUG ? + ol.renderer.webgl.map.shader.DefaultFragment.DEBUG_SOURCE : + ol.renderer.webgl.map.shader.DefaultFragment.OPTIMIZED_SOURCE; + /** - * Overridable function for computing the initial position of the target - * before dragging begins. - * @protected + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct */ -goog.fx.Dragger.prototype.computeInitialPosition = function() { - this.deltaX = this.useRightPositioningForRtl_ ? - goog.style.bidi.getOffsetStart(this.target) : this.target.offsetLeft; - this.deltaY = this.target.offsetTop; +ol.renderer.webgl.map.shader.DefaultVertex = function() { + goog.base(this, ol.renderer.webgl.map.shader.DefaultVertex.SOURCE); }; +goog.inherits(ol.renderer.webgl.map.shader.DefaultVertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.renderer.webgl.map.shader.DefaultVertex); /** - * Overridable function for handling the default action of the drag behaviour. - * Normally this is simply moving the element to x,y though in some cases it - * might be used to resize the layer. This is basically a shortcut to - * implementing a default ondrag event handler. - * @param {number} x X-coordinate for target element. In right-to-left, x this - * is the number of pixels the target should be moved to from the right. - * @param {number} y Y-coordinate for target element. + * @const + * @type {string} + */ +ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; + + +/** + * @const + * @type {string} */ -goog.fx.Dragger.prototype.defaultAction = function(x, y) { - if (this.useRightPositioningForRtl_ && this.isRightToLeft_()) { - this.target.style.right = x + 'px'; - } else { - this.target.style.left = x + 'px'; - } - this.target.style.top = y + 'px'; -}; +ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; /** - * @return {boolean} Whether the dragger is currently in the midst of a drag. + * @const + * @type {string} */ -goog.fx.Dragger.prototype.isDragging = function() { - return this.dragging_; -}; +ol.renderer.webgl.map.shader.DefaultVertex.SOURCE = goog.DEBUG ? + ol.renderer.webgl.map.shader.DefaultVertex.DEBUG_SOURCE : + ol.renderer.webgl.map.shader.DefaultVertex.OPTIMIZED_SOURCE; /** - * Object representing a drag event - * @param {string} type Event type. - * @param {goog.fx.Dragger} dragobj Drag object initiating event. - * @param {number} clientX X-coordinate relative to the viewport. - * @param {number} clientY Y-coordinate relative to the viewport. - * @param {goog.events.BrowserEvent} browserEvent The closure object - * representing the browser event that caused this drag event. - * @param {number=} opt_actX Optional actual x for drag if it has been limited. - * @param {number=} opt_actY Optional actual y for drag if it has been limited. - * @param {boolean=} opt_dragCanceled Whether the drag has been canceled. * @constructor - * @extends {goog.events.Event} + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct */ -goog.fx.DragEvent = function(type, dragobj, clientX, clientY, browserEvent, - opt_actX, opt_actY, opt_dragCanceled) { - goog.events.Event.call(this, type); +ol.renderer.webgl.map.shader.Default.Locations = function(gl, program) { /** - * X-coordinate relative to the viewport - * @type {number} + * @type {WebGLUniformLocation} */ - this.clientX = clientX; + this.u_opacity = gl.getUniformLocation( + program, goog.DEBUG ? 'u_opacity' : 'f'); /** - * Y-coordinate relative to the viewport - * @type {number} + * @type {WebGLUniformLocation} */ - this.clientY = clientY; + this.u_projectionMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_projectionMatrix' : 'e'); /** - * The closure object representing the browser event that caused this drag - * event. - * @type {goog.events.BrowserEvent} + * @type {WebGLUniformLocation} */ - this.browserEvent = browserEvent; + this.u_texCoordMatrix = gl.getUniformLocation( + program, goog.DEBUG ? 'u_texCoordMatrix' : 'd'); /** - * The real x-position of the drag if it has been limited - * @type {number} + * @type {WebGLUniformLocation} */ - this.left = goog.isDef(opt_actX) ? opt_actX : dragobj.deltaX; + this.u_texture = gl.getUniformLocation( + program, goog.DEBUG ? 'u_texture' : 'g'); /** - * The real y-position of the drag if it has been limited * @type {number} */ - this.top = goog.isDef(opt_actY) ? opt_actY : dragobj.deltaY; - - /** - * Reference to the drag object for this event - * @type {goog.fx.Dragger} - */ - this.dragger = dragobj; + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'b'); /** - * Whether drag was canceled with this event. Used to differentiate between - * a legitimate drag END that can result in an action and a drag END which is - * a result of a drag cancelation. For now it can happen 1) with drag END - * event on FireFox when user drags the mouse out of the window, 2) with - * drag END event on IE7 which is generated on MOUSEMOVE event when user - * moves the mouse into the document after the mouse button has been - * released, 3) when TOUCHCANCEL is raised instead of TOUCHEND (on touch - * events). - * @type {boolean} + * @type {number} */ - this.dragCanceled = !!opt_dragCanceled; + this.a_texCoord = gl.getAttribLocation( + program, goog.DEBUG ? 'a_texCoord' : 'c'); }; -goog.inherits(goog.fx.DragEvent, goog.events.Event); -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +goog.provide('ol.renderer.webgl.Layer'); + +goog.require('goog.vec.Mat4'); +goog.require('goog.webgl'); +goog.require('ol.color.Matrix'); +goog.require('ol.layer.Layer'); +goog.require('ol.render.Event'); +goog.require('ol.render.EventType'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.renderer.Layer'); +goog.require('ol.renderer.webgl.map.shader.Color'); +goog.require('ol.renderer.webgl.map.shader.Default'); +goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); + + /** - * @fileoverview Abstract Base Class for Drag and Drop. - * - * Provides functionality for implementing drag and drop classes. Also provides - * support classes and events. - * - * @author eae@google.com (Emil A Eklund) + * @constructor + * @extends {ol.renderer.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Layer} layer Layer. */ +ol.renderer.webgl.Layer = function(mapRenderer, layer) { -goog.provide('goog.fx.AbstractDragDrop'); -goog.provide('goog.fx.AbstractDragDrop.EventType'); -goog.provide('goog.fx.DragDropEvent'); -goog.provide('goog.fx.DragDropItem'); + goog.base(this, layer); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.classlist'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventHandler'); -goog.require('goog.events.EventTarget'); -goog.require('goog.events.EventType'); -goog.require('goog.fx.Dragger'); -goog.require('goog.math.Box'); -goog.require('goog.math.Coordinate'); -goog.require('goog.style'); + /** + * @protected + * @type {ol.renderer.webgl.Map} + */ + this.mapRenderer = mapRenderer; + /** + * @private + * @type {ol.webgl.Buffer} + */ + this.arrayBuffer_ = new ol.webgl.Buffer([ + -1, -1, 0, 0, + 1, -1, 1, 0, + -1, 1, 0, 1, + 1, 1, 1, 1 + ]); + /** + * @protected + * @type {WebGLTexture} + */ + this.texture = null; -/** - * Abstract class that provides reusable functionality for implementing drag - * and drop functionality. - * - * This class also allows clients to define their own subtargeting function - * so that drop areas can have finer granularity then a singe element. This is - * accomplished by using a client provided function to map from element and - * coordinates to a subregion id. - * - * This class can also be made aware of scrollable containers that contain - * drop targets by calling addScrollableContainer. This will cause dnd to - * take changing scroll positions into account while a drag is occuring. - * - * @extends {goog.events.EventTarget} - * @constructor - */ -goog.fx.AbstractDragDrop = function() { - goog.fx.AbstractDragDrop.base(this, 'constructor'); + /** + * @protected + * @type {WebGLFramebuffer} + */ + this.framebuffer = null; + + /** + * @protected + * @type {number|undefined} + */ + this.framebufferDimension = undefined; + + /** + * @protected + * @type {!goog.vec.Mat4.Number} + */ + this.texCoordMatrix = goog.vec.Mat4.createNumber(); /** - * List of items that makes up the drag source or drop target. - * @type {Array<goog.fx.DragDropItem>} * @protected - * @suppress {underscore|visibility} + * @type {!goog.vec.Mat4.Number} + */ + this.projectionMatrix = goog.vec.Mat4.createNumberIdentity(); + + /** + * @private + * @type {ol.color.Matrix} */ - this.items_ = []; + this.colorMatrix_ = new ol.color.Matrix(); /** - * List of associated drop targets. - * @type {Array<goog.fx.AbstractDragDrop>} * @private + * @type {ol.renderer.webgl.map.shader.Color.Locations} */ - this.targets_ = []; + this.colorLocations_ = null; /** - * Scrollable containers to account for during drag - * @type {Array<goog.fx.ScrollableContainer_>} * @private + * @type {ol.renderer.webgl.map.shader.Default.Locations} */ - this.scrollableContainers_ = []; + this.defaultLocations_ = null; }; -goog.inherits(goog.fx.AbstractDragDrop, goog.events.EventTarget); +goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); /** - * Minimum size (in pixels) for a dummy target. If the box for the target is - * less than the specified size it's not created. - * @type {number} - * @private + * @param {olx.FrameState} frameState Frame state. + * @param {number} framebufferDimension Framebuffer dimension. + * @protected */ -goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ = 10; +ol.renderer.webgl.Layer.prototype.bindFramebuffer = + function(frameState, framebufferDimension) { + var gl = this.mapRenderer.getGL(); -/** - * Flag indicating if it's a drag source, set by addTarget. - * @type {boolean} - * @private - */ -goog.fx.AbstractDragDrop.prototype.isSource_ = false; + if (!goog.isDef(this.framebufferDimension) || + this.framebufferDimension != framebufferDimension) { + frameState.postRenderFunctions.push( + goog.partial( + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLFramebuffer} framebuffer Framebuffer. + * @param {WebGLTexture} texture Texture. + */ + function(gl, framebuffer, texture) { + if (!gl.isContextLost()) { + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + } + }, gl, this.framebuffer, this.texture)); -/** - * Flag indicating if it's a drop target, set when added as target to another - * DragDrop object. - * @type {boolean} - * @private - */ -goog.fx.AbstractDragDrop.prototype.isTarget_ = false; + var texture = ol.webgl.Context.createEmptyTexture( + gl, framebufferDimension, framebufferDimension); + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER, + goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0); -/** - * Subtargeting function accepting args: - * (goog.fx.DragDropItem, goog.math.Box, number, number) - * @type {Function} - * @private - */ -goog.fx.AbstractDragDrop.prototype.subtargetFunction_; + this.texture = texture; + this.framebuffer = framebuffer; + this.framebufferDimension = framebufferDimension; + + } else { + gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer); + } + +}; /** - * Last active subtarget. - * @type {Object} - * @private + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. */ -goog.fx.AbstractDragDrop.prototype.activeSubtarget_; +ol.renderer.webgl.Layer.prototype.composeFrame = + function(frameState, layerState, context) { + + this.dispatchComposeEvent_( + ol.render.EventType.PRECOMPOSE, context, frameState); + + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_); + + var gl = context.getGL(); + + var useColor = + layerState.brightness || + layerState.contrast != 1 || + layerState.hue || + layerState.saturation != 1; + + var fragmentShader, vertexShader; + if (useColor) { + fragmentShader = ol.renderer.webgl.map.shader.ColorFragment.getInstance(); + vertexShader = ol.renderer.webgl.map.shader.ColorVertex.getInstance(); + } else { + fragmentShader = + ol.renderer.webgl.map.shader.DefaultFragment.getInstance(); + vertexShader = ol.renderer.webgl.map.shader.DefaultVertex.getInstance(); + } + + var program = context.getProgram(fragmentShader, vertexShader); + + // FIXME colorLocations_ and defaultLocations_ should be shared somehow + var locations; + if (useColor) { + if (goog.isNull(this.colorLocations_)) { + locations = + new ol.renderer.webgl.map.shader.Color.Locations(gl, program); + this.colorLocations_ = locations; + } else { + locations = this.colorLocations_; + } + } else { + if (goog.isNull(this.defaultLocations_)) { + locations = + new ol.renderer.webgl.map.shader.Default.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + } + + if (context.useProgram(program)) { + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer( + locations.a_position, 2, goog.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer( + locations.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8); + gl.uniform1i(locations.u_texture, 0); + } + + gl.uniformMatrix4fv( + locations.u_texCoordMatrix, false, this.getTexCoordMatrix()); + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + this.getProjectionMatrix()); + if (useColor) { + gl.uniformMatrix4fv(locations.u_colorMatrix, false, + this.colorMatrix_.getMatrix( + layerState.brightness, + layerState.contrast, + layerState.hue, + layerState.saturation + )); + } + gl.uniform1f(locations.u_opacity, layerState.opacity); + gl.bindTexture(goog.webgl.TEXTURE_2D, this.getTexture()); + gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); + + this.dispatchComposeEvent_( + ol.render.EventType.POSTCOMPOSE, context, frameState); + +}; /** - * Class name to add to source elements being dragged. Set by setDragClass. - * @type {?string} + * @param {ol.render.EventType} type Event type. + * @param {ol.webgl.Context} context WebGL context. + * @param {olx.FrameState} frameState Frame state. * @private */ -goog.fx.AbstractDragDrop.prototype.dragClass_; +ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = + function(type, context, frameState) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var viewState = frameState.viewState; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var extent = frameState.extent; + var center = viewState.center; + var rotation = viewState.rotation; + var size = frameState.size; + + var render = new ol.render.webgl.Immediate( + context, center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event( + type, layer, render, frameState, null, context); + layer.dispatchEvent(composeEvent); + } +}; /** - * Class name to add to source elements. Set by setSourceClass. - * @type {?string} - * @private + * @return {!goog.vec.Mat4.Number} Matrix. */ -goog.fx.AbstractDragDrop.prototype.sourceClass_; +ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix; +}; /** - * Class name to add to target elements. Set by setTargetClass. - * @type {?string} - * @private + * @return {WebGLTexture} Texture. */ -goog.fx.AbstractDragDrop.prototype.targetClass_; +ol.renderer.webgl.Layer.prototype.getTexture = function() { + return this.texture; +}; /** - * The SCROLL event target used to make drag element follow scrolling. - * @type {EventTarget} - * @private + * @return {!goog.vec.Mat4.Number} Matrix. */ -goog.fx.AbstractDragDrop.prototype.scrollTarget_; +ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { + return this.projectionMatrix; +}; /** - * Dummy target, {@see maybeCreateDummyTargetForPosition_}. - * @type {goog.fx.ActiveDropTarget_} - * @private + * Handle webglcontextlost. */ -goog.fx.AbstractDragDrop.prototype.dummyTarget_; +ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { + this.texture = null; + this.framebuffer = null; + this.framebufferDimension = undefined; +}; /** - * Whether the object has been initialized. - * @type {boolean} - * @private + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + * @return {boolean} whether composeFrame should be called. */ -goog.fx.AbstractDragDrop.prototype.initialized_ = false; +ol.renderer.webgl.Layer.prototype.prepareFrame = goog.abstractMethod; + +goog.provide('ol.renderer.webgl.ImageLayer'); + +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('goog.vec.Mat4'); +goog.require('goog.webgl'); +goog.require('ol.Coordinate'); +goog.require('ol.Extent'); +goog.require('ol.ImageBase'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.extent'); +goog.require('ol.layer.Image'); +goog.require('ol.proj'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.source.ImageVector'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl.Context'); + /** - * Constants for event names - * @const + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Image} imageLayer Tile layer. */ -goog.fx.AbstractDragDrop.EventType = { - DRAGOVER: 'dragover', - DRAGOUT: 'dragout', - DRAG: 'drag', - DROP: 'drop', - DRAGSTART: 'dragstart', - DRAGEND: 'dragend' +ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + + goog.base(this, mapRenderer, imageLayer); + + /** + * The last rendered image. + * @private + * @type {?ol.ImageBase} + */ + this.image_ = null; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.hitTransformationMatrix_ = null; + }; +goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); /** - * Constant for distance threshold, in pixels, an element has to be moved to - * initiate a drag operation. - * @type {number} + * @param {ol.ImageBase} image Image. + * @private + * @return {WebGLTexture} Texture. */ -goog.fx.AbstractDragDrop.initDragDistanceThreshold = 5; +ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { + + // We meet the conditions to work with non-power of two textures. + // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support + // http://learningwebgl.com/blog/?p=2101 + + var imageElement = image.getImage(); + var gl = this.mapRenderer.getGL(); + + return ol.webgl.Context.createTexture( + gl, imageElement, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE); +}; /** - * Set class to add to source elements being dragged. - * - * @param {string} className Class to be added. Must be a single, valid - * classname. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.setDragClass = function(className) { - this.dragClass_ = className; +ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, skippedFeatureUids, + + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); }; /** - * Set class to add to source elements. - * - * @param {string} className Class to be added. Must be a single, valid - * classname. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.setSourceClass = function(className) { - this.sourceClass_ = className; -}; +ol.renderer.webgl.ImageLayer.prototype.prepareFrame = + function(frameState, layerState, context) { + + var gl = this.mapRenderer.getGL(); + + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; + + var image = this.image_; + var texture = this.texture; + var imageLayer = this.getLayer(); + goog.asserts.assertInstanceof(imageLayer, ol.layer.Image, + 'layer is an instance of ol.layer.Image'); + var imageSource = imageLayer.getSource(); + + var hints = frameState.viewHints; + + var renderedExtent = frameState.extent; + if (goog.isDef(layerState.extent)) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + var sourceProjection = imageSource.getProjection(); + if (!goog.isNull(sourceProjection)) { + goog.asserts.assert(ol.proj.equivalent(projection, sourceProjection), + 'projection and sourceProjection are equivalent'); + projection = sourceProjection; + } + var image_ = imageSource.getImage(renderedExtent, viewResolution, + pixelRatio, projection); + if (!goog.isNull(image_)) { + var loaded = this.loadImage(image_); + if (loaded) { + image = image_; + texture = this.createTexture_(image_); + if (!goog.isNull(this.texture)) { + frameState.postRenderFunctions.push( + goog.partial( + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLTexture} texture Texture. + */ + function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }, gl, this.texture)); + } + } + } + } + + if (!goog.isNull(image)) { + goog.asserts.assert(!goog.isNull(texture), 'texture is not null'); + + var canvas = this.mapRenderer.getContext().getCanvas(); + this.updateProjectionMatrix_(canvas.width, canvas.height, + pixelRatio, viewCenter, viewResolution, viewRotation, + image.getExtent()); + this.hitTransformationMatrix_ = null; -/** - * Set class to add to target elements. - * - * @param {string} className Class to be added. Must be a single, valid - * classname. - */ -goog.fx.AbstractDragDrop.prototype.setTargetClass = function(className) { - this.targetClass_ = className; -}; + // Translate and scale to flip the Y coord. + var texCoordMatrix = this.texCoordMatrix; + goog.vec.Mat4.makeIdentity(texCoordMatrix); + goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1); + goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0); + this.image_ = image; + this.texture = texture; -/** - * Whether the control has been initialized. - * - * @return {boolean} True if it's been initialized. - */ -goog.fx.AbstractDragDrop.prototype.isInitialized = function() { - return this.initialized_; + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } + + return true; }; /** - * Add item to drag object. - * - * @param {Element|string} element Dom Node, or string representation of node - * id, to be used as drag source/drop target. - * @throws Error Thrown if called on instance of abstract class + * @param {number} canvasWidth Canvas width. + * @param {number} canvasHeight Canvas height. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Coordinate} viewCenter View center. + * @param {number} viewResolution View resolution. + * @param {number} viewRotation View rotation. + * @param {ol.Extent} imageExtent Image extent. + * @private */ -goog.fx.AbstractDragDrop.prototype.addItem = goog.abstractMethod; +ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = + function(canvasWidth, canvasHeight, pixelRatio, + viewCenter, viewResolution, viewRotation, imageExtent) { + + var canvasExtentWidth = canvasWidth * viewResolution; + var canvasExtentHeight = canvasHeight * viewResolution; + var projectionMatrix = this.projectionMatrix; + goog.vec.Mat4.makeIdentity(projectionMatrix); + goog.vec.Mat4.scale(projectionMatrix, + pixelRatio * 2 / canvasExtentWidth, + pixelRatio * 2 / canvasExtentHeight, 1); + goog.vec.Mat4.rotateZ(projectionMatrix, -viewRotation); + goog.vec.Mat4.translate(projectionMatrix, + imageExtent[0] - viewCenter[0], + imageExtent[1] - viewCenter[1], + 0); + goog.vec.Mat4.scale(projectionMatrix, + (imageExtent[2] - imageExtent[0]) / 2, + (imageExtent[3] - imageExtent[1]) / 2, + 1); + goog.vec.Mat4.translate(projectionMatrix, 1, 1, 0); -/** - * Associate drop target with drag element. - * - * @param {goog.fx.AbstractDragDrop} target Target to add. - */ -goog.fx.AbstractDragDrop.prototype.addTarget = function(target) { - this.targets_.push(target); - target.isTarget_ = true; - this.isSource_ = true; }; /** - * Sets the SCROLL event target to make drag element follow scrolling. - * - * @param {EventTarget} scrollTarget The element that dispatches SCROLL events. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.setScrollTarget = function(scrollTarget) { - this.scrollTarget_ = scrollTarget; +ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = + function(coordinate, frameState) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + return goog.isDef(hasFeature); }; /** - * Initialize drag and drop functionality for sources/targets already added. - * Sources/targets added after init has been called will initialize themselves - * one by one. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.init = function() { - if (this.initialized_) { - return; - } - for (var item, i = 0; item = this.items_[i]; i++) { - this.initItem(item); +ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.image_) || goog.isNull(this.image_.getImage())) { + return undefined; } - this.initialized_ = true; -}; + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = pixel.slice(); + ol.vec.Mat4.multVec2( + frameState.pixelToCoordinateMatrix, coordinate, coordinate); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } else { + var imageSize = + [this.image_.getImage().width, this.image_.getImage().height]; -/** - * Initializes a single item. - * - * @param {goog.fx.DragDropItem} item Item to initialize. - * @protected - */ -goog.fx.AbstractDragDrop.prototype.initItem = function(item) { - if (this.isSource_) { - goog.events.listen(item.element, goog.events.EventType.MOUSEDOWN, - item.mouseDown_, false, item); - if (this.sourceClass_) { - goog.dom.classlist.add( - goog.asserts.assert(item.element), this.sourceClass_); + if (goog.isNull(this.hitTransformationMatrix_)) { + this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( + frameState.size, imageSize); } - } - if (this.isTarget_ && this.targetClass_) { - goog.dom.classlist.add( - goog.asserts.assert(item.element), this.targetClass_); - } -}; + var pixelOnFrameBuffer = [0, 0]; + ol.vec.Mat4.multVec2( + this.hitTransformationMatrix_, pixel, pixelOnFrameBuffer); + if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || + pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { + // outside the image, no need to check + return undefined; + } -/** - * Called when removing an item. Removes event listeners and classes. - * - * @param {goog.fx.DragDropItem} item Item to dispose. - * @protected - */ -goog.fx.AbstractDragDrop.prototype.disposeItem = function(item) { - if (this.isSource_) { - goog.events.unlisten(item.element, goog.events.EventType.MOUSEDOWN, - item.mouseDown_, false, item); - if (this.sourceClass_) { - goog.dom.classlist.remove( - goog.asserts.assert(item.element), this.sourceClass_); + if (goog.isNull(this.hitCanvasContext_)) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); } - } - if (this.isTarget_ && this.targetClass_) { - goog.dom.classlist.remove( - goog.asserts.assert(item.element), this.targetClass_); - } - item.dispose(); -}; + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.image_.getImage(), + pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); -/** - * Removes all items. - */ -goog.fx.AbstractDragDrop.prototype.removeItems = function() { - for (var item, i = 0; item = this.items_[i]; i++) { - this.disposeItem(item); + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } } - this.items_.length = 0; -}; - - -/** - * Starts a drag event for an item if the mouse button stays pressed and the - * cursor moves a few pixels. Allows dragging of items without first having to - * register them with addItem. - * - * @param {goog.events.BrowserEvent} event Mouse down event. - * @param {goog.fx.DragDropItem} item Item that's being dragged. - */ -goog.fx.AbstractDragDrop.prototype.maybeStartDrag = function(event, item) { - item.maybeStartDrag_(event, item.element); }; /** - * Event handler that's used to start drag. - * - * @param {goog.events.BrowserEvent} event Mouse move event. - * @param {goog.fx.DragDropItem} item Item that's being dragged. + * The transformation matrix to get the pixel on the image for a + * pixel on the map. + * @param {ol.Size} mapSize + * @param {ol.Size} imageSize + * @return {goog.vec.Mat4.Number} + * @private */ -goog.fx.AbstractDragDrop.prototype.startDrag = function(event, item) { - - // Prevent a new drag operation from being started if another one is already - // in progress (could happen if the mouse was released outside of the - // document). - if (this.dragItem_) { - return; - } - - this.dragItem_ = item; - - // Dispatch DRAGSTART event - var dragStartEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAGSTART, this, this.dragItem_); - if (this.dispatchEvent(dragStartEvent) == false) { - this.dragItem_ = null; - return; - } +ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = + function(mapSize, imageSize) { + // the first matrix takes a map pixel, flips the y-axis and scales to + // a range between -1 ... 1 + var mapCoordMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(mapCoordMatrix); + goog.vec.Mat4.translate(mapCoordMatrix, -1, -1, 0); + goog.vec.Mat4.scale(mapCoordMatrix, 2 / mapSize[0], 2 / mapSize[1], 1); + goog.vec.Mat4.translate(mapCoordMatrix, 0, mapSize[1], 0); + goog.vec.Mat4.scale(mapCoordMatrix, 1, -1, 1); - // Get the source element and create a drag element for it. - var el = item.getCurrentDragElement(); - this.dragEl_ = this.createDragElement(el); - var doc = goog.dom.getOwnerDocument(el); - doc.body.appendChild(this.dragEl_); + // the second matrix is the inverse of the projection matrix used in the + // shader for drawing + var projectionMatrixInv = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.projectionMatrix, projectionMatrixInv); - this.dragger_ = this.createDraggerFor(el, this.dragEl_, event); - this.dragger_.setScrollTarget(this.scrollTarget_); + // the third matrix scales to the image dimensions and flips the y-axis again + var imageCoordMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(imageCoordMatrix); + goog.vec.Mat4.translate(imageCoordMatrix, 0, imageSize[1], 0); + goog.vec.Mat4.scale(imageCoordMatrix, 1, -1, 1); + goog.vec.Mat4.scale(imageCoordMatrix, imageSize[0] / 2, imageSize[1] / 2, 1); + goog.vec.Mat4.translate(imageCoordMatrix, 1, 1, 0); - goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG, - this.moveDrag_, false, this); + var transformMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.multMat( + imageCoordMatrix, projectionMatrixInv, transformMatrix); + goog.vec.Mat4.multMat( + transformMatrix, mapCoordMatrix, transformMatrix); - goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END, - this.endDrag, false, this); + return transformMatrix; +}; - // IE may issue a 'selectstart' event when dragging over an iframe even when - // default mousemove behavior is suppressed. If the default selectstart - // behavior is not suppressed, elements dragged over will show as selected. - goog.events.listen(doc.body, goog.events.EventType.SELECTSTART, - this.suppressSelect_); +// This file is automatically generated, do not edit +goog.provide('ol.renderer.webgl.tilelayer.shader'); - this.recalculateDragTargets(); - this.recalculateScrollableContainers(); - this.activeTarget_ = null; - this.initScrollableContainerListeners_(); - this.dragger_.startDrag(event); +goog.require('ol.webgl.shader'); - event.preventDefault(); -}; /** - * Recalculates the geometry of this source's drag targets. Call this - * if the position or visibility of a drag target has changed during - * a drag, or if targets are added or removed. - * - * TODO(user): this is an expensive operation; more efficient APIs - * may be necessary. + * @constructor + * @extends {ol.webgl.shader.Fragment} + * @struct */ -goog.fx.AbstractDragDrop.prototype.recalculateDragTargets = function() { - this.targetList_ = []; - for (var target, i = 0; target = this.targets_[i]; i++) { - for (var itm, j = 0; itm = target.items_[j]; j++) { - this.addDragTarget_(target, itm); - } - } - if (!this.targetBox_) { - this.targetBox_ = new goog.math.Box(0, 0, 0, 0); - } +ol.renderer.webgl.tilelayer.shader.Fragment = function() { + goog.base(this, ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE); }; +goog.inherits(ol.renderer.webgl.tilelayer.shader.Fragment, ol.webgl.shader.Fragment); +goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Fragment); /** - * Recalculates the current scroll positions of scrollable containers and - * allocates targets. Call this if the position of a container changed or if - * targets are added or removed. + * @const + * @type {string} */ -goog.fx.AbstractDragDrop.prototype.recalculateScrollableContainers = - function() { - var container, i, j, target; - for (i = 0; container = this.scrollableContainers_[i]; i++) { - container.containedTargets_ = []; - container.savedScrollLeft_ = container.element_.scrollLeft; - container.savedScrollTop_ = container.element_.scrollTop; - var pos = goog.style.getPageOffset(container.element_); - var size = goog.style.getSize(container.element_); - container.box_ = new goog.math.Box(pos.y, pos.x + size.width, - pos.y + size.height, pos.x); - } - - for (i = 0; target = this.targetList_[i]; i++) { - for (j = 0; container = this.scrollableContainers_[j]; j++) { - if (goog.dom.contains(container.element_, target.element_)) { - container.containedTargets_.push(target); - target.scrollableContainer_ = container; - } - } - } -}; +ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n'; /** - * Creates the Dragger for the drag element. - * @param {Element} sourceEl Drag source element. - * @param {Element} el the element created by createDragElement(). - * @param {goog.events.BrowserEvent} event Mouse down event for start of drag. - * @return {!goog.fx.Dragger} The new Dragger. - * @protected + * @const + * @type {string} */ -goog.fx.AbstractDragDrop.prototype.createDraggerFor = - function(sourceEl, el, event) { - // Position the drag element. - var pos = this.getDragElementPosition(sourceEl, el, event); - el.style.position = 'absolute'; - el.style.left = pos.x + 'px'; - el.style.top = pos.y + 'px'; - return new goog.fx.Dragger(el); -}; +ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}'; /** - * Event handler that's used to stop drag. Fires a drop event if over a valid - * target. - * - * @param {goog.fx.DragEvent} event Drag event. + * @const + * @type {string} */ -goog.fx.AbstractDragDrop.prototype.endDrag = function(event) { - var activeTarget = event.dragCanceled ? null : this.activeTarget_; - if (activeTarget && activeTarget.target_) { - var clientX = event.clientX; - var clientY = event.clientY; - var scroll = this.getScrollPos(); - var x = clientX + scroll.x; - var y = clientY + scroll.y; - - var subtarget; - // If a subtargeting function is enabled get the current subtarget - if (this.subtargetFunction_) { - subtarget = this.subtargetFunction_(activeTarget.item_, - activeTarget.box_, x, y); - } - - var dragEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_, - activeTarget.target_, activeTarget.item_, activeTarget.element_, - clientX, clientY, x, y); - this.dispatchEvent(dragEvent); +ol.renderer.webgl.tilelayer.shader.Fragment.SOURCE = goog.DEBUG ? + ol.renderer.webgl.tilelayer.shader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.tilelayer.shader.Fragment.OPTIMIZED_SOURCE; - var dropEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DROP, this, this.dragItem_, - activeTarget.target_, activeTarget.item_, activeTarget.element_, - clientX, clientY, x, y, subtarget); - activeTarget.target_.dispatchEvent(dropEvent); - } - var dragEndEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAGEND, this, this.dragItem_); - this.dispatchEvent(dragEndEvent); - goog.events.unlisten(this.dragger_, goog.fx.Dragger.EventType.DRAG, - this.moveDrag_, false, this); - goog.events.unlisten(this.dragger_, goog.fx.Dragger.EventType.END, - this.endDrag, false, this); - var doc = goog.dom.getOwnerDocument(this.dragItem_.getCurrentDragElement()); - goog.events.unlisten(doc.body, goog.events.EventType.SELECTSTART, - this.suppressSelect_); +/** + * @constructor + * @extends {ol.webgl.shader.Vertex} + * @struct + */ +ol.renderer.webgl.tilelayer.shader.Vertex = function() { + goog.base(this, ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE); +}; +goog.inherits(ol.renderer.webgl.tilelayer.shader.Vertex, ol.webgl.shader.Vertex); +goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Vertex); - this.afterEndDrag(this.activeTarget_ ? this.activeTarget_.item_ : null); -}; +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n v_texCoord = a_texCoord;\n}\n\n\n'; /** - * Called after a drag operation has finished. - * - * @param {goog.fx.DragDropItem=} opt_dropTarget Target for successful drop. - * @protected + * @const + * @type {string} */ -goog.fx.AbstractDragDrop.prototype.afterEndDrag = function(opt_dropTarget) { - this.disposeDrag(); -}; +ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}'; /** - * Called once a drag operation has finished. Removes event listeners and - * elements. - * - * @protected + * @const + * @type {string} */ -goog.fx.AbstractDragDrop.prototype.disposeDrag = function() { - this.disposeScrollableContainerListeners_(); - this.dragger_.dispose(); +ol.renderer.webgl.tilelayer.shader.Vertex.SOURCE = goog.DEBUG ? + ol.renderer.webgl.tilelayer.shader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.tilelayer.shader.Vertex.OPTIMIZED_SOURCE; - goog.dom.removeNode(this.dragEl_); - delete this.dragItem_; - delete this.dragEl_; - delete this.dragger_; - delete this.targetList_; - delete this.activeTarget_; -}; /** - * Event handler for drag events. Determines the active drop target, if any, and - * fires dragover and dragout events appropriately. - * - * @param {goog.fx.DragEvent} event Drag event. - * @private + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct */ -goog.fx.AbstractDragDrop.prototype.moveDrag_ = function(event) { - var position = this.getEventPosition(event); - var x = position.x; - var y = position.y; +ol.renderer.webgl.tilelayer.shader.Locations = function(gl, program) { - // Check if we're still inside the bounds of the active target, if not fire - // a dragout event and proceed to find a new target. - var activeTarget = this.activeTarget_; + /** + * @type {WebGLUniformLocation} + */ + this.u_texture = gl.getUniformLocation( + program, goog.DEBUG ? 'u_texture' : 'e'); - var subtarget; - if (activeTarget) { - // If a subtargeting function is enabled get the current subtarget - if (this.subtargetFunction_ && activeTarget.target_) { - subtarget = this.subtargetFunction_(activeTarget.item_, - activeTarget.box_, x, y); - } + /** + * @type {WebGLUniformLocation} + */ + this.u_tileOffset = gl.getUniformLocation( + program, goog.DEBUG ? 'u_tileOffset' : 'd'); - if (activeTarget.box_.contains(position) && - subtarget == this.activeSubtarget_) { - return; - } + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, goog.DEBUG ? 'a_position' : 'b'); - if (activeTarget.target_) { - var sourceDragOutEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_, - activeTarget.target_, activeTarget.item_, activeTarget.element_); - this.dispatchEvent(sourceDragOutEvent); - - // The event should be dispatched the by target DragDrop so that the - // target DragDrop can manage these events without having to know what - // sources this is a target for. - var targetDragOutEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAGOUT, - this, - this.dragItem_, - activeTarget.target_, - activeTarget.item_, - activeTarget.element_, - undefined, - undefined, - undefined, - undefined, - this.activeSubtarget_); - activeTarget.target_.dispatchEvent(targetDragOutEvent); - } - this.activeSubtarget_ = subtarget; - this.activeTarget_ = null; - } - - // Check if inside target box - if (this.targetBox_.contains(position)) { - // Search for target and fire a dragover event if found - activeTarget = this.activeTarget_ = this.getTargetFromPosition_(position); - if (activeTarget && activeTarget.target_) { - // If a subtargeting function is enabled get the current subtarget - if (this.subtargetFunction_) { - subtarget = this.subtargetFunction_(activeTarget.item_, - activeTarget.box_, x, y); - } - var sourceDragOverEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_, - activeTarget.target_, activeTarget.item_, activeTarget.element_); - sourceDragOverEvent.subtarget = subtarget; - this.dispatchEvent(sourceDragOverEvent); + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, goog.DEBUG ? 'a_texCoord' : 'c'); +}; - // The event should be dispatched by the target DragDrop so that the - // target DragDrop can manage these events without having to know what - // sources this is a target for. - var targetDragOverEvent = new goog.fx.DragDropEvent( - goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_, - activeTarget.target_, activeTarget.item_, activeTarget.element_, - event.clientX, event.clientY, undefined, undefined, subtarget); - activeTarget.target_.dispatchEvent(targetDragOverEvent); +// FIXME large resolutions lead to too large framebuffers :-( +// FIXME animated shaders! check in redraw - } else if (!activeTarget) { - // If no target was found create a dummy one so we won't have to iterate - // over all possible targets for every move event. - this.activeTarget_ = this.maybeCreateDummyTargetForPosition_(x, y); - } - } -}; +goog.provide('ol.renderer.webgl.TileLayer'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('goog.vec.Mat4'); +goog.require('goog.vec.Vec4'); +goog.require('goog.webgl'); +goog.require('ol.TileRange'); +goog.require('ol.TileState'); +goog.require('ol.extent'); +goog.require('ol.layer.Tile'); +goog.require('ol.math'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.renderer.webgl.tilelayer.shader'); +goog.require('ol.size'); +goog.require('ol.tilecoord'); +goog.require('ol.vec.Mat4'); +goog.require('ol.webgl.Buffer'); -/** - * Event handler for suppressing selectstart events. Selecting should be - * disabled while dragging. - * - * @param {goog.events.Event} event The selectstart event to suppress. - * @return {boolean} Whether to perform default behavior. - * @private - */ -goog.fx.AbstractDragDrop.prototype.suppressSelect_ = function(event) { - return false; -}; /** - * Sets up listeners for the scrollable containers that keep track of their - * scroll positions. - * @private + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Tile} tileLayer Tile layer. */ -goog.fx.AbstractDragDrop.prototype.initScrollableContainerListeners_ = - function() { - var container, i; - for (i = 0; container = this.scrollableContainers_[i]; i++) { - goog.events.listen(container.element_, goog.events.EventType.SCROLL, - this.containerScrollHandler_, false, this); - } -}; +ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { + + goog.base(this, mapRenderer, tileLayer); + /** + * @private + * @type {ol.webgl.shader.Fragment} + */ + this.fragmentShader_ = + ol.renderer.webgl.tilelayer.shader.Fragment.getInstance(); -/** - * Cleans up the scrollable container listeners. - * @private - */ -goog.fx.AbstractDragDrop.prototype.disposeScrollableContainerListeners_ = - function() { - for (var i = 0, container; container = this.scrollableContainers_[i]; i++) { - goog.events.unlisten(container.element_, 'scroll', - this.containerScrollHandler_, false, this); - container.containedTargets_ = []; - } -}; + /** + * @private + * @type {ol.webgl.shader.Vertex} + */ + this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance(); + /** + * @private + * @type {ol.renderer.webgl.tilelayer.shader.Locations} + */ + this.locations_ = null; -/** - * Makes drag and drop aware of a target container that could scroll mid drag. - * @param {Element} element The scroll container. - */ -goog.fx.AbstractDragDrop.prototype.addScrollableContainer = function(element) { - this.scrollableContainers_.push(new goog.fx.ScrollableContainer_(element)); -}; + /** + * @private + * @type {ol.webgl.Buffer} + */ + this.renderArrayBuffer_ = new ol.webgl.Buffer([ + 0, 0, 0, 1, + 1, 0, 1, 1, + 0, 1, 0, 0, + 1, 1, 1, 0 + ]); + /** + * @private + * @type {ol.TileRange} + */ + this.renderedTileRange_ = null; -/** - * Removes all scrollable containers. - */ -goog.fx.AbstractDragDrop.prototype.removeAllScrollableContainers = function() { - this.disposeScrollableContainerListeners_(); - this.scrollableContainers_ = []; -}; + /** + * @private + * @type {ol.Extent} + */ + this.renderedFramebufferExtent_ = null; + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; -/** - * Event handler for containers scrolling. - * @param {goog.events.Event} e The event. - * @private - */ -goog.fx.AbstractDragDrop.prototype.containerScrollHandler_ = function(e) { - for (var i = 0, container; container = this.scrollableContainers_[i]; i++) { - if (e.target == container.element_) { - var deltaTop = container.savedScrollTop_ - container.element_.scrollTop; - var deltaLeft = - container.savedScrollLeft_ - container.element_.scrollLeft; - container.savedScrollTop_ = container.element_.scrollTop; - container.savedScrollLeft_ = container.element_.scrollLeft; + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; - // When the container scrolls, it's possible that one of the targets will - // move to the region contained by the dummy target. Since we don't know - // which sides (if any) of the dummy target are defined by targets - // contained by this container, we are conservative and just shrink it. - if (this.dummyTarget_ && this.activeTarget_ == this.dummyTarget_) { - if (deltaTop > 0) { - this.dummyTarget_.box_.top += deltaTop; - } else { - this.dummyTarget_.box_.bottom += deltaTop; - } - if (deltaLeft > 0) { - this.dummyTarget_.box_.left += deltaLeft; - } else { - this.dummyTarget_.box_.right += deltaLeft; - } - } - for (var j = 0, target; target = container.containedTargets_[j]; j++) { - var box = target.box_; - box.top += deltaTop; - box.left += deltaLeft; - box.bottom += deltaTop; - box.right += deltaLeft; - - this.calculateTargetBox_(box); - } - } - } - this.dragger_.onScroll_(e); }; +goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); /** - * Set a function that provides subtargets. A subtargeting function - * returns an arbitrary identifier for each subtarget of an element. - * DnD code will generate additional drag over / out events when - * switching from subtarget to subtarget. This is useful for instance - * if you are interested if you are on the top half or the bottom half - * of the element. - * The provided function will be given the DragDropItem, box, x, y - * box is the current window coordinates occupied by element - * x, y is the mouse position in window coordinates - * - * @param {Function} f The new subtarget function. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.setSubtargetFunction = function(f) { - this.subtargetFunction_ = f; +ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { + var context = this.mapRenderer.getContext(); + context.deleteBuffer(this.renderArrayBuffer_); + goog.base(this, 'disposeInternal'); }; /** - * Creates an element for the item being dragged. - * - * @param {Element} sourceEl Drag source element. - * @return {Element} The new drag element. + * Create a function that adds loaded tiles to the tile lookup. + * @param {ol.source.Tile} source Tile source. + * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded + * tiles by zoom level. + * @return {function(number, ol.TileRange):boolean} A function that can be + * called with a zoom level and a tile range to add loaded tiles to the + * lookup. + * @protected */ -goog.fx.AbstractDragDrop.prototype.createDragElement = function(sourceEl) { - var dragEl = this.createDragElementInternal(sourceEl); - goog.asserts.assert(dragEl); - if (this.dragClass_) { - goog.dom.classlist.add(dragEl, this.dragClass_); - } +ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = + function(source, tiles) { + var mapRenderer = this.mapRenderer; - return dragEl; + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + return source.forEachLoadedTile(zoom, tileRange, function(tile) { + var loaded = mapRenderer.isTileTextureLoaded(tile); + if (loaded) { + if (!tiles[zoom]) { + tiles[zoom] = {}; + } + tiles[zoom][tile.tileCoord.toString()] = tile; + } + return loaded; + }); + }); }; /** - * Returns the position for the drag element. - * - * @param {Element} el Drag source element. - * @param {Element} dragEl The dragged element created by createDragElement(). - * @param {goog.events.BrowserEvent} event Mouse down event for start of drag. - * @return {!goog.math.Coordinate} The position for the drag element. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.getDragElementPosition = - function(el, dragEl, event) { - var pos = goog.style.getPageOffset(el); - - // Subtract margin from drag element position twice, once to adjust the - // position given by the original node and once for the drag node. - var marginBox = goog.style.getMarginBox(el); - pos.x -= (marginBox.left || 0) * 2; - pos.y -= (marginBox.top || 0) * 2; - - return pos; +ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { + goog.base(this, 'handleWebGLContextLost'); + this.locations_ = null; }; /** - * Returns the dragger object. - * - * @return {goog.fx.Dragger} The dragger object used by this drag and drop - * instance. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.getDragger = function() { - return this.dragger_; -}; +ol.renderer.webgl.TileLayer.prototype.prepareFrame = + function(frameState, layerState, context) { + var mapRenderer = this.mapRenderer; + var gl = context.getGL(); -/** - * Creates copy of node being dragged. - * - * @param {Element} sourceEl Element to copy. - * @return {!Element} The clone of {@code sourceEl}. - * @deprecated Use goog.fx.Dragger.cloneNode(). - * @private - */ -goog.fx.AbstractDragDrop.prototype.cloneNode_ = function(sourceEl) { - return goog.fx.Dragger.cloneNode(sourceEl); -}; + var viewState = frameState.viewState; + var projection = viewState.projection; + + var tileLayer = this.getLayer(); + goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile, + 'layer is an instance of ol.layer.Tile'); + var tileSource = tileLayer.getSource(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var z = tileGrid.getZForResolution(viewState.resolution); + var tileResolution = tileGrid.getResolution(z); + var tilePixelSize = + tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); + var pixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; + var tilePixelResolution = tileResolution / pixelRatio; + var tileGutter = tileSource.getGutter(); -/** - * Generates an element to follow the cursor during dragging, given a drag - * source element. The default behavior is simply to clone the source element, - * but this may be overridden in subclasses. This method is called by - * {@code createDragElement()} before the drag class is added. - * - * @param {Element} sourceEl Drag source element. - * @return {!Element} The new drag element. - * @protected - * @suppress {deprecated} - */ -goog.fx.AbstractDragDrop.prototype.createDragElementInternal = - function(sourceEl) { - return this.cloneNode_(sourceEl); -}; + var center = viewState.center; + var extent; + if (tileResolution == viewState.resolution) { + center = this.snapCenterToPixel(center, tileResolution, frameState.size); + extent = ol.extent.getForViewAndSize( + center, tileResolution, viewState.rotation, frameState.size); + } else { + extent = frameState.extent; + } + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); + var framebufferExtent; + if (!goog.isNull(this.renderedTileRange_) && + this.renderedTileRange_.equals(tileRange) && + this.renderedRevision_ == tileSource.getRevision()) { + framebufferExtent = this.renderedFramebufferExtent_; + } else { -/** - * Add possible drop target for current drag operation. - * - * @param {goog.fx.AbstractDragDrop} target Drag handler. - * @param {goog.fx.DragDropItem} item Item that's being dragged. - * @private - */ -goog.fx.AbstractDragDrop.prototype.addDragTarget_ = function(target, item) { + var tileRangeSize = tileRange.getSize(); - // Get all the draggable elements and add each one. - var draggableElements = item.getDraggableElements(); - var targetList = this.targetList_; - for (var i = 0; i < draggableElements.length; i++) { - var draggableElement = draggableElements[i]; + var maxDimension = Math.max( + tileRangeSize[0] * tilePixelSize[0], + tileRangeSize[1] * tilePixelSize[1]); + var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); + var framebufferExtentDimension = tilePixelResolution * framebufferDimension; + var origin = tileGrid.getOrigin(z); + var minX = origin[0] + + tileRange.minX * tilePixelSize[0] * tilePixelResolution; + var minY = origin[1] + + tileRange.minY * tilePixelSize[1] * tilePixelResolution; + framebufferExtent = [ + minX, minY, + minX + framebufferExtentDimension, minY + framebufferExtentDimension + ]; - // Determine target position and dimension - var box = this.getElementBox(item, draggableElement); + this.bindFramebuffer(frameState, framebufferDimension); + gl.viewport(0, 0, framebufferDimension, framebufferDimension); - targetList.push( - new goog.fx.ActiveDropTarget_(box, target, item, draggableElement)); + gl.clearColor(0, 0, 0, 0); + gl.clear(goog.webgl.COLOR_BUFFER_BIT); + gl.disable(goog.webgl.BLEND); - this.calculateTargetBox_(box); - } -}; + var program = context.getProgram(this.fragmentShader_, this.vertexShader_); + context.useProgram(program); + if (goog.isNull(this.locations_)) { + this.locations_ = + new ol.renderer.webgl.tilelayer.shader.Locations(gl, program); + } + context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.renderArrayBuffer_); + gl.enableVertexAttribArray(this.locations_.a_position); + gl.vertexAttribPointer( + this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(this.locations_.a_texCoord); + gl.vertexAttribPointer( + this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8); + gl.uniform1i(this.locations_.u_texture, 0); -/** - * Calculates the position and dimension of a draggable element. - * - * @param {goog.fx.DragDropItem} item Item that's being dragged. - * @param {Element} element The element to calculate the box. - * - * @return {!goog.math.Box} Box describing the position and dimension - * of element. - * @protected - */ -goog.fx.AbstractDragDrop.prototype.getElementBox = function(item, element) { - var pos = goog.style.getPageOffset(element); - var size = goog.style.getSize(element); - return new goog.math.Box(pos.y, pos.x + size.width, pos.y + size.height, - pos.x); -}; + /** + * @type {Object.<number, Object.<string, ol.Tile>>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + var findLoadedTiles = this.createLoadedTileFinder( + tileSource, tilesToDrawByZ); + + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var allTilesLoaded = true; + var tmpExtent = ol.extent.createEmpty(); + var tmpTileRange = new ol.TileRange(0, 0, 0, 0); + var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (goog.isDef(layerState.extent)) { + // ignore tiles outside layer extent + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + if (!ol.extent.intersects(tileExtent, layerState.extent)) { + continue; + } + } + tileState = tile.getState(); + if (tileState == ol.TileState.LOADED) { + if (mapRenderer.isTileTextureLoaded(tile)) { + tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile; + continue; + } + } else if (tileState == ol.TileState.EMPTY || + (tileState == ol.TileState.ERROR && + !useInterimTilesOnError)) { + continue; + } + + allTilesLoaded = false; + fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (!goog.isNull(childTileRange)) { + findLoadedTiles(z + 1, childTileRange); + } + } -/** - * Calculate the outer bounds (the region all targets are inside). - * - * @param {goog.math.Box} box Box describing the position and dimension - * of a drag target. - * @private - */ -goog.fx.AbstractDragDrop.prototype.calculateTargetBox_ = function(box) { - if (this.targetList_.length == 1) { - this.targetBox_ = new goog.math.Box(box.top, box.right, - box.bottom, box.left); - } else { - var tb = this.targetBox_; - tb.left = Math.min(box.left, tb.left); - tb.right = Math.max(box.right, tb.right); - tb.top = Math.min(box.top, tb.top); - tb.bottom = Math.max(box.bottom, tb.bottom); - } -}; - - -/** - * Creates a dummy target for the given cursor position. The assumption is to - * create as big dummy target box as possible, the only constraints are: - * - The dummy target box cannot overlap any of real target boxes. - * - The dummy target has to contain a point with current mouse coordinates. - * - * NOTE: For performance reasons the box construction algorithm is kept simple - * and it is not optimal (see example below). Currently it is O(n) in regard to - * the number of real drop target boxes, but its result depends on the order - * of those boxes being processed (the order in which they're added to the - * targetList_ collection). - * - * The algorithm. - * a) Assumptions - * - Mouse pointer is in the bounding box of real target boxes. - * - None of the boxes have negative coordinate values. - * - Mouse pointer is not contained by any of "real target" boxes. - * - For targets inside a scrollable container, the box used is the - * intersection of the scrollable container's box and the target's box. - * This is because the part of the target that extends outside the scrollable - * container should not be used in the clipping calculations. - * - * b) Outline - * - Initialize the fake target to the bounding box of real targets. - * - For each real target box - clip the fake target box so it does not contain - * that target box, but does contain the mouse pointer. - * -- Project the real target box, mouse pointer and fake target box onto - * both axes and calculate the clipping coordinates. - * -- Only one coordinate is used to clip the fake target box to keep the - * fake target as big as possible. - * -- If the projection of the real target box contains the mouse pointer, - * clipping for a given axis is not possible. - * -- If both clippings are possible, the clipping more distant from the - * mouse pointer is selected to keep bigger fake target area. - * - Save the created fake target only if it has a big enough area. - * - * - * c) Example - * <pre> - * Input: Algorithm created box: Maximum box: - * +---------------------+ +---------------------+ +---------------------+ - * | B1 | B2 | | B1 B2 | | B1 B2 | - * | | | | +-------------+ | |+-------------------+| - * |---------x-----------| | | | | || || - * | | | | | | | || || - * | | | | | | | || || - * | | | | | | | || || - * | | | | | | | || || - * | | | | +-------------+ | |+-------------------+| - * | B4 | B3 | | B4 B3 | | B4 B3 | - * +---------------------+ +---------------------+ +---------------------+ - * </pre> - * - * @param {number} x Cursor position on the x-axis. - * @param {number} y Cursor position on the y-axis. - * @return {goog.fx.ActiveDropTarget_} Dummy drop target. - * @private - */ -goog.fx.AbstractDragDrop.prototype.maybeCreateDummyTargetForPosition_ = - function(x, y) { - if (!this.dummyTarget_) { - this.dummyTarget_ = new goog.fx.ActiveDropTarget_(this.targetBox_.clone()); - } - var fakeTargetBox = this.dummyTarget_.box_; - - // Initialize the fake target box to the bounding box of DnD targets. - fakeTargetBox.top = this.targetBox_.top; - fakeTargetBox.right = this.targetBox_.right; - fakeTargetBox.bottom = this.targetBox_.bottom; - fakeTargetBox.left = this.targetBox_.left; - - // Clip the fake target based on mouse position and DnD target boxes. - for (var i = 0, target; target = this.targetList_[i]; i++) { - var box = target.box_; - - if (target.scrollableContainer_) { - // If the target has a scrollable container, use the intersection of that - // container's box and the target's box. - var scrollBox = target.scrollableContainer_.box_; - - box = new goog.math.Box( - Math.max(box.top, scrollBox.top), - Math.min(box.right, scrollBox.right), - Math.min(box.bottom, scrollBox.bottom), - Math.max(box.left, scrollBox.left)); - } - - // Calculate clipping coordinates for horizontal and vertical axis. - // The clipping coordinate is calculated by projecting fake target box, - // the mouse pointer and DnD target box onto an axis and checking how - // box projections overlap and if the projected DnD target box contains - // mouse pointer. The clipping coordinate cannot be computed and is set to - // a negative value if the projected DnD target contains the mouse pointer. - - var horizontalClip = null; // Assume mouse is above or below the DnD box. - if (x >= box.right) { // Mouse is to the right of the DnD box. - // Clip the fake box only if the DnD box overlaps it. - horizontalClip = box.right > fakeTargetBox.left ? - box.right : fakeTargetBox.left; - } else if (x < box.left) { // Mouse is to the left of the DnD box. - // Clip the fake box only if the DnD box overlaps it. - horizontalClip = box.left < fakeTargetBox.right ? - box.left : fakeTargetBox.right; - } - var verticalClip = null; - if (y >= box.bottom) { - verticalClip = box.bottom > fakeTargetBox.top ? - box.bottom : fakeTargetBox.top; - } else if (y < box.top) { - verticalClip = box.top < fakeTargetBox.bottom ? - box.top : fakeTargetBox.bottom; - } - - // If both clippings are possible, choose one that gives us larger distance - // to mouse pointer (mark the shorter clipping as impossible, by setting it - // to null). - if (!goog.isNull(horizontalClip) && !goog.isNull(verticalClip)) { - if (Math.abs(horizontalClip - x) > Math.abs(verticalClip - y)) { - verticalClip = null; - } else { - horizontalClip = null; } + } - // Clip none or one of fake target box sides (at most one clipping - // coordinate can be active). - if (!goog.isNull(horizontalClip)) { - if (horizontalClip <= x) { - fakeTargetBox.left = horizontalClip; - } else { - fakeTargetBox.right = horizontalClip; - } - } else if (!goog.isNull(verticalClip)) { - if (verticalClip <= y) { - fakeTargetBox.top = verticalClip; - } else { - fakeTargetBox.bottom = verticalClip; + /** @type {Array.<number>} */ + var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); + goog.array.sort(zs); + var u_tileOffset = goog.vec.Vec4.createFloat32(); + var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty; + for (i = 0, ii = zs.length; i < ii; ++i) { + tilesToDraw = tilesToDrawByZ[zs[i]]; + for (tileKey in tilesToDraw) { + tile = tilesToDraw[tileKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + sx = 2 * (tileExtent[2] - tileExtent[0]) / + framebufferExtentDimension; + sy = 2 * (tileExtent[3] - tileExtent[1]) / + framebufferExtentDimension; + tx = 2 * (tileExtent[0] - framebufferExtent[0]) / + framebufferExtentDimension - 1; + ty = 2 * (tileExtent[1] - framebufferExtent[1]) / + framebufferExtentDimension - 1; + goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty); + gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); + mapRenderer.bindTileTexture(tile, tilePixelSize, + tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR); + gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); } } - } - // Only return the new fake target if it is big enough. - return (fakeTargetBox.right - fakeTargetBox.left) * - (fakeTargetBox.bottom - fakeTargetBox.top) >= - goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ ? - this.dummyTarget_ : null; -}; + if (allTilesLoaded) { + this.renderedTileRange_ = tileRange; + this.renderedFramebufferExtent_ = framebufferExtent; + this.renderedRevision_ = tileSource.getRevision(); + } else { + this.renderedTileRange_ = null; + this.renderedFramebufferExtent_ = null; + this.renderedRevision_ = -1; + frameState.animate = true; + } + } -/** - * Returns the target for a given cursor position. - * - * @param {goog.math.Coordinate} position Cursor position. - * @return {Object} Target for position or null if no target was defined - * for the given position. - * @private - */ -goog.fx.AbstractDragDrop.prototype.getTargetFromPosition_ = function(position) { - for (var target, i = 0; target = this.targetList_[i]; i++) { - if (target.box_.contains(position)) { - if (target.scrollableContainer_) { - // If we have a scrollable container we will need to make sure - // we account for clipping of the scroll area - var box = target.scrollableContainer_.box_; - if (box.contains(position)) { - return target; + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + var tileTextureQueue = mapRenderer.getTileTextureQueue(); + this.manageTilePyramid( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, + tileLayer.getPreload(), + /** + * @param {ol.Tile} tile Tile. + */ + function(tile) { + if (tile.getState() == ol.TileState.LOADED && + !mapRenderer.isTileTextureLoaded(tile) && + !tileTextureQueue.isKeyQueued(tile.getKey())) { + tileTextureQueue.enqueue([ + tile, + tileGrid.getTileCoordCenter(tile.tileCoord), + tileGrid.getResolution(tile.tileCoord[0]), + tilePixelSize, tileGutter * pixelRatio + ]); } - } else { - return target; - } - } + }, this); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + var texCoordMatrix = this.texCoordMatrix; + goog.vec.Mat4.makeIdentity(texCoordMatrix); + goog.vec.Mat4.translate(texCoordMatrix, + (center[0] - framebufferExtent[0]) / + (framebufferExtent[2] - framebufferExtent[0]), + (center[1] - framebufferExtent[1]) / + (framebufferExtent[3] - framebufferExtent[1]), + 0); + if (viewState.rotation !== 0) { + goog.vec.Mat4.rotateZ(texCoordMatrix, viewState.rotation); } + goog.vec.Mat4.scale(texCoordMatrix, + frameState.size[0] * viewState.resolution / + (framebufferExtent[2] - framebufferExtent[0]), + frameState.size[1] * viewState.resolution / + (framebufferExtent[3] - framebufferExtent[1]), + 1); + goog.vec.Mat4.translate(texCoordMatrix, + -0.5, + -0.5, + 0); - return null; + return true; }; /** - * Checks whatever a given point is inside a given box. - * - * @param {number} x Cursor position on the x-axis. - * @param {number} y Cursor position on the y-axis. - * @param {goog.math.Box} box Box to check position against. - * @return {boolean} Whether the given point is inside {@code box}. - * @protected - * @deprecated Use goog.math.Box.contains. + * @inheritDoc */ -goog.fx.AbstractDragDrop.prototype.isInside = function(x, y, box) { - return x >= box.left && - x < box.right && - y >= box.top && - y < box.bottom; -}; +ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.framebuffer)) { + return undefined; + } + var pixelOnMapScaled = [ + pixel[0] / frameState.size[0], + (frameState.size[1] - pixel[1]) / frameState.size[1]]; -/** - * Gets the scroll distance as a coordinate object, using - * the window of the current drag element's dom. - * @return {!goog.math.Coordinate} Object with scroll offsets 'x' and 'y'. - * @protected - */ -goog.fx.AbstractDragDrop.prototype.getScrollPos = function() { - return goog.dom.getDomHelper(this.dragEl_).getDocumentScroll(); -}; + var pixelOnFrameBufferScaled = [0, 0]; + ol.vec.Mat4.multVec2( + this.texCoordMatrix, pixelOnMapScaled, pixelOnFrameBufferScaled); + var pixelOnFrameBuffer = [ + pixelOnFrameBufferScaled[0] * this.framebufferDimension, + pixelOnFrameBufferScaled[1] * this.framebufferDimension]; + var gl = this.mapRenderer.getContext().getGL(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + var imageData = new Uint8Array(4); + gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, + gl.RGBA, gl.UNSIGNED_BYTE, imageData); -/** - * Get the position of a drag event. - * @param {goog.fx.DragEvent} event Drag event. - * @return {!goog.math.Coordinate} Position of the event. - * @protected - */ -goog.fx.AbstractDragDrop.prototype.getEventPosition = function(event) { - var scroll = this.getScrollPos(); - return new goog.math.Coordinate(event.clientX + scroll.x, - event.clientY + scroll.y); + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } }; +goog.provide('ol.renderer.webgl.VectorLayer'); -/** @override */ -goog.fx.AbstractDragDrop.prototype.disposeInternal = function() { - goog.fx.AbstractDragDrop.base(this, 'disposeInternal'); - this.removeItems(); -}; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('ol.ViewHint'); +goog.require('ol.extent'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.webgl.ReplayGroup'); +goog.require('ol.renderer.vector'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.vec.Mat4'); /** - * Object representing a drag and drop event. - * - * @param {string} type Event type. - * @param {goog.fx.AbstractDragDrop} source Source drag drop object. - * @param {goog.fx.DragDropItem} sourceItem Source item. - * @param {goog.fx.AbstractDragDrop=} opt_target Target drag drop object. - * @param {goog.fx.DragDropItem=} opt_targetItem Target item. - * @param {Element=} opt_targetElement Target element. - * @param {number=} opt_clientX X-Position relative to the screen. - * @param {number=} opt_clientY Y-Position relative to the screen. - * @param {number=} opt_x X-Position relative to the viewport. - * @param {number=} opt_y Y-Position relative to the viewport. - * @param {Object=} opt_subtarget The currently active subtarget. - * @extends {goog.events.Event} * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Vector} vectorLayer Vector layer. */ -goog.fx.DragDropEvent = function(type, source, sourceItem, - opt_target, opt_targetItem, opt_targetElement, - opt_clientX, opt_clientY, opt_x, opt_y, - opt_subtarget) { - // TODO(eae): Get rid of all the optional parameters and have the caller set - // the fields directly instead. - goog.fx.DragDropEvent.base(this, 'constructor', type); - - /** - * Reference to the source goog.fx.AbstractDragDrop object. - * @type {goog.fx.AbstractDragDrop} - */ - this.dragSource = source; - - /** - * Reference to the source goog.fx.DragDropItem object. - * @type {goog.fx.DragDropItem} - */ - this.dragSourceItem = sourceItem; +ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { - /** - * Reference to the target goog.fx.AbstractDragDrop object. - * @type {goog.fx.AbstractDragDrop|undefined} - */ - this.dropTarget = opt_target; + goog.base(this, mapRenderer, vectorLayer); /** - * Reference to the target goog.fx.DragDropItem object. - * @type {goog.fx.DragDropItem|undefined} + * @private + * @type {boolean} */ - this.dropTargetItem = opt_targetItem; + this.dirty_ = false; /** - * The actual element of the drop target that is the target for this event. - * @type {Element|undefined} + * @private + * @type {number} */ - this.dropTargetElement = opt_targetElement; + this.renderedRevision_ = -1; /** - * X-Position relative to the screen. - * @type {number|undefined} + * @private + * @type {number} */ - this.clientX = opt_clientX; + this.renderedResolution_ = NaN; /** - * Y-Position relative to the screen. - * @type {number|undefined} + * @private + * @type {ol.Extent} */ - this.clientY = opt_clientY; + this.renderedExtent_ = ol.extent.createEmpty(); /** - * X-Position relative to the viewport. - * @type {number|undefined} + * @private + * @type {function(ol.Feature, ol.Feature): number|null} */ - this.viewportX = opt_x; + this.renderedRenderOrder_ = null; /** - * Y-Position relative to the viewport. - * @type {number|undefined} + * @private + * @type {ol.render.webgl.ReplayGroup} */ - this.viewportY = opt_y; + this.replayGroup_ = null; /** - * The subtarget that is currently active if a subtargeting function - * is supplied. - * @type {Object|undefined} + * The last layer state. + * @private + * @type {?ol.layer.LayerState} */ - this.subtarget = opt_subtarget; -}; -goog.inherits(goog.fx.DragDropEvent, goog.events.Event); - + this.layerState_ = null; -/** @override */ -goog.fx.DragDropEvent.prototype.disposeInternal = function() { }; - +goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); /** - * Class representing a source or target element for drag and drop operations. - * - * @param {Element|string} element Dom Node, or string representation of node - * id, to be used as drag source/drop target. - * @param {Object=} opt_data Data associated with the source/target. - * @throws Error If no element argument is provided or if the type is invalid - * @extends {goog.events.EventTarget} - * @constructor + * @inheritDoc */ -goog.fx.DragDropItem = function(element, opt_data) { - goog.fx.DragDropItem.base(this, 'constructor'); - - /** - * Reference to drag source/target element - * @type {Element} - */ - this.element = goog.dom.getElement(element); - - /** - * Data associated with element. - * @type {Object|undefined} - */ - this.data = opt_data; - - /** - * Drag object the item belongs to. - * @type {goog.fx.AbstractDragDrop?} - * @private - */ - this.parent_ = null; - - /** - * Event handler for listeners on events that can initiate a drag. - * @type {!goog.events.EventHandler<!goog.fx.DragDropItem>} - * @private - */ - this.eventHandler_ = new goog.events.EventHandler(this); - this.registerDisposable(this.eventHandler_); - - if (!this.element) { - throw Error('Invalid argument'); +ol.renderer.webgl.VectorLayer.prototype.composeFrame = + function(frameState, layerState, context) { + this.layerState_ = layerState; + var viewState = frameState.viewState; + var replayGroup = this.replayGroup_; + if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { + replayGroup.replay(context, + viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + layerState.brightness, layerState.contrast, layerState.hue, + layerState.saturation, + layerState.managed ? frameState.skippedFeatureUids : {}); } + }; -goog.inherits(goog.fx.DragDropItem, goog.events.EventTarget); /** - * The current element being dragged. This is needed because a DragDropItem can - * have multiple elements that can be dragged. - * @type {Element} - * @private + * @inheritDoc */ -goog.fx.DragDropItem.prototype.currentDragElement_ = null; +ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { + var replayGroup = this.replayGroup_; + if (!goog.isNull(replayGroup)) { + var context = this.mapRenderer.getContext(); + replayGroup.getDeleteResourcesFunction(context)(); + this.replayGroup_ = null; + } + goog.base(this, 'disposeInternal'); +}; /** - * Get the data associated with the source/target. - * @return {Object|null|undefined} Data associated with the source/target. + * @inheritDoc */ -goog.fx.DragDropItem.prototype.getData = function() { - return this.data; +ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) { + return undefined; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layer = this.getLayer(); + var layerState = this.layerState_; + /** @type {Object.<string, boolean>} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + layerState.opacity, layerState.brightness, layerState.contrast, + layerState.hue, layerState.saturation, + layerState.managed ? frameState.skippedFeatureUids : {}, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + goog.asserts.assert(goog.isDef(feature), 'received a feature'); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } }; /** - * Gets the element that is actually draggable given that the given target was - * attempted to be dragged. This should be overriden when the element that was - * given actually contains many items that can be dragged. From the target, you - * can determine what element should actually be dragged. - * - * @param {Element} target The target that was attempted to be dragged. - * @return {Element} The element that is draggable given the target. If - * none are draggable, this will return null. + * @inheritDoc */ -goog.fx.DragDropItem.prototype.getDraggableElement = function(target) { - return target; +ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = + function(coordinate, frameState) { + if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) { + return false; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layerState = this.layerState_; + return this.replayGroup_.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + layerState.opacity, layerState.brightness, layerState.contrast, + layerState.hue, layerState.saturation, frameState.skippedFeatureUids); + } }; /** - * Gets the element that is currently being dragged. - * - * @return {Element} The element that is currently being dragged. + * @inheritDoc */ -goog.fx.DragDropItem.prototype.getCurrentDragElement = function() { - return this.currentDragElement_; +ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + var coordinate = pixel.slice(); + ol.vec.Mat4.multVec2( + frameState.pixelToCoordinateMatrix, coordinate, coordinate); + var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } }; /** - * Gets all the elements of this item that are potentially draggable/ - * - * @return {!Array<Element>} The draggable elements. + * Handle changes in image style state. + * @param {goog.events.Event} event Image style change event. + * @private */ -goog.fx.DragDropItem.prototype.getDraggableElements = function() { - return [this.element]; +ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = + function(event) { + this.renderIfReadyAndVisible(); }; /** - * Event handler for mouse down. - * - * @param {goog.events.BrowserEvent} event Mouse down event. - * @private + * @inheritDoc */ -goog.fx.DragDropItem.prototype.mouseDown_ = function(event) { - if (!event.isMouseActionButton()) { - return; - } +ol.renderer.webgl.VectorLayer.prototype.prepareFrame = + function(frameState, layerState, context) { - // Get the draggable element for the target. - var element = this.getDraggableElement(/** @type {Element} */ (event.target)); - if (element) { - this.maybeStartDrag_(event, element); + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + goog.asserts.assertInstanceof(vectorLayer, ol.layer.Vector, + 'layer is an instance of ol.layer.Vector'); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + var animating = frameState.viewHints[ol.ViewHint.ANIMATING]; + var interacting = frameState.viewHints[ol.ViewHint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { + return true; } -}; + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); -/** - * Sets the dragdrop to which this item belongs. - * @param {goog.fx.AbstractDragDrop} parent The parent dragdrop. - */ -goog.fx.DragDropItem.prototype.setParent = function(parent) { - this.parent_ = parent; -}; + if (!goog.isDef(vectorLayerRenderOrder)) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); -/** - * Adds mouse move, mouse out and mouse up handlers. - * - * @param {goog.events.BrowserEvent} event Mouse down event. - * @param {Element} element Element. - * @private - */ -goog.fx.DragDropItem.prototype.maybeStartDrag_ = function(event, element) { - var eventType = goog.events.EventType; - this.eventHandler_. - listen(element, eventType.MOUSEMOVE, this.mouseMove_, false). - listen(element, eventType.MOUSEOUT, this.mouseMove_, false); + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } - // Capture the MOUSEUP on the document to ensure that we cancel the start - // drag handlers even if the mouse up occurs on some other element. This can - // happen for instance when the mouse down changes the geometry of the element - // clicked on (e.g. through changes in activation styling) such that the mouse - // up occurs outside the original element. - var doc = goog.dom.getOwnerDocument(element); - this.eventHandler_.listen(doc, eventType.MOUSEUP, this.mouseUp_, true); + if (!goog.isNull(this.replayGroup_)) { + frameState.postRenderFunctions.push( + this.replayGroup_.getDeleteResourcesFunction(context)); + } - this.currentDragElement_ = element; + this.dirty_ = false; + + var replayGroup = new ol.render.webgl.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), + extent, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + var renderFeature = + /** + * @param {ol.Feature} feature Feature. + * @this {ol.renderer.webgl.VectorLayer} + */ + function(feature) { + var styles; + if (goog.isDef(feature.getStyleFunction())) { + styles = feature.getStyleFunction().call(feature, resolution); + } else if (goog.isDef(vectorLayer.getStyleFunction())) { + styles = vectorLayer.getStyleFunction()(feature, resolution); + } + if (goog.isDefAndNotNull(styles)) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (!goog.isNull(vectorLayerRenderOrder)) { + /** @type {Array.<ol.Feature>} */ + var features = []; + vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + goog.array.sort(features, vectorLayerRenderOrder); + goog.array.forEach(features, renderFeature, this); + } else { + vectorSource.forEachFeatureInExtentAtResolution( + extent, resolution, renderFeature, this); + } + replayGroup.finish(context); - this.startPosition_ = new goog.math.Coordinate( - event.clientX, event.clientY); + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; - event.preventDefault(); + return true; }; /** - * Event handler for mouse move. Starts drag operation if moved more than the - * threshold value. - * - * @param {goog.events.BrowserEvent} event Mouse move or mouse out event. - * @private + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.style.Style>} styles Array of styles + * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. */ -goog.fx.DragDropItem.prototype.mouseMove_ = function(event) { - var distance = Math.abs(event.clientX - this.startPosition_.x) + - Math.abs(event.clientY - this.startPosition_.y); - // Fire dragStart event if the drag distance exceeds the threshold or if the - // mouse leave the dragged element. - // TODO(user): Consider using the goog.fx.Dragger to track the distance - // even after the mouse leaves the dragged element. - var currentDragElement = this.currentDragElement_; - var distanceAboveThreshold = - distance > goog.fx.AbstractDragDrop.initDragDistanceThreshold; - var mouseOutOnDragElement = event.type == goog.events.EventType.MOUSEOUT && - event.target == currentDragElement; - if (distanceAboveThreshold || mouseOutOnDragElement) { - this.eventHandler_.removeAll(); - this.parent_.startDrag(event, this); +ol.renderer.webgl.VectorLayer.prototype.renderFeature = + function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!goog.isDefAndNotNull(styles)) { + return false; + } + var i, ii, loading = false; + for (i = 0, ii = styles.length; i < ii; ++i) { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles[i], + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; } + return loading; }; +// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE) + +goog.provide('ol.renderer.webgl.Map'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.log'); +goog.require('goog.log.Logger'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('goog.webgl'); +goog.require('ol'); +goog.require('ol.RendererType'); +goog.require('ol.css'); +goog.require('ol.dom'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Layer'); +goog.require('ol.layer.Tile'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.Event'); +goog.require('ol.render.EventType'); +goog.require('ol.render.webgl.Immediate'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.webgl.ImageLayer'); +goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.renderer.webgl.TileLayer'); +goog.require('ol.renderer.webgl.VectorLayer'); +goog.require('ol.source.State'); +goog.require('ol.structs.LRUCache'); +goog.require('ol.structs.PriorityQueue'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); +goog.require('ol.webgl.WebGLContextEventType'); + /** - * Event handler for mouse up. Removes mouse move, mouse out and mouse up event - * handlers. - * - * @param {goog.events.BrowserEvent} event Mouse up event. - * @private + * @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}} */ -goog.fx.DragDropItem.prototype.mouseUp_ = function(event) { - this.eventHandler_.removeAll(); - delete this.startPosition_; - this.currentDragElement_ = null; -}; +ol.renderer.webgl.TextureCacheEntry; /** - * Class representing an active drop target - * - * @param {goog.math.Box} box Box describing the position and dimension of the - * target item. - * @param {goog.fx.AbstractDragDrop=} opt_target Target that contains the item - associated with position. - * @param {goog.fx.DragDropItem=} opt_item Item associated with position. - * @param {Element=} opt_element Element of item associated with position. * @constructor - * @private + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.Map} map Map. */ -goog.fx.ActiveDropTarget_ = function(box, opt_target, opt_item, opt_element) { - - /** - * Box describing the position and dimension of the target item - * @type {goog.math.Box} - * @private - */ - this.box_ = box; +ol.renderer.webgl.Map = function(container, map) { - /** - * Target that contains the item associated with position - * @type {goog.fx.AbstractDragDrop|undefined} - * @private - */ - this.target_ = opt_target; + goog.base(this, container, map); /** - * Item associated with position - * @type {goog.fx.DragDropItem|undefined} * @private + * @type {HTMLCanvasElement} */ - this.item_ = opt_item; + this.canvas_ = /** @type {HTMLCanvasElement} */ + (goog.dom.createElement(goog.dom.TagName.CANVAS)); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + goog.dom.insertChildAt(container, this.canvas_, 0); /** - * The draggable element of the item associated with position. - * @type {Element|undefined} * @private + * @type {number} */ - this.element_ = opt_element; -}; - - -/** - * If this target is in a scrollable container this is it. - * @type {goog.fx.ScrollableContainer_} - * @private - */ -goog.fx.ActiveDropTarget_.prototype.scrollableContainer_ = null; - - - -/** - * Class for representing a scrollable container - * @param {Element} element the scrollable element. - * @constructor - * @private - */ -goog.fx.ScrollableContainer_ = function(element) { + this.clipTileCanvasWidth_ = 0; /** - * The targets that lie within this container. - * @type {Array<goog.fx.ActiveDropTarget_>} * @private + * @type {number} */ - this.containedTargets_ = []; + this.clipTileCanvasHeight_ = 0; /** - * The element that is this container - * @type {Element} * @private + * @type {CanvasRenderingContext2D} */ - this.element_ = element; + this.clipTileContext_ = ol.dom.createCanvasContext2D(); /** - * The saved scroll left location for calculating deltas. - * @type {number} * @private + * @type {boolean} */ - this.savedScrollLeft_ = 0; + this.renderedVisible_ = true; /** - * The saved scroll top location for calculating deltas. - * @type {number} * @private + * @type {WebGLRenderingContext} */ - this.savedScrollTop_ = 0; + this.gl_ = ol.webgl.getContext(this.canvas_, { + antialias: true, + depth: false, + failIfMajorPerformanceCaveat: true, + preserveDrawingBuffer: false, + stencil: true + }); + goog.asserts.assert(!goog.isNull(this.gl_), 'got a WebGLRenderingContext'); /** - * The space occupied by the container. - * @type {goog.math.Box} * @private + * @type {ol.webgl.Context} */ - this.box_ = null; -}; - -// FIXME should possibly show tooltip when dragging? - -goog.provide('ol.control.ZoomSlider'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventType'); -goog.require('goog.fx.DragDropEvent'); -goog.require('goog.fx.Dragger'); -goog.require('goog.fx.Dragger.EventType'); -goog.require('goog.math'); -goog.require('goog.math.Rect'); -goog.require('goog.style'); -goog.require('ol'); -goog.require('ol.Size'); -goog.require('ol.ViewHint'); -goog.require('ol.animation'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.easing'); - - - -/** - * @classdesc - * A slider type of control for zooming. - * - * Example: - * - * map.addControl(new ol.control.ZoomSlider()); - * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options. - * @api stable - */ -ol.control.ZoomSlider = function(opt_options) { + this.context_ = new ol.webgl.Context(this.canvas_, this.gl_); - var options = goog.isDef(opt_options) ? opt_options : {}; + goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST, + this.handleWebGLContextLost, false, this); + goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED, + this.handleWebGLContextRestored, false, this); /** - * Will hold the current resolution of the view. - * - * @type {number|undefined} * @private + * @type {ol.structs.LRUCache.<ol.renderer.webgl.TextureCacheEntry|null>} */ - this.currentResolution_ = undefined; + this.textureCache_ = new ol.structs.LRUCache(); /** - * The direction of the slider. Will be determined from actual display of the - * container and defaults to ol.control.ZoomSlider.direction.VERTICAL. - * - * @type {ol.control.ZoomSlider.direction} * @private + * @type {ol.Coordinate} */ - this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; + this.focus_ = null; /** - * The calculated thumb size (border box plus margins). Set when initSlider_ - * is called. - * @type {ol.Size} * @private + * @type {ol.structs.PriorityQueue.<Array>} */ - this.thumbSize_ = null; + this.tileTextureQueue_ = new ol.structs.PriorityQueue( + goog.bind( + /** + * @param {Array.<*>} element Element. + * @return {number} Priority. + * @this {ol.renderer.webgl.Map} + */ + function(element) { + var tileCenter = /** @type {ol.Coordinate} */ (element[1]); + var tileResolution = /** @type {number} */ (element[2]); + var deltaX = tileCenter[0] - this.focus_[0]; + var deltaY = tileCenter[1] - this.focus_[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; + }, this), + /** + * @param {Array.<*>} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); /** - * Whether the slider is initialized. - * @type {boolean} * @private + * @type {ol.PostRenderFunction} */ - this.sliderInitialized_ = false; - - var className = goog.isDef(options.className) ? - options.className : 'ol-zoomslider'; - var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV, - [className + '-thumb', ol.css.CLASS_UNSELECTABLE]); - var containerElement = goog.dom.createDom(goog.dom.TagName.DIV, - [className, ol.css.CLASS_UNSELECTABLE, ol.css.CLASS_CONTROL], - thumbElement); + this.loadNextTileTexture_ = goog.bind( + function(map, frameState) { + if (!this.tileTextureQueue_.isEmpty()) { + this.tileTextureQueue_.reprioritize(); + var element = this.tileTextureQueue_.dequeue(); + var tile = /** @type {ol.Tile} */ (element[0]); + var tileSize = /** @type {ol.Size} */ (element[3]); + var tileGutter = /** @type {number} */ (element[4]); + this.bindTileTexture( + tile, tileSize, tileGutter, goog.webgl.LINEAR, goog.webgl.LINEAR); + } + }, this); /** - * @type {goog.fx.Dragger} * @private + * @type {number} */ - this.dragger_ = new goog.fx.Dragger(thumbElement); - this.registerDisposable(this.dragger_); - - goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.START, - this.handleDraggerStart_, false, this); - goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG, - this.handleDraggerDrag_, false, this); - goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END, - this.handleDraggerEnd_, false, this); - - goog.events.listen(containerElement, goog.events.EventType.CLICK, - this.handleContainerClick_, false, this); - goog.events.listen(thumbElement, goog.events.EventType.CLICK, - goog.events.Event.stopPropagation); + this.textureCacheFrameMarkerCount_ = 0; - var render = goog.isDef(options.render) ? - options.render : ol.control.ZoomSlider.render; + this.initializeGL_(); - goog.base(this, { - element: containerElement, - render: render - }); }; -goog.inherits(ol.control.ZoomSlider, ol.control.Control); +goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map); /** - * The enum for available directions. - * - * @enum {number} + * @param {ol.Tile} tile Tile. + * @param {ol.Size} tileSize Tile size. + * @param {number} tileGutter Tile gutter. + * @param {number} magFilter Mag filter. + * @param {number} minFilter Min filter. */ -ol.control.ZoomSlider.direction = { - VERTICAL: 0, - HORIZONTAL: 1 +ol.renderer.webgl.Map.prototype.bindTileTexture = + function(tile, tileSize, tileGutter, magFilter, minFilter) { + var gl = this.getGL(); + var tileKey = tile.getKey(); + if (this.textureCache_.containsKey(tileKey)) { + var textureCacheEntry = this.textureCache_.get(tileKey); + goog.asserts.assert(!goog.isNull(textureCacheEntry), + 'a texture cache entry exists for key %s', tileKey); + gl.bindTexture(goog.webgl.TEXTURE_2D, textureCacheEntry.texture); + if (textureCacheEntry.magFilter != magFilter) { + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter); + textureCacheEntry.magFilter = magFilter; + } + if (textureCacheEntry.minFilter != minFilter) { + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, minFilter); + textureCacheEntry.minFilter = minFilter; + } + } else { + var texture = gl.createTexture(); + gl.bindTexture(goog.webgl.TEXTURE_2D, texture); + if (tileGutter > 0) { + var clipTileCanvas = this.clipTileContext_.canvas; + var clipTileContext = this.clipTileContext_; + if (this.clipTileCanvasWidth_ !== tileSize[0] || + this.clipTileCanvasHeight_ !== tileSize[1]) { + clipTileCanvas.width = tileSize[0]; + clipTileCanvas.height = tileSize[1]; + this.clipTileCanvasWidth_ = tileSize[0]; + this.clipTileCanvasHeight_ = tileSize[1]; + } else { + clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); + } + clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, + tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); + gl.texImage2D(goog.webgl.TEXTURE_2D, 0, + goog.webgl.RGBA, goog.webgl.RGBA, + goog.webgl.UNSIGNED_BYTE, clipTileCanvas); + } else { + gl.texImage2D(goog.webgl.TEXTURE_2D, 0, + goog.webgl.RGBA, goog.webgl.RGBA, + goog.webgl.UNSIGNED_BYTE, tile.getImage()); + } + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, minFilter); + gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, + goog.webgl.CLAMP_TO_EDGE); + gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, + goog.webgl.CLAMP_TO_EDGE); + this.textureCache_.set(tileKey, { + texture: texture, + magFilter: magFilter, + minFilter: minFilter + }); + } }; /** * @inheritDoc */ -ol.control.ZoomSlider.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); - if (!goog.isNull(map)) { - map.render(); +ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { + if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) { + return new ol.renderer.webgl.ImageLayer(this, layer); + } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) { + return new ol.renderer.webgl.TileLayer(this, layer); + } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) { + return new ol.renderer.webgl.VectorLayer(this, layer); + } else { + goog.asserts.fail('unexpected layer configuration'); + return null; } }; /** - * Initializes the slider element. This will determine and set this controls - * direction_ and also constrain the dragging of the thumb to always be within - * the bounds of the container. - * + * @param {ol.render.EventType} type Event type. + * @param {olx.FrameState} frameState Frame state. * @private */ -ol.control.ZoomSlider.prototype.initSlider_ = function() { - var container = this.element; - var containerSize = goog.style.getSize(container); +ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = + function(type, frameState) { + var map = this.getMap(); + if (map.hasListener(type)) { + var context = this.context_; - var thumb = goog.dom.getFirstElementChild(container); - var thumbMargins = goog.style.getMarginBox(thumb); - var thumbBorderBoxSize = goog.style.getBorderBoxSize(thumb); - var thumbWidth = thumbBorderBoxSize.width + - thumbMargins.right + thumbMargins.left; - var thumbHeight = thumbBorderBoxSize.height + - thumbMargins.top + thumbMargins.bottom; - this.thumbSize_ = [thumbWidth, thumbHeight]; + var extent = frameState.extent; + var size = frameState.size; + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; - var width = containerSize.width - thumbWidth; - var height = containerSize.height - thumbHeight; + var resolution = viewState.resolution; + var center = viewState.center; + var rotation = viewState.rotation; - var limits; - if (containerSize.width > containerSize.height) { - this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL; - limits = new goog.math.Rect(0, 0, width, 0); - } else { - this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; - limits = new goog.math.Rect(0, 0, 0, height); + var vectorContext = new ol.render.webgl.Immediate(context, + center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event(type, map, vectorContext, + frameState, null, context); + map.dispatchEvent(composeEvent); + + vectorContext.flush(); } - this.dragger_.setLimits(limits); - this.sliderInitialized_ = true; }; /** - * @param {ol.MapEvent} mapEvent Map event. - * @this {ol.control.ZoomSlider} - * @api + * @inheritDoc */ -ol.control.ZoomSlider.render = function(mapEvent) { - if (goog.isNull(mapEvent.frameState)) { - return; - } - goog.asserts.assert(goog.isDefAndNotNull(mapEvent.frameState.viewState)); - if (!this.sliderInitialized_) { - this.initSlider_(); - } - var res = mapEvent.frameState.viewState.resolution; - if (res !== this.currentResolution_) { - this.currentResolution_ = res; - this.setThumbPosition_(res); +ol.renderer.webgl.Map.prototype.disposeInternal = function() { + var gl = this.getGL(); + if (!gl.isContextLost()) { + this.textureCache_.forEach( + /** + * @param {?ol.renderer.webgl.TextureCacheEntry} textureCacheEntry + * Texture cache entry. + */ + function(textureCacheEntry) { + if (!goog.isNull(textureCacheEntry)) { + gl.deleteTexture(textureCacheEntry.texture); + } + }); } + goog.dispose(this.context_); + goog.base(this, 'disposeInternal'); }; /** - * @param {goog.events.BrowserEvent} browserEvent The browser event to handle. + * @param {ol.Map} map Map. + * @param {olx.FrameState} frameState Frame state. * @private */ -ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) { - var map = this.getMap(); - var view = map.getView(); - var currentResolution = view.getResolution(); - goog.asserts.assert(goog.isDef(currentResolution)); - map.beforeRender(ol.animation.zoom({ - resolution: currentResolution, - duration: ol.ZOOMSLIDER_ANIMATION_DURATION, - easing: ol.easing.easeOut - })); - var relativePosition = this.getRelativePosition_( - browserEvent.offsetX - this.thumbSize_[0] / 2, - browserEvent.offsetY - this.thumbSize_[1] / 2); - var resolution = this.getResolutionForPosition_(relativePosition); - view.setResolution(view.constrainResolution(resolution)); +ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) { + var gl = this.getGL(); + var textureCacheEntry; + while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + textureCacheEntry = this.textureCache_.peekLast(); + if (goog.isNull(textureCacheEntry)) { + if (+this.textureCache_.peekLastKey() == frameState.index) { + break; + } else { + --this.textureCacheFrameMarkerCount_; + } + } else { + gl.deleteTexture(textureCacheEntry.texture); + } + this.textureCache_.pop(); + } }; /** - * Handle dragger start events. - * @param {goog.fx.DragDropEvent} event The dragdropevent. - * @private + * @return {ol.webgl.Context} */ -ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) { - this.getMap().getView().setHint(ol.ViewHint.INTERACTING, 1); +ol.renderer.webgl.Map.prototype.getContext = function() { + return this.context_; }; /** - * Handle dragger drag events. - * - * @param {goog.fx.DragDropEvent} event The dragdropevent. - * @private + * @return {WebGLRenderingContext} GL. */ -ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) { - var relativePosition = this.getRelativePosition_(event.left, event.top); - this.currentResolution_ = this.getResolutionForPosition_(relativePosition); - this.getMap().getView().setResolution(this.currentResolution_); +ol.renderer.webgl.Map.prototype.getGL = function() { + return this.gl_; }; /** - * Handle dragger end events. - * @param {goog.fx.DragDropEvent} event The dragdropevent. - * @private + * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue. */ -ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) { - var map = this.getMap(); - var view = map.getView(); - view.setHint(ol.ViewHint.INTERACTING, -1); - goog.asserts.assert(goog.isDef(this.currentResolution_)); - map.beforeRender(ol.animation.zoom({ - resolution: this.currentResolution_, - duration: ol.ZOOMSLIDER_ANIMATION_DURATION, - easing: ol.easing.easeOut - })); - var resolution = view.constrainResolution(this.currentResolution_); - view.setResolution(resolution); +ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() { + return this.tileTextureQueue_; }; /** - * Positions the thumb inside its container according to the given resolution. - * - * @param {number} res The res. - * @private + * @inheritDoc */ -ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) { - var position = this.getPositionForResolution_(res); - var dragger = this.dragger_; - var thumb = goog.dom.getFirstElementChild(this.element); - - if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) { - var left = dragger.limits.left + dragger.limits.width * position; - goog.style.setPosition(thumb, left); - } else { - var top = dragger.limits.top + dragger.limits.height * position; - goog.style.setPosition(thumb, dragger.limits.left, top); - } +ol.renderer.webgl.Map.prototype.getType = function() { + return ol.RendererType.WEBGL; }; /** - * Calculates the relative position of the thumb given x and y offsets. The - * relative position scales from 0 to 1. The x and y offsets are assumed to be - * in pixel units within the dragger limits. - * - * @param {number} x Pixel position relative to the left of the slider. - * @param {number} y Pixel position relative to the top of the slider. - * @return {number} The relative position of the thumb. - * @private + * @param {goog.events.Event} event Event. + * @protected */ -ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) { - var draggerLimits = this.dragger_.limits; - var amount; - if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) { - amount = (x - draggerLimits.left) / draggerLimits.width; - } else { - amount = (y - draggerLimits.top) / draggerLimits.height; - } - return goog.math.clamp(amount, 0, 1); +ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { + event.preventDefault(); + this.textureCache_.clear(); + this.textureCacheFrameMarkerCount_ = 0; + goog.object.forEach(this.getLayerRenderers(), + /** + * @param {ol.renderer.Layer} layerRenderer Layer renderer. + * @param {string} key Key. + * @param {Object.<string, ol.renderer.Layer>} object Object. + */ + function(layerRenderer, key, object) { + goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer, + 'renderer is an instance of ol.renderer.webgl.Layer'); + var webGLLayerRenderer = /** @type {ol.renderer.webgl.Layer} */ + (layerRenderer); + webGLLayerRenderer.handleWebGLContextLost(); + }); }; /** - * Calculates the corresponding resolution of the thumb given its relative - * position (where 0 is the minimum and 1 is the maximum). - * - * @param {number} position The relative position of the thumb. - * @return {number} The corresponding resolution. - * @private + * @protected */ -ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) { - var fn = this.getMap().getView().getResolutionForValueFunction(); - return fn(1 - position); +ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() { + this.initializeGL_(); + this.getMap().render(); }; /** - * Determines the relative position of the slider for the given resolution. A - * relative position of 0 corresponds to the minimum view resolution. A - * relative position of 1 corresponds to the maximum view resolution. - * - * @param {number} res The resolution. - * @return {number} The relative position value (between 0 and 1). * @private */ -ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) { - var fn = this.getMap().getView().getValueForResolutionFunction(); - return 1 - fn(res); +ol.renderer.webgl.Map.prototype.initializeGL_ = function() { + var gl = this.gl_; + gl.activeTexture(goog.webgl.TEXTURE0); + gl.blendFuncSeparate( + goog.webgl.SRC_ALPHA, goog.webgl.ONE_MINUS_SRC_ALPHA, + goog.webgl.ONE, goog.webgl.ONE_MINUS_SRC_ALPHA); + gl.disable(goog.webgl.CULL_FACE); + gl.disable(goog.webgl.DEPTH_TEST); + gl.disable(goog.webgl.SCISSOR_TEST); + gl.disable(goog.webgl.STENCIL_TEST); }; -goog.provide('ol.control.ZoomToExtent'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.control.Control'); -goog.require('ol.css'); - - /** - * @classdesc - * A button control which, when pressed, changes the map view to a specific - * extent. To style this control use the css selector `.ol-zoom-extent`. - * - * @constructor - * @extends {ol.control.Control} - * @param {olx.control.ZoomToExtentOptions=} opt_options Options. - * @api stable + * @param {ol.Tile} tile Tile. + * @return {boolean} Is tile texture loaded. */ -ol.control.ZoomToExtent = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @type {ol.Extent} - * @private - */ - this.extent_ = goog.isDef(options.extent) ? options.extent : null; - - var className = goog.isDef(options.className) ? options.className : - 'ol-zoom-extent'; - - var tipLabel = goog.isDef(options.tipLabel) ? - options.tipLabel : 'Fit to extent'; - var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { - 'type': 'button', - 'title': tipLabel - }); - - goog.events.listen(button, goog.events.EventType.CLICK, - this.handleClick_, false, this); - - goog.events.listen(button, [ - goog.events.EventType.MOUSEOUT, - goog.events.EventType.FOCUSOUT - ], function() { - this.blur(); - }, false); - - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - ol.css.CLASS_CONTROL; - var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); - - goog.base(this, { - element: element, - target: options.target - }); +ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { + return this.textureCache_.containsKey(tile.getKey()); }; -goog.inherits(ol.control.ZoomToExtent, ol.control.Control); /** - * @param {goog.events.BrowserEvent} event The event to handle * @private + * @type {goog.log.Logger} */ -ol.control.ZoomToExtent.prototype.handleClick_ = function(event) { - event.preventDefault(); - this.handleZoomToExtent_(); -}; +ol.renderer.webgl.Map.prototype.logger_ = + goog.log.getLogger('ol.renderer.webgl.Map'); /** - * @private + * @inheritDoc */ -ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() { - var map = this.getMap(); - var view = map.getView(); - var extent = goog.isNull(this.extent_) ? - view.getProjection().getExtent() : this.extent_; - var size = map.getSize(); - goog.asserts.assert(goog.isDef(size)); - view.fitExtent(extent, size); -}; +ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { -goog.provide('ol.DeviceOrientation'); -goog.provide('ol.DeviceOrientationProperty'); + var context = this.getContext(); + var gl = this.getGL(); -goog.require('goog.events'); -goog.require('goog.math'); -goog.require('ol.Object'); -goog.require('ol.has'); + if (gl.isContextLost()) { + return false; + } + if (goog.isNull(frameState)) { + if (this.renderedVisible_) { + goog.style.setElementShown(this.canvas_, false); + this.renderedVisible_ = false; + } + return false; + } -/** - * @enum {string} - */ -ol.DeviceOrientationProperty = { - ALPHA: 'alpha', - BETA: 'beta', - GAMMA: 'gamma', - HEADING: 'heading', - TRACKING: 'tracking' -}; + this.focus_ = frameState.focus; + this.textureCache_.set((-frameState.index).toString(), null); + ++this.textureCacheFrameMarkerCount_; + this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); -/** - * @classdesc - * The ol.DeviceOrientation class provides access to DeviceOrientation - * information and events, see the [HTML 5 DeviceOrientation Specification]( - * http://www.w3.org/TR/orientation-event/) for more details. - * - * Many new computers, and especially mobile phones - * and tablets, provide hardware support for device orientation. Web - * developers targetting mobile devices will be especially interested in this - * class. - * - * Device orientation data are relative to a common starting point. For mobile - * devices, the starting point is to lay your phone face up on a table with the - * top of the phone pointing north. This represents the zero state. All - * angles are then relative to this state. For computers, it is the same except - * the screen is open at 90 degrees. - * - * Device orientation is reported as three angles - `alpha`, `beta`, and - * `gamma` - relative to the starting position along the three planar axes X, Y - * and Z. The X axis runs from the left edge to the right edge through the - * middle of the device. Similarly, the Y axis runs from the bottom to the top - * of the device through the middle. The Z axis runs from the back to the front - * through the middle. In the starting position, the X axis points to the - * right, the Y axis points away from you and the Z axis points straight up - * from the device lying flat. - * - * The three angles representing the device orientation are relative to the - * three axes. `alpha` indicates how much the device has been rotated around the - * Z axis, which is commonly interpreted as the compass heading (see note - * below). `beta` indicates how much the device has been rotated around the X - * axis, or how much it is tilted from front to back. `gamma` indicates how - * much the device has been rotated around the Y axis, or how much it is tilted - * from left to right. - * - * For most browsers, the `alpha` value returns the compass heading so if the - * device points north, it will be 0. With Safari on iOS, the 0 value of - * `alpha` is calculated from when device orientation was first requested. - * ol.DeviceOrientation provides the `heading` property which normalizes this - * behavior across all browsers for you. - * - * It is important to note that the HTML 5 DeviceOrientation specification - * indicates that `alpha`, `beta` and `gamma` are in degrees while the - * equivalent properties in ol.DeviceOrientation are in radians for consistency - * with all other uses of angles throughout OpenLayers. - * - * @see http://www.w3.org/TR/orientation-event/ - * - * @constructor - * @extends {ol.Object} - * @fires change Triggered when the device orientation changes. - * @param {olx.DeviceOrientationOptions=} opt_options Options. - * @api - */ -ol.DeviceOrientation = function(opt_options) { + /** @type {Array.<ol.layer.LayerState>} */ + var layerStatesToDraw = []; + var layerStatesArray = frameState.layerStatesArray; + var viewResolution = frameState.viewState.resolution; + var i, ii, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerState.sourceState == ol.source.State.READY) { + layerRenderer = this.getLayerRenderer(layerState.layer); + goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer, + 'renderer is an instance of ol.renderer.webgl.Layer'); + if (layerRenderer.prepareFrame(frameState, layerState, context)) { + layerStatesToDraw.push(layerState); + } + } + } - goog.base(this); + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } - var options = goog.isDef(opt_options) ? opt_options : {}; + gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, null); - /** - * @private - * @type {goog.events.Key} - */ - this.listenerKey_ = null; + gl.clearColor(0, 0, 0, 0); + gl.clear(goog.webgl.COLOR_BUFFER_BIT); + gl.enable(goog.webgl.BLEND); + gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); - goog.events.listen(this, - ol.Object.getChangeEventType(ol.DeviceOrientationProperty.TRACKING), - this.handleTrackingChanged_, false, this); + for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { + layerState = layerStatesToDraw[i]; + layerRenderer = this.getLayerRenderer(layerState.layer); + goog.asserts.assertInstanceof(layerRenderer, ol.renderer.webgl.Layer, + 'renderer is an instance of ol.renderer.webgl.Layer'); + layerRenderer.composeFrame(frameState, layerState, context); + } - this.setTracking(goog.isDef(options.tracking) ? options.tracking : false); + if (!this.renderedVisible_) { + goog.style.setElementShown(this.canvas_, true); + this.renderedVisible_ = true; + } + + this.calculateMatrices2D(frameState); + + if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + frameState.postRenderFunctions.push(goog.bind(this.expireCache_, this)); + } + + if (!this.tileTextureQueue_.isEmpty()) { + frameState.postRenderFunctions.push(this.loadNextTileTexture_); + frameState.animate = true; + } + + this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); + + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); }; -goog.inherits(ol.DeviceOrientation, ol.Object); /** * @inheritDoc */ -ol.DeviceOrientation.prototype.disposeInternal = function() { - this.setTracking(false); - goog.base(this, 'disposeInternal'); -}; +ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = + function(coordinate, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + if (this.getGL().isContextLost()) { + return false; + } -/** - * @private - * @param {goog.events.BrowserEvent} browserEvent Event. - */ -ol.DeviceOrientation.prototype.orientationChange_ = function(browserEvent) { - var event = /** @type {DeviceOrientationEvent} */ - (browserEvent.getBrowserEvent()); - if (goog.isDefAndNotNull(event.alpha)) { - var alpha = goog.math.toRadians(event.alpha); - this.set(ol.DeviceOrientationProperty.ALPHA, alpha); - // event.absolute is undefined in iOS. - if (goog.isBoolean(event.absolute) && event.absolute) { - this.set(ol.DeviceOrientationProperty.HEADING, alpha); - } else if (goog.isDefAndNotNull(event.webkitCompassHeading) && - goog.isDefAndNotNull(event.webkitCompassAccuracy) && - event.webkitCompassAccuracy != -1) { - var heading = goog.math.toRadians(event.webkitCompassHeading); - this.set(ol.DeviceOrientationProperty.HEADING, heading); + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachFeatureAtCoordinate( + coordinate, frameState, callback, thisArg); + if (result) { + return result; + } } } - if (goog.isDefAndNotNull(event.beta)) { - this.set(ol.DeviceOrientationProperty.BETA, - goog.math.toRadians(event.beta)); - } - if (goog.isDefAndNotNull(event.gamma)) { - this.set(ol.DeviceOrientationProperty.GAMMA, - goog.math.toRadians(event.gamma)); - } - this.changed(); + return undefined; }; /** - * @return {number|undefined} The euler angle in radians of the device from the - * standard Z axis. - * @observable - * @api + * @inheritDoc */ -ol.DeviceOrientation.prototype.getAlpha = function() { - return /** @type {number|undefined} */ ( - this.get(ol.DeviceOrientationProperty.ALPHA)); -}; -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'getAlpha', - ol.DeviceOrientation.prototype.getAlpha); +ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = + function(coordinate, frameState, layerFilter, thisArg) { + var hasFeature = false; + if (this.getGL().isContextLost()) { + return false; + } -/** - * @return {number|undefined} The euler angle in radians of the device from the - * planar X axis. - * @observable - * @api - */ -ol.DeviceOrientation.prototype.getBeta = function() { - return /** @type {number|undefined} */ ( - this.get(ol.DeviceOrientationProperty.BETA)); + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + hasFeature = + layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); + if (hasFeature) { + return true; + } + } + } + return hasFeature; }; -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'getBeta', - ol.DeviceOrientation.prototype.getBeta); /** - * @return {number|undefined} The euler angle in radians of the device from the - * planar Y axis. - * @observable - * @api + * @inheritDoc */ -ol.DeviceOrientation.prototype.getGamma = function() { - return /** @type {number|undefined} */ ( - this.get(ol.DeviceOrientationProperty.GAMMA)); -}; -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'getGamma', - ol.DeviceOrientation.prototype.getGamma); +ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + if (this.getGL().isContextLost()) { + return false; + } + var viewState = frameState.viewState; + var result; -/** - * @return {number|undefined} The heading of the device relative to north, in - * radians, normalizing for different browser behavior. - * @observable - * @api - */ -ol.DeviceOrientation.prototype.getHeading = function() { - return /** @type {number|undefined} */ ( - this.get(ol.DeviceOrientationProperty.HEADING)); + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; }; -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'getHeading', - ol.DeviceOrientation.prototype.getHeading); /** - * Are we tracking the device's orientation? - * @return {boolean} The status of tracking changes to alpha, beta and gamma. - * If true, changes are tracked and reported immediately. - * @observable - * @api + * @private + * @const */ -ol.DeviceOrientation.prototype.getTracking = function() { - return /** @type {boolean} */ ( - this.get(ol.DeviceOrientationProperty.TRACKING)); +ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_ = { + opacity: 1, + brightness: 0, + contrast: 1, + hue: 0, + saturation: 1 }; -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'getTracking', - ol.DeviceOrientation.prototype.getTracking); + +// FIXME recheck layer/map projection compatibility when projection changes +// FIXME layer renderers should skip when they can't reproject +// FIXME add tilt and height? + +goog.provide('ol.Map'); +goog.provide('ol.MapProperty'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.async.AnimationDelay'); +goog.require('goog.async.nextTick'); +goog.require('goog.debug.Console'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.ViewportSizeMonitor'); +goog.require('goog.dom.classlist'); +goog.require('goog.events'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.events.KeyHandler'); +goog.require('goog.events.KeyHandler.EventType'); +goog.require('goog.events.MouseWheelHandler'); +goog.require('goog.events.MouseWheelHandler.EventType'); +goog.require('goog.functions'); +goog.require('goog.log'); +goog.require('goog.log.Level'); +goog.require('goog.object'); +goog.require('goog.style'); +goog.require('goog.vec.Mat4'); +goog.require('ol.Collection'); +goog.require('ol.CollectionEventType'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.MapBrowserEventHandler'); +goog.require('ol.MapEvent'); +goog.require('ol.MapEventType'); +goog.require('ol.Object'); +goog.require('ol.ObjectEvent'); +goog.require('ol.ObjectEventType'); +goog.require('ol.Pixel'); +goog.require('ol.PostRenderFunction'); +goog.require('ol.PreRenderFunction'); +goog.require('ol.RendererType'); +goog.require('ol.Size'); +goog.require('ol.TileQueue'); +goog.require('ol.View'); +goog.require('ol.ViewHint'); +goog.require('ol.control'); +goog.require('ol.extent'); +goog.require('ol.has'); +goog.require('ol.interaction'); +goog.require('ol.layer.Base'); +goog.require('ol.layer.Group'); +goog.require('ol.proj'); +goog.require('ol.proj.common'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.Map'); +goog.require('ol.renderer.dom.Map'); +goog.require('ol.renderer.webgl.Map'); +goog.require('ol.size'); +goog.require('ol.structs.PriorityQueue'); +goog.require('ol.tilecoord'); +goog.require('ol.vec.Mat4'); /** - * @private + * @const + * @type {string} */ -ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() { - if (ol.has.DEVICE_ORIENTATION) { - var tracking = this.getTracking(); - if (tracking && goog.isNull(this.listenerKey_)) { - this.listenerKey_ = goog.events.listen(goog.global, 'deviceorientation', - this.orientationChange_, false, this); - } else if (!tracking && !goog.isNull(this.listenerKey_)) { - goog.events.unlistenByKey(this.listenerKey_); - this.listenerKey_ = null; - } - } -}; +ol.OL3_URL = 'http://openlayers.org/'; /** - * Enable or disable tracking of DeviceOrientation events. - * @param {boolean} tracking The status of tracking changes to alpha, beta and - * gamma. If true, changes are tracked and reported immediately. - * @observable - * @api + * @const + * @type {string} */ -ol.DeviceOrientation.prototype.setTracking = function(tracking) { - this.set(ol.DeviceOrientationProperty.TRACKING, tracking); -}; -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'setTracking', - ol.DeviceOrientation.prototype.setTracking); +ol.OL3_LOGO_URL = 'data:image/png;base64,' + + 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' + + 'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' + + 'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' + + 'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' + + 'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' + + 'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' + + 'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' + + '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' + + 'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' + + 'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' + + 'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' + + 'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' + + 'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' + + 'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' + + 'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' + + 'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' + + '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' + + 'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' + + 'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' + + 'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' + + 'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC'; -goog.provide('ol.dom.Input'); -goog.provide('ol.dom.InputProperty'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.Object'); +/** + * @type {Array.<ol.RendererType>} + */ +ol.DEFAULT_RENDERER_TYPES = [ + ol.RendererType.CANVAS, + ol.RendererType.WEBGL, + ol.RendererType.DOM +]; /** * @enum {string} */ -ol.dom.InputProperty = { - VALUE: 'value', - CHECKED: 'checked' +ol.MapProperty = { + LAYERGROUP: 'layergroup', + SIZE: 'size', + TARGET: 'target', + VIEW: 'view' }; /** * @classdesc - * Helper class for binding HTML input to an {@link ol.Object}. + * The map is the core component of OpenLayers. For a map to render, a view, + * one or more layers, and a target container are needed: * - * Example: + * var map = new ol.Map({ + * view: new ol.View({ + * center: [0, 0], + * zoom: 1 + * }), + * layers: [ + * new ol.layer.Tile({ + * source: new ol.source.MapQuest({layer: 'osm'}) + * }) + * ], + * target: 'map' + * }); + * + * The above snippet creates a map using a {@link ol.layer.Tile} to display + * {@link ol.source.MapQuest} OSM data and render it to a DOM element with the + * id `map`. + * + * The constructor places a viewport container (with CSS class name + * `ol-viewport`) in the target element (see `getViewport()`), and then two + * further elements within the viewport: one with CSS class name + * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with + * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent` + * option of {@link ol.Overlay} for the difference). The map itself is placed in + * a further element within the viewport, either DOM or Canvas, depending on the + * renderer. * - * // bind a checkbox with id 'visible' to a layer's visibility - * var visible = new ol.dom.Input(document.getElementById('visible')); - * visible.bindTo('checked', layer, 'visible'); + * Layers are stored as a `ol.Collection` in layerGroups. A top-level group is + * provided by the library. This is what is accessed by `getLayerGroup` and + * `setLayerGroup`. Layers entered in the options are added to this group, and + * `addLayer` and `removeLayer` change the layer collection in the group. + * `getLayers` is a convenience function for `getLayerGroup().getLayers()`. + * Note that `ol.layer.Group` is a subclass of `ol.layer.Base`, so layers + * entered in the options or added with `addLayer` can be groups, which can + * contain further groups, and so on. * * @constructor * @extends {ol.Object} - * @param {Element} target Target element. - * @api + * @param {olx.MapOptions} options Map options. + * @fires ol.MapBrowserEvent + * @fires ol.MapEvent + * @fires ol.render.Event#postcompose + * @fires ol.render.Event#precompose + * @api stable */ -ol.dom.Input = function(target) { +ol.Map = function(options) { + goog.base(this); + var optionsInternal = ol.Map.createOptionsInternal(options); + /** + * @type {boolean} * @private - * @type {HTMLInputElement} */ - this.target_ = /** @type {HTMLInputElement} */ (target); + this.loadTilesWhileAnimating_ = goog.isDef(options.loadTilesWhileAnimating) ? + options.loadTilesWhileAnimating : false; - goog.events.listen(this.target_, - [goog.events.EventType.CHANGE, goog.events.EventType.INPUT], - this.handleInputChanged_, false, this); + /** + * @type {boolean} + * @private + */ + this.loadTilesWhileInteracting_ = + goog.isDef(options.loadTilesWhileInteracting) ? + options.loadTilesWhileInteracting : false; - goog.events.listen(this, - ol.Object.getChangeEventType(ol.dom.InputProperty.VALUE), - this.handleValueChanged_, false, this); - goog.events.listen(this, - ol.Object.getChangeEventType(ol.dom.InputProperty.CHECKED), - this.handleCheckedChanged_, false, this); -}; -goog.inherits(ol.dom.Input, ol.Object); + /** + * @private + * @type {number} + */ + this.pixelRatio_ = goog.isDef(options.pixelRatio) ? + options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO; + /** + * @private + * @type {Object} + */ + this.logos_ = optionsInternal.logos; -/** - * If the input is a checkbox, return whether or not the checkbox is checked. - * @return {boolean|undefined} The checked state of the Input. - * @observable - * @api - */ -ol.dom.Input.prototype.getChecked = function() { - return /** @type {boolean} */ (this.get(ol.dom.InputProperty.CHECKED)); -}; -goog.exportProperty( - ol.dom.Input.prototype, - 'getChecked', - ol.dom.Input.prototype.getChecked); + /** + * @private + * @type {goog.async.AnimationDelay} + */ + this.animationDelay_ = + new goog.async.AnimationDelay(this.renderFrame_, undefined, this); + this.registerDisposable(this.animationDelay_); + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.coordinateToPixelMatrix_ = goog.vec.Mat4.createNumber(); -/** - * Get the value of the input. - * @return {string|undefined} The value of the Input. - * @observable - * @api - */ -ol.dom.Input.prototype.getValue = function() { - return /** @type {string} */ (this.get(ol.dom.InputProperty.VALUE)); -}; -goog.exportProperty( - ol.dom.Input.prototype, - 'getValue', - ol.dom.Input.prototype.getValue); + /** + * @private + * @type {goog.vec.Mat4.Number} + */ + this.pixelToCoordinateMatrix_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {number} + */ + this.frameIndex_ = 0; -/** - * Sets the value of the input. - * @param {string} value The value of the Input. - * @observable - * @api - */ -ol.dom.Input.prototype.setValue = function(value) { - this.set(ol.dom.InputProperty.VALUE, value); -}; -goog.exportProperty( - ol.dom.Input.prototype, - 'setValue', - ol.dom.Input.prototype.setValue); + /** + * @private + * @type {?olx.FrameState} + */ + this.frameState_ = null; + /** + * The extent at the previous 'moveend' event. + * @private + * @type {ol.Extent} + */ + this.previousExtent_ = ol.extent.createEmpty(); -/** - * Set whether or not a checkbox is checked. - * @param {boolean} checked The checked state of the Input. - * @observable - * @api - */ -ol.dom.Input.prototype.setChecked = function(checked) { - this.set(ol.dom.InputProperty.CHECKED, checked); -}; -goog.exportProperty( - ol.dom.Input.prototype, - 'setChecked', - ol.dom.Input.prototype.setChecked); + /** + * @private + * @type {goog.events.Key} + */ + this.viewPropertyListenerKey_ = null; + /** + * @private + * @type {Array.<goog.events.Key>} + */ + this.layerGroupPropertyListenerKeys_ = null; -/** - * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @private - */ -ol.dom.Input.prototype.handleInputChanged_ = function(browserEvent) { - goog.asserts.assert(browserEvent.currentTarget == this.target_); - var target = this.target_; - if (target.type === 'checkbox' || target.type === 'radio') { - this.setChecked(target.checked); - } else { - this.setValue(target.value); + /** + * @private + * @type {Element} + */ + this.viewport_ = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-viewport'); + this.viewport_.style.position = 'relative'; + this.viewport_.style.overflow = 'hidden'; + this.viewport_.style.width = '100%'; + this.viewport_.style.height = '100%'; + // prevent page zoom on IE >= 10 browsers + this.viewport_.style.msTouchAction = 'none'; + if (ol.has.TOUCH) { + goog.dom.classlist.add(this.viewport_, 'ol-touch'); } -}; + /** + * @private + * @type {Element} + */ + this.overlayContainer_ = goog.dom.createDom(goog.dom.TagName.DIV, + 'ol-overlaycontainer'); + goog.dom.appendChild(this.viewport_, this.overlayContainer_); + + /** + * @private + * @type {Element} + */ + this.overlayContainerStopEvent_ = goog.dom.createDom(goog.dom.TagName.DIV, + 'ol-overlaycontainer-stopevent'); + goog.events.listen(this.overlayContainerStopEvent_, [ + goog.events.EventType.CLICK, + goog.events.EventType.DBLCLICK, + goog.events.EventType.MOUSEDOWN, + goog.events.EventType.TOUCHSTART, + goog.events.EventType.MSPOINTERDOWN, + ol.MapBrowserEvent.EventType.POINTERDOWN, + goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel' + ], goog.events.Event.stopPropagation); + goog.dom.appendChild(this.viewport_, this.overlayContainerStopEvent_); + + var mapBrowserEventHandler = new ol.MapBrowserEventHandler(this); + goog.events.listen(mapBrowserEventHandler, + goog.object.getValues(ol.MapBrowserEvent.EventType), + this.handleMapBrowserEvent, false, this); + this.registerDisposable(mapBrowserEventHandler); -/** - * @param {goog.events.Event} event Change event. - * @private - */ -ol.dom.Input.prototype.handleCheckedChanged_ = function(event) { - this.target_.checked = /** @type {boolean} */ (this.getChecked()); -}; + /** + * @private + * @type {Element|Document} + */ + this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget; + /** + * @private + * @type {goog.events.KeyHandler} + */ + this.keyHandler_ = new goog.events.KeyHandler(); + goog.events.listen(this.keyHandler_, goog.events.KeyHandler.EventType.KEY, + this.handleBrowserEvent, false, this); + this.registerDisposable(this.keyHandler_); -/** - * @param {goog.events.Event} event Change event. - * @private - */ -ol.dom.Input.prototype.handleValueChanged_ = function(event) { - this.target_.value = /** @type {string} */ (this.getValue()); -}; + var mouseWheelHandler = new goog.events.MouseWheelHandler(this.viewport_); + goog.events.listen(mouseWheelHandler, + goog.events.MouseWheelHandler.EventType.MOUSEWHEEL, + this.handleBrowserEvent, false, this); + this.registerDisposable(mouseWheelHandler); -goog.provide('ol.Ellipsoid'); + /** + * @type {ol.Collection.<ol.control.Control>} + * @private + */ + this.controls_ = optionsInternal.controls; -goog.require('goog.math'); -goog.require('ol.Coordinate'); + /** + * @type {ol.Collection.<ol.interaction.Interaction>} + * @private + */ + this.interactions_ = optionsInternal.interactions; + /** + * @type {ol.Collection.<ol.Overlay>} + * @private + */ + this.overlays_ = optionsInternal.overlays; + /** + * @type {ol.renderer.Map} + * @private + */ + this.renderer_ = + new optionsInternal.rendererConstructor(this.viewport_, this); + this.registerDisposable(this.renderer_); -/** - * @constructor - * @param {number} a Major radius. - * @param {number} flattening Flattening. - */ -ol.Ellipsoid = function(a, flattening) { + /** + * @type {goog.dom.ViewportSizeMonitor} + * @private + */ + this.viewportSizeMonitor_ = new goog.dom.ViewportSizeMonitor(); + this.registerDisposable(this.viewportSizeMonitor_); /** - * @const - * @type {number} + * @type {goog.events.Key} + * @private */ - this.a = a; + this.viewportResizeListenerKey_ = null; /** - * @const - * @type {number} + * @private + * @type {ol.Coordinate} */ - this.flattening = flattening; + this.focus_ = null; /** - * @const - * @type {number} + * @private + * @type {Array.<ol.PreRenderFunction>} */ - this.b = this.a * (1 - this.flattening); + this.preRenderFunctions_ = []; /** - * @const - * @type {number} + * @private + * @type {Array.<ol.PostRenderFunction>} */ - this.eSquared = 2 * flattening - flattening * flattening; + this.postRenderFunctions_ = []; /** - * @const - * @type {number} + * @private + * @type {ol.TileQueue} */ - this.e = Math.sqrt(this.eSquared); + this.tileQueue_ = new ol.TileQueue( + goog.bind(this.getTilePriority, this), + goog.bind(this.handleTileChange_, this)); + + /** + * Uids of features to skip at rendering time. + * @type {Object.<string, boolean>} + * @private + */ + this.skippedFeatureUids_ = {}; + + goog.events.listen( + this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP), + this.handleLayerGroupChanged_, false, this); + goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW), + this.handleViewChanged_, false, this); + goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE), + this.handleSizeChanged_, false, this); + goog.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET), + this.handleTargetChanged_, false, this); + + // setProperties will trigger the rendering of the map if the map + // is "defined" already. + this.setProperties(optionsInternal.values); + + this.controls_.forEach( + /** + * @param {ol.control.Control} control Control. + * @this {ol.Map} + */ + function(control) { + control.setMap(this); + }, this); + + goog.events.listen(this.controls_, ol.CollectionEventType.ADD, + /** + * @param {ol.CollectionEvent} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, false, this); + + goog.events.listen(this.controls_, ol.CollectionEventType.REMOVE, + /** + * @param {ol.CollectionEvent} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, false, this); + + this.interactions_.forEach( + /** + * @param {ol.interaction.Interaction} interaction Interaction. + * @this {ol.Map} + */ + function(interaction) { + interaction.setMap(this); + }, this); + + goog.events.listen(this.interactions_, ol.CollectionEventType.ADD, + /** + * @param {ol.CollectionEvent} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, false, this); + + goog.events.listen(this.interactions_, ol.CollectionEventType.REMOVE, + /** + * @param {ol.CollectionEvent} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, false, this); + + this.overlays_.forEach( + /** + * @param {ol.Overlay} overlay Overlay. + * @this {ol.Map} + */ + function(overlay) { + overlay.setMap(this); + }, this); + + goog.events.listen(this.overlays_, ol.CollectionEventType.ADD, + /** + * @param {ol.CollectionEvent} event Collection event. + */ + function(event) { + event.element.setMap(this); + }, false, this); + + goog.events.listen(this.overlays_, ol.CollectionEventType.REMOVE, + /** + * @param {ol.CollectionEvent} event Collection event. + */ + function(event) { + event.element.setMap(null); + }, false, this); }; +goog.inherits(ol.Map, ol.Object); /** - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 1. - * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. - * @param {number=} opt_maxIterations Maximum iterations. - * @return {{distance: number, initialBearing: number, finalBearing: number}} - * Vincenty. + * Add the given control to the map. + * @param {ol.control.Control} control Control. + * @api stable */ -ol.Ellipsoid.prototype.vincenty = - function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { - var minDeltaLambda = goog.isDef(opt_minDeltaLambda) ? - opt_minDeltaLambda : 1e-12; - var maxIterations = goog.isDef(opt_maxIterations) ? - opt_maxIterations : 100; - var f = this.flattening; - var lat1 = goog.math.toRadians(c1[1]); - var lat2 = goog.math.toRadians(c2[1]); - var deltaLon = goog.math.toRadians(c2[0] - c1[0]); - var U1 = Math.atan((1 - f) * Math.tan(lat1)); - var cosU1 = Math.cos(U1); - var sinU1 = Math.sin(U1); - var U2 = Math.atan((1 - f) * Math.tan(lat2)); - var cosU2 = Math.cos(U2); - var sinU2 = Math.sin(U2); - var lambda = deltaLon; - var cosSquaredAlpha, sinAlpha; - var cosLambda, deltaLambda = Infinity, sinLambda; - var cos2SigmaM, cosSigma, sigma, sinSigma; - var i; - for (i = maxIterations; i > 0; --i) { - cosLambda = Math.cos(lambda); - sinLambda = Math.sin(lambda); - var x = cosU2 * sinLambda; - var y = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; - sinSigma = Math.sqrt(x * x + y * y); - if (sinSigma === 0) { - return { - distance: 0, - initialBearing: 0, - finalBearing: 0 - }; - } - cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; - sigma = Math.atan2(sinSigma, cosSigma); - sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; - cosSquaredAlpha = 1 - sinAlpha * sinAlpha; - cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSquaredAlpha; - if (isNaN(cos2SigmaM)) { - cos2SigmaM = 0; - } - var C = f / 16 * cosSquaredAlpha * (4 + f * (4 - 3 * cosSquaredAlpha)); - var lambdaPrime = deltaLon + (1 - C) * f * sinAlpha * (sigma + - C * sinSigma * (cos2SigmaM + - C * cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1))); - deltaLambda = Math.abs(lambdaPrime - lambda); - lambda = lambdaPrime; - if (deltaLambda < minDeltaLambda) { - break; - } - } - if (i === 0) { - return { - distance: NaN, - finalBearing: NaN, - initialBearing: NaN - }; - } - var aSquared = this.a * this.a; - var bSquared = this.b * this.b; - var uSquared = cosSquaredAlpha * (aSquared - bSquared) / bSquared; - var A = 1 + uSquared / 16384 * - (4096 + uSquared * (uSquared * (320 - 175 * uSquared) - 768)); - var B = uSquared / 1024 * - (256 + uSquared * (uSquared * (74 - 47 * uSquared) - 128)); - var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * - (cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1) - - B / 6 * cos2SigmaM * (4 * sinSigma * sinSigma - 3) * - (4 * cos2SigmaM * cos2SigmaM - 3))); - cosLambda = Math.cos(lambda); - sinLambda = Math.sin(lambda); - var alpha1 = Math.atan2(cosU2 * sinLambda, - cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); - var alpha2 = Math.atan2(cosU1 * sinLambda, - cosU1 * sinU2 * cosLambda - sinU1 * cosU2); - return { - distance: this.b * A * (sigma - deltaSigma), - initialBearing: goog.math.toDegrees(alpha1), - finalBearing: goog.math.toDegrees(alpha2) - }; +ol.Map.prototype.addControl = function(control) { + var controls = this.getControls(); + goog.asserts.assert(goog.isDef(controls), 'controls should be defined'); + controls.push(control); }; /** - * Returns the distance from c1 to c2 using Vincenty. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 1. - * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. - * @param {number=} opt_maxIterations Maximum iterations. - * @return {number} Vincenty distance. + * Add the given interaction to the map. + * @param {ol.interaction.Interaction} interaction Interaction to add. + * @api stable */ -ol.Ellipsoid.prototype.vincentyDistance = - function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { - var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); - return vincenty.distance; +ol.Map.prototype.addInteraction = function(interaction) { + var interactions = this.getInteractions(); + goog.asserts.assert(goog.isDef(interactions), + 'interactions should be defined'); + interactions.push(interaction); }; /** - * Returns the final bearing from c1 to c2 using Vincenty. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 1. - * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. - * @param {number=} opt_maxIterations Maximum iterations. - * @return {number} Initial bearing. + * Adds the given layer to the top of this map. If you want to add a layer + * elsewhere in the stack, use `getLayers()` and the methods available on + * {@link ol.Collection}. + * @param {ol.layer.Base} layer Layer. + * @api stable */ -ol.Ellipsoid.prototype.vincentyFinalBearing = - function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { - var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); - return vincenty.finalBearing; +ol.Map.prototype.addLayer = function(layer) { + var layers = this.getLayerGroup().getLayers(); + layers.push(layer); }; /** - * Returns the initial bearing from c1 to c2 using Vincenty. - * - * @param {ol.Coordinate} c1 Coordinate 1. - * @param {ol.Coordinate} c2 Coordinate 1. - * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. - * @param {number=} opt_maxIterations Maximum iterations. - * @return {number} Initial bearing. + * Add the given overlay to the map. + * @param {ol.Overlay} overlay Overlay. + * @api stable */ -ol.Ellipsoid.prototype.vincentyInitialBearing = - function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { - var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); - return vincenty.initialBearing; +ol.Map.prototype.addOverlay = function(overlay) { + var overlays = this.getOverlays(); + goog.asserts.assert(goog.isDef(overlays), 'overlays should be defined'); + overlays.push(overlay); }; -goog.provide('ol.ellipsoid.WGS84'); - -goog.require('ol.Ellipsoid'); - /** - * @const - * @type {ol.Ellipsoid} + * Add functions to be called before rendering. This can be used for attaching + * animations before updating the map's view. The {@link ol.animation} + * namespace provides several static methods for creating prerender functions. + * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions. + * @api */ -ol.ellipsoid.WGS84 = new ol.Ellipsoid(6378137, 1 / 298.257223563); - -goog.provide('ol.Feature'); -goog.provide('ol.feature'); +ol.Map.prototype.beforeRender = function(var_args) { + this.render(); + Array.prototype.push.apply(this.preRenderFunctions_, arguments); +}; -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.functions'); -goog.require('ol.Object'); -goog.require('ol.geom.Geometry'); -goog.require('ol.style.Style'); +/** + * @param {ol.PreRenderFunction} preRenderFunction Pre-render function. + * @return {boolean} Whether the preRenderFunction has been found and removed. + */ +ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) { + return goog.array.remove(this.preRenderFunctions_, preRenderFunction); +}; /** - * @classdesc - * A vector object for geographic features with a geometry and other - * attribute properties, similar to the features in vector file formats like - * GeoJSON. - * - * Features can be styled individually with `setStyle`; otherwise they use the - * style of their vector layer or feature overlay. - * - * Note that attribute properties are set as {@link ol.Object} properties on - * the feature object, so they are observable, and have get/set accessors. - * - * Typically, a feature has a single geometry property. You can set the - * geometry using the `setGeometry` method and get it with `getGeometry`. - * It is possible to store more than one geometry on a feature using attribute - * properties. By default, the geometry used for rendering is identified by - * the property name `geometry`. If you want to use another geometry property - * for rendering, use the `setGeometryName` method to change the attribute - * property associated with the geometry for the feature. For example: - * - * ```js - * var feature = new ol.Feature({ - * geometry: new ol.geom.Polygon(polyCoords), - * labelPoint: new ol.geom.Point(labelCoords), - * name: 'My Polygon' - * }); - * - * // get the polygon geometry - * var poly = feature.getGeometry(); - * - * // Render the feature as a point using the coordinates from labelPoint - * feature.setGeometryName('labelPoint'); - * - * // get the point geometry - * var point = feature.getGeometry(); - * ``` * - * @constructor - * @extends {ol.Object} - * @fires change Triggered when the id, the geometry or the style of the - * feature changes. - * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties - * You may pass a Geometry object directly, or an object literal - * containing properties. If you pass an object literal, you may - * include a Geometry associated with a `geometry` key. - * @api stable + * @inheritDoc */ -ol.Feature = function(opt_geometryOrProperties) { - - goog.base(this); - - /** - * @private - * @type {number|string|undefined} - */ - this.id_ = undefined; - - /** - * @type {string} - * @private - */ - this.geometryName_ = 'geometry'; - - /** - * User provided style. - * @private - * @type {ol.style.Style|Array.<ol.style.Style>| - * ol.feature.FeatureStyleFunction} - */ - this.style_ = null; - - /** - * @private - * @type {ol.feature.FeatureStyleFunction|undefined} - */ - this.styleFunction_ = undefined; - - /** - * @private - * @type {goog.events.Key} - */ - this.geometryChangeKey_ = null; - - goog.events.listen( - this, ol.Object.getChangeEventType(this.geometryName_), - this.handleGeometryChanged_, false, this); - - if (goog.isDef(opt_geometryOrProperties)) { - if (opt_geometryOrProperties instanceof ol.geom.Geometry || - goog.isNull(opt_geometryOrProperties)) { - var geometry = /** @type {ol.geom.Geometry} */ (opt_geometryOrProperties); - this.setGeometry(geometry); - } else { - goog.asserts.assert(goog.isObject(opt_geometryOrProperties)); - var properties = /** @type {Object.<string, *>} */ - (opt_geometryOrProperties); - this.setProperties(properties); - } - } +ol.Map.prototype.disposeInternal = function() { + goog.dom.removeNode(this.viewport_); + goog.base(this, 'disposeInternal'); }; -goog.inherits(ol.Feature, ol.Object); /** - * Clone this feature. If the original feature has a geometry it - * is also cloned. The feature id is not set in the clone. - * @return {ol.Feature} The clone. + * Detect features that intersect a pixel on the viewport, and execute a + * callback with each intersecting feature. Layers included in the detection can + * be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature + * callback. The callback will be called with two arguments. The first + * argument is one {@link ol.Feature feature} at the pixel, the second is + * the {@link ol.layer.Layer layer} of the feature. To stop detection, + * callback functions can return a truthy value. + * @param {S=} opt_this Value to use as `this` when executing `callback`. + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer + * filter function. The filter function will receive one argument, the + * {@link ol.layer.Layer layer-candidate} and it should return a boolean + * value. Only layers which are visible and for which this function returns + * `true` will be tested for features. By default, all visible layers will + * be tested. + * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T,U * @api stable */ -ol.Feature.prototype.clone = function() { - var clone = new ol.Feature(this.getProperties()); - clone.setGeometryName(this.getGeometryName()); - var geometry = this.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - clone.setGeometry(geometry.clone()); - } - var style = this.getStyle(); - if (!goog.isNull(style)) { - clone.setStyle(style); +ol.Map.prototype.forEachFeatureAtPixel = + function(pixel, callback, opt_this, opt_layerFilter, opt_this2) { + if (goog.isNull(this.frameState_)) { + return; } - return clone; + var coordinate = this.getCoordinateFromPixel(pixel); + var thisArg = goog.isDef(opt_this) ? opt_this : null; + var layerFilter = goog.isDef(opt_layerFilter) ? + opt_layerFilter : goog.functions.TRUE; + var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null; + return this.renderer_.forEachFeatureAtCoordinate( + coordinate, this.frameState_, callback, thisArg, + layerFilter, thisArg2); }; /** - * @return {ol.geom.Geometry|undefined} Returns the Geometry associated - * with this feature using the current geometry name property. By - * default, this is `geometry` but it may be changed by calling - * `setGeometryName`. + * Detect layers that have a color value at a pixel on the viewport, and + * execute a callback with each matching layer. Layers included in the + * detection can be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, ol.layer.Layer): T} callback Layer + * callback. Will receive one argument, the {@link ol.layer.Layer layer} + * that contains the color pixel. To stop detection, callback functions can + * return a truthy value. + * @param {S=} opt_this Value to use as `this` when executing `callback`. + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer + * filter function. The filter function will receive one argument, the + * {@link ol.layer.Layer layer-candidate} and it should return a boolean + * value. Only layers which are visible and for which this function returns + * `true` will be tested for features. By default, all visible layers will + * be tested. + * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T,U * @api stable - * @observable */ -ol.Feature.prototype.getGeometry = function() { - return /** @type {ol.geom.Geometry|undefined} */ ( - this.get(this.geometryName_)); +ol.Map.prototype.forEachLayerAtPixel = + function(pixel, callback, opt_this, opt_layerFilter, opt_this2) { + if (goog.isNull(this.frameState_)) { + return; + } + var thisArg = goog.isDef(opt_this) ? opt_this : null; + var layerFilter = goog.isDef(opt_layerFilter) ? + opt_layerFilter : goog.functions.TRUE; + var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null; + return this.renderer_.forEachLayerAtPixel( + pixel, this.frameState_, callback, thisArg, + layerFilter, thisArg2); }; -goog.exportProperty( - ol.Feature.prototype, - 'getGeometry', - ol.Feature.prototype.getGeometry); /** - * @return {number|string|undefined} Id. - * @api stable + * Detect if features intersect a pixel on the viewport. Layers included in the + * detection can be configured through `opt_layerFilter`. + * @param {ol.Pixel} pixel Pixel. + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer + * filter function. The filter function will receive one argument, the + * {@link ol.layer.Layer layer-candidate} and it should return a boolean + * value. Only layers which are visible and for which this function returns + * `true` will be tested for features. By default, all visible layers will + * be tested. + * @param {U=} opt_this Value to use as `this` when executing `layerFilter`. + * @return {boolean} Is there a feature at the given pixel? + * @template U + * @api */ -ol.Feature.prototype.getId = function() { - return this.id_; +ol.Map.prototype.hasFeatureAtPixel = + function(pixel, opt_layerFilter, opt_this) { + if (goog.isNull(this.frameState_)) { + return false; + } + var coordinate = this.getCoordinateFromPixel(pixel); + var layerFilter = goog.isDef(opt_layerFilter) ? + opt_layerFilter : goog.functions.TRUE; + var thisArg = goog.isDef(opt_this) ? opt_this : null; + return this.renderer_.hasFeatureAtCoordinate( + coordinate, this.frameState_, layerFilter, thisArg); }; /** - * @return {string} Get the property name associated with the geometry for - * this feature. By default, this is `geometry` but it may be changed by - * calling `setGeometryName`. + * Returns the geographical coordinate for a browser event. + * @param {Event} event Event. + * @return {ol.Coordinate} Coordinate. * @api stable */ -ol.Feature.prototype.getGeometryName = function() { - return this.geometryName_; +ol.Map.prototype.getEventCoordinate = function(event) { + return this.getCoordinateFromPixel(this.getEventPixel(event)); }; /** - * @return {ol.style.Style|Array.<ol.style.Style>| - * ol.feature.FeatureStyleFunction} Return the style as set by `setStyle` - * in the same format that it was provided in. If `setStyle` has not been - * called, or if it was called with `null`, then `getStyle()` will return - * `null`. + * Returns the map pixel position for a browser event relative to the viewport. + * @param {Event} event Event. + * @return {ol.Pixel} Pixel. * @api stable */ -ol.Feature.prototype.getStyle = function() { - return this.style_; +ol.Map.prototype.getEventPixel = function(event) { + // goog.style.getRelativePosition is based on event.targetTouches, + // but touchend and touchcancel events have no targetTouches when + // the last finger is removed from the screen. + // So we ourselves compute the position of touch events. + // See https://github.com/google/closure-library/pull/323 + if (goog.isDef(event.changedTouches)) { + var touch = event.changedTouches[0]; + var viewportPosition = goog.style.getClientPosition(this.viewport_); + return [ + touch.clientX - viewportPosition.x, + touch.clientY - viewportPosition.y + ]; + } else { + var eventPosition = goog.style.getRelativePosition(event, this.viewport_); + return [eventPosition.x, eventPosition.y]; + } }; /** - * @return {ol.feature.FeatureStyleFunction|undefined} Return a function - * representing the current style of this feature. + * Get the target in which this map is rendered. + * Note that this returns what is entered as an option or in setTarget: + * if that was an element, it returns an element; if a string, it returns that. + * @return {Element|string|undefined} The Element or id of the Element that the + * map is rendered in. + * @observable * @api stable */ -ol.Feature.prototype.getStyleFunction = function() { - return this.styleFunction_; +ol.Map.prototype.getTarget = function() { + return /** @type {Element|string|undefined} */ ( + this.get(ol.MapProperty.TARGET)); }; /** - * @private + * Get the DOM element into which this map is rendered. In contrast to + * `getTarget` this method always return an `Element`, or `null` if the + * map has no target. + * @return {Element} The element that the map is rendered in. + * @api */ -ol.Feature.prototype.handleGeometryChange_ = function() { - this.changed(); +ol.Map.prototype.getTargetElement = function() { + var target = this.getTarget(); + return goog.isDef(target) ? goog.dom.getElement(target) : null; }; /** - * @private + * Get the coordinate for a given pixel. This returns a coordinate in the + * map view projection. + * @param {ol.Pixel} pixel Pixel position in the map viewport. + * @return {ol.Coordinate} The coordinate for the pixel position. + * @api stable */ -ol.Feature.prototype.handleGeometryChanged_ = function() { - if (!goog.isNull(this.geometryChangeKey_)) { - goog.events.unlistenByKey(this.geometryChangeKey_); - this.geometryChangeKey_ = null; - } - var geometry = this.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - this.geometryChangeKey_ = goog.events.listen(geometry, - goog.events.EventType.CHANGE, this.handleGeometryChange_, false, this); - this.changed(); +ol.Map.prototype.getCoordinateFromPixel = function(pixel) { + var frameState = this.frameState_; + if (goog.isNull(frameState)) { + return null; + } else { + var vec2 = pixel.slice(); + return ol.vec.Mat4.multVec2(frameState.pixelToCoordinateMatrix, vec2, vec2); } }; /** - * @param {ol.geom.Geometry|undefined} geometry Set the geometry for this - * feature. This will update the property associated with the current - * geometry property name. By default, this is `geometry` but it can be - * changed by calling `setGeometryName`. + * Get the map controls. Modifying this collection changes the controls + * associated with the map. + * @return {ol.Collection.<ol.control.Control>} Controls. * @api stable - * @observable */ -ol.Feature.prototype.setGeometry = function(geometry) { - this.set(this.geometryName_, geometry); +ol.Map.prototype.getControls = function() { + return this.controls_; }; -goog.exportProperty( - ol.Feature.prototype, - 'setGeometry', - ol.Feature.prototype.setGeometry); /** - * Set the style for the feature. This can be a single style object, an array - * of styles, or a function that takes a resolution and returns an array of - * styles. If it is `null` the feature has no style (a `null` style). - * @param {ol.style.Style|Array.<ol.style.Style>| - * ol.feature.FeatureStyleFunction} style Style for this feature. + * Get the map overlays. Modifying this collection changes the overlays + * associated with the map. + * @return {ol.Collection.<ol.Overlay>} Overlays. * @api stable */ -ol.Feature.prototype.setStyle = function(style) { - this.style_ = style; - this.styleFunction_ = goog.isNull(style) ? - undefined : ol.feature.createFeatureStyleFunction(style); - this.changed(); +ol.Map.prototype.getOverlays = function() { + return this.overlays_; }; /** - * @param {number|string|undefined} id Set a unique id for this feature. - * The id may be used to retrieve a feature from a vector source with the - * {@link ol.source.Vector#getFeatureById} method. + * Get the map interactions. Modifying this collection changes the interactions + * associated with the map. + * + * Interactions are used for e.g. pan, zoom and rotate. + * @return {ol.Collection.<ol.interaction.Interaction>} Interactions. * @api stable */ -ol.Feature.prototype.setId = function(id) { - this.id_ = id; - this.changed(); +ol.Map.prototype.getInteractions = function() { + return this.interactions_; }; /** - * @param {string} name Set the property name from which this feature's - * geometry will be fetched when calling `getGeometry`. + * Get the layergroup associated with this map. + * @return {ol.layer.Group} A layer group containing the layers in this map. + * @observable * @api stable */ -ol.Feature.prototype.setGeometryName = function(name) { - goog.events.unlisten( - this, ol.Object.getChangeEventType(this.geometryName_), - this.handleGeometryChanged_, false, this); - this.geometryName_ = name; - goog.events.listen( - this, ol.Object.getChangeEventType(this.geometryName_), - this.handleGeometryChanged_, false, this); - this.handleGeometryChanged_(); +ol.Map.prototype.getLayerGroup = function() { + return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP)); }; /** - * A function that takes a `{number}` representing the view's resolution. It - * returns an Array of {@link ol.style.Style}. This way individual features - * can be styled. The this keyword inside the function references the - * {@link ol.Feature} to be styled. - * - * @typedef {function(this: ol.Feature, number): Array.<ol.style.Style>} + * Get the collection of layers associated with this map. + * @return {!ol.Collection.<ol.layer.Base>} Layers. * @api stable */ -ol.feature.FeatureStyleFunction; +ol.Map.prototype.getLayers = function() { + var layers = this.getLayerGroup().getLayers(); + return layers; +}; /** - * Convert the provided object into a feature style function. Functions passed - * through unchanged. Arrays of ol.style.Style or single style objects wrapped - * in a new feature style function. - * @param {ol.feature.FeatureStyleFunction|!Array.<ol.style.Style>| - * !ol.style.Style} obj A feature style function, a single style, or an - * array of styles. - * @return {ol.feature.FeatureStyleFunction} A style function. + * Get the pixel for a coordinate. This takes a coordinate in the map view + * projection and returns the corresponding pixel. + * @param {ol.Coordinate} coordinate A map coordinate. + * @return {ol.Pixel} A pixel position in the map viewport. + * @api stable */ -ol.feature.createFeatureStyleFunction = function(obj) { - /** - * @type {ol.feature.FeatureStyleFunction} - */ - var styleFunction; - - if (goog.isFunction(obj)) { - styleFunction = /** @type {ol.feature.FeatureStyleFunction} */ (obj); +ol.Map.prototype.getPixelFromCoordinate = function(coordinate) { + var frameState = this.frameState_; + if (goog.isNull(frameState)) { + return null; } else { - /** - * @type {Array.<ol.style.Style>} - */ - var styles; - if (goog.isArray(obj)) { - styles = obj; - } else { - goog.asserts.assertInstanceof(obj, ol.style.Style); - styles = [obj]; - } - styleFunction = goog.functions.constant(styles); + var vec2 = coordinate.slice(0, 2); + return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2); } - return styleFunction; }; -goog.provide('ol.FeatureOverlay'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('ol.Collection'); -goog.require('ol.CollectionEventType'); -goog.require('ol.Feature'); -goog.require('ol.render.EventType'); -goog.require('ol.renderer.vector'); -goog.require('ol.style.Style'); - - /** - * @classdesc - * A mechanism for changing the style of a small number of features on a - * temporary basis, for example highlighting. This is necessary with the Canvas - * renderer, where, unlike in SVG, features cannot be individually referenced. - * See examples/vector-layers for an example: create a FeatureOverlay with a - * different style, copy the feature(s) you want rendered in this different - * style into it, and then remove them again when you're finished. - * - * @constructor - * @param {olx.FeatureOverlayOptions=} opt_options Options. - * @api + * Get the map renderer. + * @return {ol.renderer.Map} Renderer */ -ol.FeatureOverlay = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {ol.Collection.<ol.Feature>} - */ - this.features_ = null; - - /** - * @private - * @type {Array.<goog.events.Key>} - */ - this.featuresListenerKeys_ = null; - - /** - * @private - * @type {Object.<string, goog.events.Key>} - */ - this.featureChangeListenerKeys_ = null; - - /** - * @private - * @type {ol.Map} - */ - this.map_ = null; - - /** - * @private - * @type {goog.events.Key} - */ - this.postComposeListenerKey_ = null; - - /** - * @private - * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} - */ - this.style_ = null; - - /** - * @private - * @type {ol.style.StyleFunction|undefined} - */ - this.styleFunction_ = undefined; - - this.setStyle(goog.isDef(options.style) ? - options.style : ol.style.defaultStyleFunction); - - if (goog.isDef(options.features)) { - if (goog.isArray(options.features)) { - this.setFeatures(new ol.Collection(goog.array.clone(options.features))); - } else { - goog.asserts.assertInstanceof(options.features, ol.Collection); - this.setFeatures(options.features); - } - } else { - this.setFeatures(new ol.Collection()); - } +ol.Map.prototype.getRenderer = function() { + return this.renderer_; +}; - if (goog.isDef(options.map)) { - this.setMap(options.map); - } +/** + * Get the size of this map. + * @return {ol.Size|undefined} The size in pixels of the map in the DOM. + * @observable + * @api stable + */ +ol.Map.prototype.getSize = function() { + return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE)); }; /** - * @param {ol.Feature} feature Feature. - * @api + * Get the view associated with this map. A view manages properties such as + * center and resolution. + * @return {ol.View} The view that controls this map. + * @observable + * @api stable */ -ol.FeatureOverlay.prototype.addFeature = function(feature) { - this.features_.push(feature); +ol.Map.prototype.getView = function() { + return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW)); }; /** - * @return {ol.Collection.<ol.Feature>} Features collection. - * @api + * Get the element that serves as the map viewport. + * @return {Element} Viewport. + * @api stable */ -ol.FeatureOverlay.prototype.getFeatures = function() { - return this.features_; +ol.Map.prototype.getViewport = function() { + return this.viewport_; }; /** - * @private + * Get the element that serves as the container for overlays. Elements added to + * this container will let mousedown and touchstart events through to the map, + * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent} + * events. + * @return {Element} The map's overlay container. */ -ol.FeatureOverlay.prototype.handleFeatureChange_ = function() { - this.render_(); +ol.Map.prototype.getOverlayContainer = function() { + return this.overlayContainer_; }; /** - * @private - * @param {ol.CollectionEvent} collectionEvent Collection event. + * Get the element that serves as a container for overlays that don't allow + * event propagation. Elements added to this container won't let mousedown and + * touchstart events through to the map, so clicks and gestures on an overlay + * don't trigger any {@link ol.MapBrowserEvent}. + * @return {Element} The map's overlay container that stops events. */ -ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) { - goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_)); - var feature = /** @type {ol.Feature} */ (collectionEvent.element); - this.featureChangeListenerKeys_[goog.getUid(feature).toString()] = - goog.events.listen(feature, goog.events.EventType.CHANGE, - this.handleFeatureChange_, false, this); - this.render_(); +ol.Map.prototype.getOverlayContainerStopEvent = function() { + return this.overlayContainerStopEvent_; }; /** - * @private - * @param {ol.CollectionEvent} collectionEvent Collection event. + * @param {ol.Tile} tile Tile. + * @param {string} tileSourceKey Tile source key. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + * @return {number} Tile priority. */ -ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) { - goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_)); - var feature = /** @type {ol.Feature} */ (collectionEvent.element); - var key = goog.getUid(feature).toString(); - goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]); - delete this.featureChangeListenerKeys_[key]; - this.render_(); +ol.Map.prototype.getTilePriority = + function(tile, tileSourceKey, tileCenter, tileResolution) { + // Filter out tiles at higher zoom levels than the current zoom level, or that + // are outside the visible extent. + var frameState = this.frameState_; + if (goog.isNull(frameState) || !(tileSourceKey in frameState.wantedTiles)) { + return ol.structs.PriorityQueue.DROP; + } + var coordKey = ol.tilecoord.toString(tile.tileCoord); + if (!frameState.wantedTiles[tileSourceKey][coordKey]) { + return ol.structs.PriorityQueue.DROP; + } + // Prioritize the highest zoom level tiles closest to the focus. + // Tiles at higher zoom levels are prioritized using Math.log(tileResolution). + // Within a zoom level, tiles are prioritized by the distance in pixels + // between the center of the tile and the focus. The factor of 65536 means + // that the prioritization should behave as desired for tiles up to + // 65536 * Math.log(2) = 45426 pixels from the focus. + var deltaX = tileCenter[0] - frameState.focus[0]; + var deltaY = tileCenter[1] - frameState.focus[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; }; /** - * Handle changes in image style state. - * @param {goog.events.Event} event Image style change event. - * @private + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @param {string=} opt_type Type. */ -ol.FeatureOverlay.prototype.handleImageChange_ = function(event) { - this.render_(); +ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) { + var type = opt_type || browserEvent.type; + var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent); + this.handleMapBrowserEvent(mapBrowserEvent); }; /** - * @param {ol.render.Event} event Event. - * @private + * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle. */ -ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) { - if (goog.isNull(this.features_)) { +ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { + if (goog.isNull(this.frameState_)) { + // With no view defined, we cannot translate pixels into geographical + // coordinates so interactions cannot be used. return; } - var styleFunction = this.styleFunction_; - if (!goog.isDef(styleFunction)) { - styleFunction = ol.style.defaultStyleFunction; - } - var replayGroup = /** @type {ol.render.IReplayGroup} */ - (event.replayGroup); - goog.asserts.assert(goog.isDef(replayGroup)); - var frameState = event.frameState; - var pixelRatio = frameState.pixelRatio; - var resolution = frameState.viewState.resolution; - var squaredTolerance = ol.renderer.vector.getSquaredTolerance(resolution, - pixelRatio); - var i, ii, styles, featureStyleFunction; - this.features_.forEach(function(feature) { - featureStyleFunction = feature.getStyleFunction(); - styles = goog.isDef(featureStyleFunction) ? - featureStyleFunction.call(feature, resolution) : - styleFunction(feature, resolution); - - if (!goog.isDefAndNotNull(styles)) { - return; - } - ii = styles.length; - for (i = 0; i < ii; ++i) { - ol.renderer.vector.renderFeature(replayGroup, feature, styles[i], - squaredTolerance, this.handleImageChange_, this); + this.focus_ = mapBrowserEvent.coordinate; + mapBrowserEvent.frameState = this.frameState_; + var interactions = this.getInteractions(); + goog.asserts.assert(goog.isDef(interactions), + 'interactions should be defined'); + var interactionsArray = interactions.getArray(); + var i; + if (this.dispatchEvent(mapBrowserEvent) !== false) { + for (i = interactionsArray.length - 1; i >= 0; i--) { + var interaction = interactionsArray[i]; + if (!interaction.getActive()) { + continue; + } + var cont = interaction.handleEvent(mapBrowserEvent); + if (!cont) { + break; + } } - }, this); + } }; /** - * @param {ol.Feature} feature Feature. - * @api + * @protected */ -ol.FeatureOverlay.prototype.removeFeature = function(feature) { - this.features_.remove(feature); -}; +ol.Map.prototype.handlePostRender = function() { + var frameState = this.frameState_; -/** - * @private - */ -ol.FeatureOverlay.prototype.render_ = function() { - if (!goog.isNull(this.map_)) { - this.map_.render(); + // Manage the tile queue + // Image loads are expensive and a limited resource, so try to use them + // efficiently: + // * When the view is static we allow a large number of parallel tile loads + // to complete the frame as quickly as possible. + // * When animating or interacting, image loads can cause janks, so we reduce + // the maximum number of loads per frame and limit the number of parallel + // tile loads to remain reactive to view changes and to reduce the chance of + // loading tiles that will quickly disappear from view. + var tileQueue = this.tileQueue_; + if (!tileQueue.isEmpty()) { + var maxTotalLoading = 16; + var maxNewLoads = maxTotalLoading; + var tileSourceCount = 0; + if (!goog.isNull(frameState)) { + var hints = frameState.viewHints; + if (hints[ol.ViewHint.ANIMATING]) { + maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0; + maxNewLoads = 2; + } + if (hints[ol.ViewHint.INTERACTING]) { + maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0; + maxNewLoads = 2; + } + tileSourceCount = goog.object.getCount(frameState.wantedTiles); + } + maxTotalLoading *= tileSourceCount; + maxNewLoads *= tileSourceCount; + if (tileQueue.getTilesLoading() < maxTotalLoading) { + tileQueue.reprioritize(); // FIXME only call if view has changed + tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads); + } + } + + var postRenderFunctions = this.postRenderFunctions_; + var i, ii; + for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) { + postRenderFunctions[i](this, frameState); } + postRenderFunctions.length = 0; }; /** - * @param {ol.Collection.<ol.Feature>} features Features collection. - * @api + * @private */ -ol.FeatureOverlay.prototype.setFeatures = function(features) { - if (!goog.isNull(this.featuresListenerKeys_)) { - goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey); - this.featuresListenerKeys_ = null; - } - if (!goog.isNull(this.featureChangeListenerKeys_)) { - goog.array.forEach( - goog.object.getValues(this.featureChangeListenerKeys_), - goog.events.unlistenByKey); - this.featureChangeListenerKeys_ = null; - } - this.features_ = features; - if (!goog.isNull(features)) { - this.featuresListenerKeys_ = [ - goog.events.listen(features, ol.CollectionEventType.ADD, - this.handleFeaturesAdd_, false, this), - goog.events.listen(features, ol.CollectionEventType.REMOVE, - this.handleFeaturesRemove_, false, this) - ]; - this.featureChangeListenerKeys_ = {}; - features.forEach(function(feature) { - this.featureChangeListenerKeys_[goog.getUid(feature).toString()] = - goog.events.listen(feature, goog.events.EventType.CHANGE, - this.handleFeatureChange_, false, this); - }, this); - } - this.render_(); +ol.Map.prototype.handleSizeChanged_ = function() { + this.render(); }; /** - * @param {ol.Map} map Map. - * @api + * @private */ -ol.FeatureOverlay.prototype.setMap = function(map) { - if (!goog.isNull(this.postComposeListenerKey_)) { - goog.events.unlistenByKey(this.postComposeListenerKey_); - this.postComposeListenerKey_ = null; - } - this.render_(); - this.map_ = map; - if (!goog.isNull(map)) { - this.postComposeListenerKey_ = goog.events.listen( - map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false, - this); - map.render(); +ol.Map.prototype.handleTargetChanged_ = function() { + // target may be undefined, null, a string or an Element. + // If it's a string we convert it to an Element before proceeding. + // If it's not now an Element we remove the viewport from the DOM. + // If it's an Element we append the viewport element to it. + + var targetElement = this.getTargetElement(); + + this.keyHandler_.detach(); + + if (goog.isNull(targetElement)) { + goog.dom.removeNode(this.viewport_); + if (!goog.isNull(this.viewportResizeListenerKey_)) { + goog.events.unlistenByKey(this.viewportResizeListenerKey_); + this.viewportResizeListenerKey_ = null; + } + } else { + goog.dom.appendChild(targetElement, this.viewport_); + + var keyboardEventTarget = goog.isNull(this.keyboardEventTarget_) ? + targetElement : this.keyboardEventTarget_; + this.keyHandler_.attach(keyboardEventTarget); + + if (goog.isNull(this.viewportResizeListenerKey_)) { + this.viewportResizeListenerKey_ = goog.events.listen( + this.viewportSizeMonitor_, goog.events.EventType.RESIZE, + this.updateSize, false, this); + } } + + this.updateSize(); + // updateSize calls setSize, so no need to call this.render + // ourselves here. }; /** - * Set the style for features. This can be a single style object, an array - * of styles, or a function that takes a feature and resolution and returns - * an array of styles. - * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} style - * Overlay style. - * @api + * @private */ -ol.FeatureOverlay.prototype.setStyle = function(style) { - this.style_ = style; - this.styleFunction_ = ol.style.createStyleFunction(style); - this.render_(); +ol.Map.prototype.handleTileChange_ = function() { + this.render(); }; /** - * Get the style for features. This returns whatever was passed to the `style` - * option at construction or to the `setStyle` method. - * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} - * Overlay style. - * @api + * @private */ -ol.FeatureOverlay.prototype.getStyle = function() { - return this.style_; +ol.Map.prototype.handleViewPropertyChanged_ = function() { + this.render(); }; /** - * Get the style function. - * @return {ol.style.StyleFunction|undefined} Style function. - * @api + * @private */ -ol.FeatureOverlay.prototype.getStyleFunction = function() { - return this.styleFunction_; +ol.Map.prototype.handleViewChanged_ = function() { + if (!goog.isNull(this.viewPropertyListenerKey_)) { + goog.events.unlistenByKey(this.viewPropertyListenerKey_); + this.viewPropertyListenerKey_ = null; + } + var view = this.getView(); + if (!goog.isNull(view)) { + this.viewPropertyListenerKey_ = goog.events.listen( + view, ol.ObjectEventType.PROPERTYCHANGE, + this.handleViewPropertyChanged_, false, this); + } + this.render(); }; -goog.provide('ol.format.Feature'); - -goog.require('goog.array'); -goog.require('ol.geom.Geometry'); -goog.require('ol.proj'); - - /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for feature formats. - * {ol.format.Feature} subclasses provide the ability to decode and encode - * {@link ol.Feature} objects from a variety of commonly used geospatial - * file formats. See the documentation for each format for more details. - * - * @constructor - * @api stable + * @param {goog.events.Event} event Event. + * @private */ -ol.format.Feature = function() { - - /** - * @protected - * @type {ol.proj.Projection} - */ - this.defaultDataProjection = null; +ol.Map.prototype.handleLayerGroupMemberChanged_ = function(event) { + goog.asserts.assertInstanceof(event, goog.events.Event, + 'event should be an Event'); + this.render(); }; /** - * @return {Array.<string>} Extensions. + * @param {ol.ObjectEvent} event Event. + * @private */ -ol.format.Feature.prototype.getExtensions = goog.abstractMethod; +ol.Map.prototype.handleLayerGroupPropertyChanged_ = function(event) { + goog.asserts.assertInstanceof(event, ol.ObjectEvent, + 'event should be an ol.ObjectEvent'); + this.render(); +}; /** - * Adds the data projection to the read options. - * @param {Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Options. - * @return {olx.format.ReadOptions|undefined} Options. - * @protected + * @private */ -ol.format.Feature.prototype.getReadOptions = function( - source, opt_options) { - var options; - if (goog.isDef(opt_options)) { - options = { - dataProjection: goog.isDef(opt_options.dataProjection) ? - opt_options.dataProjection : this.readProjection(source), - featureProjection: opt_options.featureProjection - }; +ol.Map.prototype.handleLayerGroupChanged_ = function() { + if (!goog.isNull(this.layerGroupPropertyListenerKeys_)) { + var length = this.layerGroupPropertyListenerKeys_.length; + for (var i = 0; i < length; ++i) { + goog.events.unlistenByKey(this.layerGroupPropertyListenerKeys_[i]); + } + this.layerGroupPropertyListenerKeys_ = null; + } + var layerGroup = this.getLayerGroup(); + if (goog.isDefAndNotNull(layerGroup)) { + this.layerGroupPropertyListenerKeys_ = [ + goog.events.listen( + layerGroup, ol.ObjectEventType.PROPERTYCHANGE, + this.handleLayerGroupPropertyChanged_, false, this), + goog.events.listen( + layerGroup, goog.events.EventType.CHANGE, + this.handleLayerGroupMemberChanged_, false, this) + ]; } - return this.adaptOptions(options); + this.render(); }; /** - * Sets the `defaultDataProjection` on the options, if no `dataProjection` - * is set. - * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options - * Options. - * @protected - * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined} - * Updated options. + * Returns `true` if the map is defined, `false` otherwise. The map is defined + * if it is contained in `document`, visible, has non-zero height and width, and + * has a defined view. + * @return {boolean} Is defined. */ -ol.format.Feature.prototype.adaptOptions = function( - options) { - var updatedOptions; - if (goog.isDef(options)) { - updatedOptions = { - featureProjection: options.featureProjection, - dataProjection: goog.isDefAndNotNull(options.dataProjection) ? - options.dataProjection : this.defaultDataProjection - }; +ol.Map.prototype.isDef = function() { + if (!goog.dom.contains(document, this.viewport_)) { + return false; } - return updatedOptions; + if (!goog.style.isElementShown(this.viewport_)) { + return false; + } + var size = this.getSize(); + if (!goog.isDefAndNotNull(size) || size[0] <= 0 || size[1] <= 0) { + return false; + } + var view = this.getView(); + if (goog.isNull(view) || !view.isDef()) { + return false; + } + return true; }; /** - * @return {ol.format.FormatType} Format. + * @return {boolean} Is rendered. */ -ol.format.Feature.prototype.getType = goog.abstractMethod; +ol.Map.prototype.isRendered = function() { + return !goog.isNull(this.frameState_); +}; /** - * Read a single feature from a source. - * - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. + * Requests an immediate render in a synchronous manner. + * @api stable */ -ol.format.Feature.prototype.readFeature = goog.abstractMethod; +ol.Map.prototype.renderSync = function() { + this.animationDelay_.fire(); +}; /** - * Read all features from a source. - * - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. + * Request a map rendering (at the next animation frame). + * @api stable */ -ol.format.Feature.prototype.readFeatures = goog.abstractMethod; +ol.Map.prototype.render = function() { + if (!this.animationDelay_.isActive()) { + this.animationDelay_.start(); + } +}; /** - * Read a single geometry from a source. - * - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.geom.Geometry} Geometry. + * Remove the given control from the map. + * @param {ol.control.Control} control Control. + * @return {ol.control.Control|undefined} The removed control (or undefined + * if the control was not found). + * @api stable */ -ol.format.Feature.prototype.readGeometry = goog.abstractMethod; +ol.Map.prototype.removeControl = function(control) { + var controls = this.getControls(); + goog.asserts.assert(goog.isDef(controls), 'controls should be defined'); + if (goog.isDef(controls.remove(control))) { + return control; + } + return undefined; +}; /** - * Read the projection from a source. - * - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.proj.Projection} Projection. + * Remove the given interaction from the map. + * @param {ol.interaction.Interaction} interaction Interaction to remove. + * @return {ol.interaction.Interaction|undefined} The removed interaction (or + * undefined if the interaction was not found). + * @api stable */ -ol.format.Feature.prototype.readProjection = goog.abstractMethod; +ol.Map.prototype.removeInteraction = function(interaction) { + var removed; + var interactions = this.getInteractions(); + goog.asserts.assert(goog.isDef(interactions), + 'interactions should be defined'); + if (goog.isDef(interactions.remove(interaction))) { + removed = interaction; + } + return removed; +}; /** - * Encode a feature in this format. - * - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} Result. + * Removes the given layer from the map. + * @param {ol.layer.Base} layer Layer. + * @return {ol.layer.Base|undefined} The removed layer (or undefined if the + * layer was not found). + * @api stable */ -ol.format.Feature.prototype.writeFeature = goog.abstractMethod; +ol.Map.prototype.removeLayer = function(layer) { + var layers = this.getLayerGroup().getLayers(); + return layers.remove(layer); +}; /** - * Encode an array of features in this format. - * - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} Result. + * Remove the given overlay from the map. + * @param {ol.Overlay} overlay Overlay. + * @return {ol.Overlay|undefined} The removed overlay (or undefined + * if the overlay was not found). + * @api stable */ -ol.format.Feature.prototype.writeFeatures = goog.abstractMethod; +ol.Map.prototype.removeOverlay = function(overlay) { + var overlays = this.getOverlays(); + goog.asserts.assert(goog.isDef(overlays), 'overlays should be defined'); + if (goog.isDef(overlays.remove(overlay))) { + return overlay; + } + return undefined; +}; /** - * Write a single geometry in this format. - * - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} Result. + * @param {number} time Time. + * @private */ -ol.format.Feature.prototype.writeGeometry = goog.abstractMethod; +ol.Map.prototype.renderFrame_ = function(time) { + var i, ii, viewState; -/** - * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. - * @param {boolean} write Set to true for writing, false for reading. - * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options - * Options. - * @return {ol.geom.Geometry|ol.Extent} Transformed geometry. - * @protected - */ -ol.format.Feature.transformWithOptions = function( - geometry, write, opt_options) { - var featureProjection = goog.isDef(opt_options) ? - ol.proj.get(opt_options.featureProjection) : null; - var dataProjection = goog.isDef(opt_options) ? - ol.proj.get(opt_options.dataProjection) : null; - if (!goog.isNull(featureProjection) && !goog.isNull(dataProjection) && - !ol.proj.equivalent(featureProjection, dataProjection)) { - if (geometry instanceof ol.geom.Geometry) { - return (write ? geometry.clone() : geometry).transform( - write ? featureProjection : dataProjection, - write ? dataProjection : featureProjection); - } else { - // FIXME this is necessary because ol.format.GML treats extents - // as geometries - return ol.proj.transformExtent( - write ? goog.array.clone(geometry) : geometry, - write ? featureProjection : dataProjection, - write ? dataProjection : featureProjection); + var size = this.getSize(); + var view = this.getView(); + /** @type {?olx.FrameState} */ + var frameState = null; + if (goog.isDef(size) && ol.size.hasArea(size) && + !goog.isNull(view) && view.isDef()) { + var viewHints = view.getHints(); + var layerStatesArray = this.getLayerGroup().getLayerStatesArray(); + var layerStates = {}; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerStates[goog.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; } - } else { - return geometry; + viewState = view.getState(); + frameState = /** @type {olx.FrameState} */ ({ + animate: false, + attributions: {}, + coordinateToPixelMatrix: this.coordinateToPixelMatrix_, + extent: null, + focus: goog.isNull(this.focus_) ? viewState.center : this.focus_, + index: this.frameIndex_++, + layerStates: layerStates, + layerStatesArray: layerStatesArray, + logos: goog.object.clone(this.logos_), + pixelRatio: this.pixelRatio_, + pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_, + postRenderFunctions: [], + size: size, + skippedFeatureUids: this.skippedFeatureUids_, + tileQueue: this.tileQueue_, + time: time, + usedTiles: {}, + viewState: viewState, + viewHints: viewHints, + wantedTiles: {} + }); } -}; -goog.provide('ol.format.FormatType'); + if (!goog.isNull(frameState)) { + var preRenderFunctions = this.preRenderFunctions_; + var n = 0, preRenderFunction; + for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) { + preRenderFunction = preRenderFunctions[i]; + if (preRenderFunction(this, frameState)) { + preRenderFunctions[n++] = preRenderFunction; + } + } + preRenderFunctions.length = n; + frameState.extent = ol.extent.getForViewAndSize(viewState.center, + viewState.resolution, viewState.rotation, frameState.size); + } -/** - * @enum {string} - */ -ol.format.FormatType = { - BINARY: 'binary', - JSON: 'json', - TEXT: 'text', - XML: 'xml' -}; + this.frameState_ = frameState; + this.renderer_.renderFrame(frameState); + + if (!goog.isNull(frameState)) { + if (frameState.animate) { + this.render(); + } + Array.prototype.push.apply( + this.postRenderFunctions_, frameState.postRenderFunctions); -goog.provide('ol.format.BinaryFeature'); + var idle = this.preRenderFunctions_.length === 0 && + !frameState.viewHints[ol.ViewHint.ANIMATING] && + !frameState.viewHints[ol.ViewHint.INTERACTING] && + !ol.extent.equals(frameState.extent, this.previousExtent_); -goog.require('goog.asserts'); -goog.require('ol.binary.Buffer'); -goog.require('ol.format.Feature'); -goog.require('ol.format.FormatType'); -goog.require('ol.has'); -goog.require('ol.proj'); + if (idle) { + this.dispatchEvent( + new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState)); + ol.extent.clone(frameState.extent, this.previousExtent_); + } + } + this.dispatchEvent( + new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState)); + goog.async.nextTick(this.handlePostRender, this); -/** - * @constructor - * @extends {ol.format.Feature} - */ -ol.format.BinaryFeature = function() { - goog.base(this); }; -goog.inherits(ol.format.BinaryFeature, ol.format.Feature); /** - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @private - * @return {ol.binary.Buffer} Buffer. + * Sets the layergroup of this map. + * @param {ol.layer.Group} layerGroup A layer group containing the layers in + * this map. + * @observable + * @api stable */ -ol.format.BinaryFeature.getBuffer_ = function(source) { - if (ol.has.ARRAY_BUFFER && source instanceof ArrayBuffer) { - return new ol.binary.Buffer(source); - } else if (goog.isString(source)) { - return new ol.binary.Buffer(source); - } else { - goog.asserts.fail(); - return null; - } +ol.Map.prototype.setLayerGroup = function(layerGroup) { + this.set(ol.MapProperty.LAYERGROUP, layerGroup); }; /** - * @inheritDoc + * Set the size of this map. + * @param {ol.Size|undefined} size The size in pixels of the map in the DOM. + * @observable + * @api */ -ol.format.BinaryFeature.prototype.getType = function() { - return ol.format.FormatType.BINARY; +ol.Map.prototype.setSize = function(size) { + this.set(ol.MapProperty.SIZE, size); }; /** - * @inheritDoc + * Set the target element to render this map into. + * @param {Element|string|undefined} target The Element or id of the Element + * that the map is rendered in. + * @observable + * @api stable */ -ol.format.BinaryFeature.prototype.readFeature = function(source) { - return this.readFeatureFromBuffer(ol.format.BinaryFeature.getBuffer_(source)); +ol.Map.prototype.setTarget = function(target) { + this.set(ol.MapProperty.TARGET, target); }; /** - * @inheritDoc + * Set the view for this map. + * @param {ol.View} view The view that controls this map. + * @observable + * @api stable */ -ol.format.BinaryFeature.prototype.readFeatures = function(source) { - return this.readFeaturesFromBuffer( - ol.format.BinaryFeature.getBuffer_(source)); +ol.Map.prototype.setView = function(view) { + this.set(ol.MapProperty.VIEW, view); }; /** - * @param {ol.binary.Buffer} buffer Buffer. - * @protected - * @return {ol.Feature} Feature. + * @param {ol.Feature} feature Feature. */ -ol.format.BinaryFeature.prototype.readFeatureFromBuffer = goog.abstractMethod; +ol.Map.prototype.skipFeature = function(feature) { + var featureUid = goog.getUid(feature).toString(); + this.skippedFeatureUids_[featureUid] = true; + this.render(); +}; /** - * @param {ol.binary.Buffer} buffer Buffer. - * @protected - * @return {Array.<ol.Feature>} Feature. + * Force a recalculation of the map viewport size. This should be called when + * third-party code changes the size of the map viewport. + * @api stable */ -ol.format.BinaryFeature.prototype.readFeaturesFromBuffer = goog.abstractMethod; +ol.Map.prototype.updateSize = function() { + var targetElement = this.getTargetElement(); + + if (goog.isNull(targetElement)) { + this.setSize(undefined); + } else { + var size = goog.style.getContentBoxSize(targetElement); + this.setSize([size.width, size.height]); + } +}; /** - * @inheritDoc + * @param {ol.Feature} feature Feature. */ -ol.format.BinaryFeature.prototype.readGeometry = function(source) { - return this.readGeometryFromBuffer( - ol.format.BinaryFeature.getBuffer_(source)); +ol.Map.prototype.unskipFeature = function(feature) { + var featureUid = goog.getUid(feature).toString(); + delete this.skippedFeatureUids_[featureUid]; + this.render(); }; /** - * @param {ol.binary.Buffer} buffer Buffer. - * @protected - * @return {ol.geom.Geometry} Geometry. + * @typedef {{controls: ol.Collection.<ol.control.Control>, + * interactions: ol.Collection.<ol.interaction.Interaction>, + * keyboardEventTarget: (Element|Document), + * logos: Object, + * overlays: ol.Collection.<ol.Overlay>, + * rendererConstructor: + * function(new: ol.renderer.Map, Element, ol.Map), + * values: Object.<string, *>}} */ -ol.format.BinaryFeature.prototype.readGeometryFromBuffer = goog.abstractMethod; +ol.MapOptionsInternal; /** - * @inheritDoc + * @param {olx.MapOptions} options Map options. + * @return {ol.MapOptionsInternal} Internal map options. */ -ol.format.BinaryFeature.prototype.readProjection = function(source) { - return this.readProjectionFromBuffer( - ol.format.BinaryFeature.getBuffer_(source)); -}; +ol.Map.createOptionsInternal = function(options) { + /** + * @type {Element|Document} + */ + var keyboardEventTarget = null; + if (goog.isDef(options.keyboardEventTarget)) { + // cannot use goog.dom.getElement because its argument cannot be + // of type Document + keyboardEventTarget = goog.isString(options.keyboardEventTarget) ? + document.getElementById(options.keyboardEventTarget) : + options.keyboardEventTarget; + } -/** - * @param {ol.binary.Buffer} buffer Buffer. - * @return {ol.proj.Projection} Projection. - */ -ol.format.BinaryFeature.prototype.readProjectionFromBuffer = - goog.abstractMethod; + /** + * @type {Object.<string, *>} + */ + var values = {}; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + var logos = {}; + if (!goog.isDef(options.logo) || + (goog.isBoolean(options.logo) && options.logo)) { + logos[ol.OL3_LOGO_URL] = ol.OL3_URL; + } else { + var logo = options.logo; + if (goog.isString(logo)) { + logos[logo] = ''; + } else if (goog.isObject(logo)) { + goog.asserts.assertString(logo.href, 'logo.href should be a string'); + goog.asserts.assertString(logo.src, 'logo.src should be a string'); + logos[logo.src] = logo.href; + } + } -/** - * @fileoverview JSON utility functions. - * @author arv@google.com (Erik Arvidsson) - */ + var layerGroup = (options.layers instanceof ol.layer.Group) ? + options.layers : new ol.layer.Group({layers: options.layers}); + values[ol.MapProperty.LAYERGROUP] = layerGroup; + values[ol.MapProperty.TARGET] = options.target; -goog.provide('goog.json'); -goog.provide('goog.json.Replacer'); -goog.provide('goog.json.Reviver'); -goog.provide('goog.json.Serializer'); + values[ol.MapProperty.VIEW] = goog.isDef(options.view) ? + options.view : new ol.View(); + /** + * @type {function(new: ol.renderer.Map, Element, ol.Map)} + */ + var rendererConstructor = ol.renderer.Map; -/** - * @define {boolean} If true, use the native JSON parsing API. - * NOTE(ruilopes): EXPERIMENTAL, handle with care. Setting this to true might - * break your code. The default {@code goog.json.parse} implementation is able - * to handle invalid JSON, such as JSPB. - */ -goog.define('goog.json.USE_NATIVE_JSON', false); + /** + * @type {Array.<ol.RendererType>} + */ + var rendererTypes; + if (goog.isDef(options.renderer)) { + if (goog.isArray(options.renderer)) { + rendererTypes = options.renderer; + } else if (goog.isString(options.renderer)) { + rendererTypes = [options.renderer]; + } else { + goog.asserts.fail('Incorrect format for renderer option'); + } + } else { + rendererTypes = ol.DEFAULT_RENDERER_TYPES; + } + + var i, ii; + for (i = 0, ii = rendererTypes.length; i < ii; ++i) { + /** @type {ol.RendererType} */ + var rendererType = rendererTypes[i]; + if (ol.ENABLE_CANVAS && rendererType == ol.RendererType.CANVAS) { + if (ol.has.CANVAS) { + rendererConstructor = ol.renderer.canvas.Map; + break; + } + } else if (ol.ENABLE_DOM && rendererType == ol.RendererType.DOM) { + if (ol.has.DOM) { + rendererConstructor = ol.renderer.dom.Map; + break; + } + } else if (ol.ENABLE_WEBGL && rendererType == ol.RendererType.WEBGL) { + if (ol.has.WEBGL) { + rendererConstructor = ol.renderer.webgl.Map; + break; + } + } + } + var controls; + if (goog.isDef(options.controls)) { + if (goog.isArray(options.controls)) { + controls = new ol.Collection(options.controls.slice()); + } else { + goog.asserts.assertInstanceof(options.controls, ol.Collection, + 'options.controls should be an ol.Collection'); + controls = options.controls; + } + } else { + controls = ol.control.defaults(); + } -/** - * Tests if a string is an invalid JSON string. This only ensures that we are - * not using any invalid characters - * @param {string} s The string to test. - * @return {boolean} True if the input is a valid JSON string. - */ -goog.json.isValid = function(s) { - // All empty whitespace is not valid. - if (/^\s*$/.test(s)) { - return false; + var interactions; + if (goog.isDef(options.interactions)) { + if (goog.isArray(options.interactions)) { + interactions = new ol.Collection(options.interactions.slice()); + } else { + goog.asserts.assertInstanceof(options.interactions, ol.Collection, + 'options.interactions should be an ol.Collection'); + interactions = options.interactions; + } + } else { + interactions = ol.interaction.defaults(); } - // This is taken from http://www.json.org/json2.js which is released to the - // public domain. - // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator - // inside strings. We also treat \u2028 and \u2029 as whitespace which they - // are in the RFC but IE and Safari does not match \s to these so we need to - // include them in the reg exps in all places where whitespace is allowed. - // We allowed \x7f inside strings because some tools don't escape it, - // e.g. http://www.json.org/java/org/json/JSONObject.java + var overlays; + if (goog.isDef(options.overlays)) { + if (goog.isArray(options.overlays)) { + overlays = new ol.Collection(options.overlays.slice()); + } else { + goog.asserts.assertInstanceof(options.overlays, ol.Collection, + 'options.overlays should be an ol.Collection'); + overlays = options.overlays; + } + } else { + overlays = new ol.Collection(); + } + + return { + controls: controls, + interactions: interactions, + keyboardEventTarget: keyboardEventTarget, + logos: logos, + overlays: overlays, + rendererConstructor: rendererConstructor, + values: values + }; + +}; + + +ol.proj.common.add(); - // Parsing happens in three stages. In the first stage, we run the text - // against regular expressions that look for non-JSON patterns. We are - // especially concerned with '()' and 'new' because they can cause invocation, - // and '=' because it can cause mutation. But just to be safe, we want to - // reject all unexpected forms. - // We split the first stage into 4 regexp operations in order to work around - // crippling inefficiencies in IE's and Safari's regexp engines. First we - // replace all backslash pairs with '@' (a non-JSON character). Second, we - // replace all simple value tokens with ']' characters. Third, we delete all - // open brackets that follow a colon or comma or that begin the text. Finally, - // we look to see that the remaining characters are only whitespace or ']' or - // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. +if (goog.DEBUG) { + (function() { + goog.debug.Console.autoInstall(); + var logger = goog.log.getLogger('ol'); + logger.setLevel(goog.log.Level.FINEST); + })(); +} - // Don't make these static since they have the global flag. - var backslashesRe = /\\["\\\/bfnrtu]/g; - var simpleValuesRe = - /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; - var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g; - var remainderRe = /^[\],:{}\s\u2028\u2029]*$/; +goog.provide('ol.Overlay'); +goog.provide('ol.OverlayPositioning'); +goog.provide('ol.OverlayProperty'); - return remainderRe.test(s.replace(backslashesRe, '@'). - replace(simpleValuesRe, ']'). - replace(openBracketsRe, '')); -}; +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('ol.Coordinate'); +goog.require('ol.Map'); +goog.require('ol.MapEventType'); +goog.require('ol.Object'); +goog.require('ol.animation'); +goog.require('ol.dom'); +goog.require('ol.extent'); /** - * Parses a JSON string and returns the result. This throws an exception if - * the string is an invalid JSON string. - * - * Note that this is very slow on large strings. If you trust the source of - * the string then you should use unsafeParse instead. - * - * @param {*} s The JSON string to parse. - * @throws Error if s is invalid JSON. - * @return {Object} The object generated from the JSON string, or null. + * @enum {string} */ -goog.json.parse = goog.json.USE_NATIVE_JSON ? - /** @type {function(*):Object} */ (goog.global['JSON']['parse']) : - function(s) { - var o = String(s); - if (goog.json.isValid(o)) { - /** @preserveTry */ - try { - return /** @type {Object} */ (eval('(' + o + ')')); - } catch (ex) { - } - } - throw Error('Invalid JSON string: ' + o); - }; +ol.OverlayProperty = { + ELEMENT: 'element', + MAP: 'map', + OFFSET: 'offset', + POSITION: 'position', + POSITIONING: 'positioning' +}; /** - * Parses a JSON string and returns the result. This uses eval so it is open - * to security issues and it should only be used if you trust the source. - * - * @param {string} s The JSON string to parse. - * @return {Object} The object generated from the JSON string. + * Overlay position: `'bottom-left'`, `'bottom-center'`, `'bottom-right'`, + * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`, + * `'top-center'`, `'top-right'` + * @enum {string} + * @api stable */ -goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ? - /** @type {function(string):Object} */ (goog.global['JSON']['parse']) : - function(s) { - return /** @type {Object} */ (eval('(' + s + ')')); - }; +ol.OverlayPositioning = { + BOTTOM_LEFT: 'bottom-left', + BOTTOM_CENTER: 'bottom-center', + BOTTOM_RIGHT: 'bottom-right', + CENTER_LEFT: 'center-left', + CENTER_CENTER: 'center-center', + CENTER_RIGHT: 'center-right', + TOP_LEFT: 'top-left', + TOP_CENTER: 'top-center', + TOP_RIGHT: 'top-right' +}; + /** - * JSON replacer, as defined in Section 15.12.3 of the ES5 spec. - * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3 + * @classdesc + * An element to be displayed over the map and attached to a single map + * location. Like {@link ol.control.Control}, Overlays are visible widgets. + * Unlike Controls, they are not in a fixed position on the screen, but are tied + * to a geographical coordinate, so panning the map will move an Overlay but not + * a Control. * - * TODO(nicksantos): Array should also be a valid replacer. + * Example: * - * @typedef {function(this:Object, string, *): *} - */ -goog.json.Replacer; - - -/** - * JSON reviver, as defined in Section 15.12.2 of the ES5 spec. - * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3 + * var popup = new ol.Overlay({ + * element: document.getElementById('popup') + * }); + * popup.setPosition(coordinate); + * map.addOverlay(popup); * - * @typedef {function(this:Object, string, *): *} + * @constructor + * @extends {ol.Object} + * @param {olx.OverlayOptions} options Overlay options. + * @api stable */ -goog.json.Reviver; +ol.Overlay = function(options) { + goog.base(this); -/** - * Serializes an object or a value to a JSON string. - * - * @param {*} object The object to serialize. - * @param {?goog.json.Replacer=} opt_replacer A replacer function - * called for each (key, value) pair that determines how the value - * should be serialized. By defult, this just returns the value - * and allows default serialization to kick in. - * @throws Error if there are loops in the object graph. - * @return {string} A JSON string representation of the input. - */ -goog.json.serialize = goog.json.USE_NATIVE_JSON ? - /** @type {function(*, ?goog.json.Replacer=):string} */ - (goog.global['JSON']['stringify']) : - function(object, opt_replacer) { - // NOTE(nicksantos): Currently, we never use JSON.stringify. - // - // The last time I evaluated this, JSON.stringify had subtle bugs and - // behavior differences on all browsers, and the performance win was not - // large enough to justify all the issues. This may change in the future - // as browser implementations get better. - // - // assertSerialize in json_test contains if branches for the cases - // that fail. - return new goog.json.Serializer(opt_replacer).serialize(object); - }; + /** + * @private + * @type {boolean} + */ + this.insertFirst_ = goog.isDef(options.insertFirst) ? + options.insertFirst : true; + /** + * @private + * @type {boolean} + */ + this.stopEvent_ = goog.isDef(options.stopEvent) ? options.stopEvent : true; + /** + * @private + * @type {Element} + */ + this.element_ = goog.dom.createDom(goog.dom.TagName.DIV, { + 'class': 'ol-overlay-container' + }); + this.element_.style.position = 'absolute'; -/** - * Class that is used to serialize JSON objects to a string. - * @param {?goog.json.Replacer=} opt_replacer Replacer. - * @constructor - */ -goog.json.Serializer = function(opt_replacer) { /** - * @type {goog.json.Replacer|null|undefined} * @private + * @type {boolean} */ - this.replacer_ = opt_replacer; -}; + this.autoPan_ = goog.isDef(options.autoPan) ? options.autoPan : false; + /** + * @private + * @type {olx.animation.PanOptions} + */ + this.autoPanAnimation_ = goog.isDef(options.autoPanAnimation) ? + options.autoPanAnimation : /** @type {olx.animation.PanOptions} */ ({}); -/** - * Serializes an object or a value to a JSON string. - * - * @param {*} object The object to serialize. - * @throws Error if there are loops in the object graph. - * @return {string} A JSON string representation of the input. - */ -goog.json.Serializer.prototype.serialize = function(object) { - var sb = []; - this.serializeInternal(object, sb); - return sb.join(''); -}; + /** + * @private + * @type {number} + */ + this.autoPanMargin_ = goog.isDef(options.autoPanMargin) ? + options.autoPanMargin : 20; + /** + * @private + * @type {{bottom_: string, + * left_: string, + * right_: string, + * top_: string, + * visible: boolean}} + */ + this.rendered_ = { + bottom_: '', + left_: '', + right_: '', + top_: '', + visible: true + }; -/** - * Serializes a generic value to a JSON string - * @protected - * @param {*} object The object to serialize. - * @param {Array<string>} sb Array used as a string builder. - * @throws Error if there are loops in the object graph. - */ -goog.json.Serializer.prototype.serializeInternal = function(object, sb) { - switch (typeof object) { - case 'string': - this.serializeString_(/** @type {string} */ (object), sb); - break; - case 'number': - this.serializeNumber_(/** @type {number} */ (object), sb); - break; - case 'boolean': - sb.push(object); - break; - case 'undefined': - sb.push('null'); - break; - case 'object': - if (object == null) { - sb.push('null'); - break; - } - if (goog.isArray(object)) { - this.serializeArray(/** @type {!Array<?>} */ (object), sb); - break; - } - // should we allow new String, new Number and new Boolean to be treated - // as string, number and boolean? Most implementations do not and the - // need is not very big - this.serializeObject_(/** @type {Object} */ (object), sb); - break; - case 'function': - // Skip functions. - // TODO(user) Should we return something here? - break; - default: - throw Error('Unknown type: ' + typeof object); - } -}; + /** + * @private + * @type {goog.events.Key} + */ + this.mapPostrenderListenerKey_ = null; + goog.events.listen( + this, ol.Object.getChangeEventType(ol.OverlayProperty.ELEMENT), + this.handleElementChanged, false, this); -/** - * Character mappings used internally for goog.string.quote - * @private - * @type {!Object} - */ -goog.json.Serializer.charToJsonCharCache_ = { - '\"': '\\"', - '\\': '\\\\', - '/': '\\/', - '\b': '\\b', - '\f': '\\f', - '\n': '\\n', - '\r': '\\r', - '\t': '\\t', + goog.events.listen( + this, ol.Object.getChangeEventType(ol.OverlayProperty.MAP), + this.handleMapChanged, false, this); - '\x0B': '\\u000b' // '\v' is not supported in JScript -}; + goog.events.listen( + this, ol.Object.getChangeEventType(ol.OverlayProperty.OFFSET), + this.handleOffsetChanged, false, this); + goog.events.listen( + this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITION), + this.handlePositionChanged, false, this); -/** - * Regular expression used to match characters that need to be replaced. - * The S60 browser has a bug where unicode characters are not matched by - * regular expressions. The condition below detects such behaviour and - * adjusts the regular expression accordingly. - * @private - * @type {!RegExp} - */ -goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ? - /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g; + goog.events.listen( + this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING), + this.handlePositioningChanged, false, this); + if (goog.isDef(options.element)) { + this.setElement(options.element); + } -/** - * Serializes a string to a JSON string - * @private - * @param {string} s The string to serialize. - * @param {Array<string>} sb Array used as a string builder. - */ -goog.json.Serializer.prototype.serializeString_ = function(s, sb) { - // The official JSON implementation does not work with international - // characters. - sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) { - // caching the result improves performance by a factor 2-3 - if (c in goog.json.Serializer.charToJsonCharCache_) { - return goog.json.Serializer.charToJsonCharCache_[c]; - } + this.setOffset(goog.isDef(options.offset) ? options.offset : [0, 0]); - var cc = c.charCodeAt(0); - var rv = '\\u'; - if (cc < 16) { - rv += '000'; - } else if (cc < 256) { - rv += '00'; - } else if (cc < 4096) { // \u1000 - rv += '0'; - } - return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16); - }), '"'); -}; + this.setPositioning(goog.isDef(options.positioning) ? + /** @type {ol.OverlayPositioning} */ (options.positioning) : + ol.OverlayPositioning.TOP_LEFT); + if (goog.isDef(options.position)) { + this.setPosition(options.position); + } -/** - * Serializes a number to a JSON string - * @private - * @param {number} n The number to serialize. - * @param {Array<string>} sb Array used as a string builder. - */ -goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) { - sb.push(isFinite(n) && !isNaN(n) ? n : 'null'); }; +goog.inherits(ol.Overlay, ol.Object); /** - * Serializes an array to a JSON string - * @param {Array<string>} arr The array to serialize. - * @param {Array<string>} sb Array used as a string builder. - * @protected + * Get the DOM element of this overlay. + * @return {Element|undefined} The Element containing the overlay. + * @observable + * @api stable */ -goog.json.Serializer.prototype.serializeArray = function(arr, sb) { - var l = arr.length; - sb.push('['); - var sep = ''; - for (var i = 0; i < l; i++) { - sb.push(sep); - - var value = arr[i]; - this.serializeInternal( - this.replacer_ ? this.replacer_.call(arr, String(i), value) : value, - sb); - - sep = ','; - } - sb.push(']'); +ol.Overlay.prototype.getElement = function() { + return /** @type {Element|undefined} */ ( + this.get(ol.OverlayProperty.ELEMENT)); }; /** - * Serializes an object to a JSON string - * @private - * @param {Object} obj The object to serialize. - * @param {Array<string>} sb Array used as a string builder. + * Get the map associated with this overlay. + * @return {ol.Map|undefined} The map that the overlay is part of. + * @observable + * @api stable */ -goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) { - sb.push('{'); - var sep = ''; - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - var value = obj[key]; - // Skip functions. - // TODO(ptucker) Should we return something for function properties? - if (typeof value != 'function') { - sb.push(sep); - this.serializeString_(key, sb); - sb.push(':'); - - this.serializeInternal( - this.replacer_ ? this.replacer_.call(obj, key, value) : value, - sb); - - sep = ','; - } - } - } - sb.push('}'); +ol.Overlay.prototype.getMap = function() { + return /** @type {ol.Map|undefined} */ ( + this.get(ol.OverlayProperty.MAP)); }; -goog.provide('ol.format.JSONFeature'); - -goog.require('goog.asserts'); -goog.require('goog.json'); -goog.require('ol.format.Feature'); -goog.require('ol.format.FormatType'); - - /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for JSON feature formats. - * - * @constructor - * @extends {ol.format.Feature} + * Get the offset of this overlay. + * @return {Array.<number>} The offset. + * @observable + * @api stable */ -ol.format.JSONFeature = function() { - goog.base(this); +ol.Overlay.prototype.getOffset = function() { + return /** @type {Array.<number>} */ ( + this.get(ol.OverlayProperty.OFFSET)); }; -goog.inherits(ol.format.JSONFeature, ol.format.Feature); /** - * @param {Document|Node|Object|string} source Source. - * @private - * @return {Object} Object. + * Get the current position of this overlay. + * @return {ol.Coordinate|undefined} The spatial point that the overlay is + * anchored at. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.getObject_ = function(source) { - if (goog.isObject(source)) { - return source; - } else if (goog.isString(source)) { - var object = goog.json.parse(source); - return goog.isDef(object) ? object : null; - } else { - goog.asserts.fail(); - return null; - } +ol.Overlay.prototype.getPosition = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.OverlayProperty.POSITION)); }; /** - * @inheritDoc + * Get the current positioning of this overlay. + * @return {ol.OverlayPositioning} How the overlay is positioned + * relative to its point on the map. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.getType = function() { - return ol.format.FormatType.JSON; +ol.Overlay.prototype.getPositioning = function() { + return /** @type {ol.OverlayPositioning} */ ( + this.get(ol.OverlayProperty.POSITIONING)); }; /** - * @inheritDoc + * @protected */ -ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) { - return this.readFeatureFromObject( - this.getObject_(source), this.getReadOptions(source, opt_options)); +ol.Overlay.prototype.handleElementChanged = function() { + goog.dom.removeChildren(this.element_); + var element = this.getElement(); + if (goog.isDefAndNotNull(element)) { + goog.dom.append(/** @type {!Node} */ (this.element_), element); + } }; /** - * @inheritDoc + * @protected */ -ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) { - return this.readFeaturesFromObject( - this.getObject_(source), this.getReadOptions(source, opt_options)); +ol.Overlay.prototype.handleMapChanged = function() { + if (!goog.isNull(this.mapPostrenderListenerKey_)) { + goog.dom.removeNode(this.element_); + goog.events.unlistenByKey(this.mapPostrenderListenerKey_); + this.mapPostrenderListenerKey_ = null; + } + var map = this.getMap(); + if (goog.isDefAndNotNull(map)) { + this.mapPostrenderListenerKey_ = goog.events.listen(map, + ol.MapEventType.POSTRENDER, this.render, false, this); + this.updatePixelPosition_(); + var container = this.stopEvent_ ? + map.getOverlayContainerStopEvent() : map.getOverlayContainer(); + if (this.insertFirst_) { + goog.dom.insertChildAt(/** @type {!Element} */ ( + container), this.element_, 0); + } else { + goog.dom.append(/** @type {!Node} */ (container), this.element_); + } + } }; /** - * @param {Object} object Object. - * @param {olx.format.ReadOptions=} opt_options Read options. * @protected - * @return {ol.Feature} Feature. */ -ol.format.JSONFeature.prototype.readFeatureFromObject = goog.abstractMethod; +ol.Overlay.prototype.render = function() { + this.updatePixelPosition_(); +}; /** - * @param {Object} object Object. - * @param {olx.format.ReadOptions=} opt_options Read options. * @protected - * @return {Array.<ol.Feature>} Features. */ -ol.format.JSONFeature.prototype.readFeaturesFromObject = goog.abstractMethod; +ol.Overlay.prototype.handleOffsetChanged = function() { + this.updatePixelPosition_(); +}; /** - * @inheritDoc + * @protected */ -ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) { - return this.readGeometryFromObject( - this.getObject_(source), this.getReadOptions(source, opt_options)); +ol.Overlay.prototype.handlePositionChanged = function() { + this.updatePixelPosition_(); + if (goog.isDef(this.get(ol.OverlayProperty.POSITION)) && this.autoPan_) { + this.panIntoView_(); + } }; /** - * @param {Object} object Object. - * @param {olx.format.ReadOptions=} opt_options Read options. * @protected - * @return {ol.geom.Geometry} Geometry. */ -ol.format.JSONFeature.prototype.readGeometryFromObject = goog.abstractMethod; +ol.Overlay.prototype.handlePositioningChanged = function() { + this.updatePixelPosition_(); +}; /** - * @inheritDoc + * Set the DOM element to be associated with this overlay. + * @param {Element|undefined} element The Element containing the overlay. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.readProjection = function(source) { - return this.readProjectionFromObject(this.getObject_(source)); +ol.Overlay.prototype.setElement = function(element) { + this.set(ol.OverlayProperty.ELEMENT, element); }; /** - * @param {Object} object Object. - * @protected - * @return {ol.proj.Projection} Projection. + * Set the map to be associated with this overlay. + * @param {ol.Map|undefined} map The map that the overlay is part of. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.readProjectionFromObject = goog.abstractMethod; +ol.Overlay.prototype.setMap = function(map) { + this.set(ol.OverlayProperty.MAP, map); +}; /** - * @inheritDoc + * Set the offset for this overlay. + * @param {Array.<number>} offset Offset. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) { - return goog.json.serialize(this.writeFeatureObject(feature, opt_options)); +ol.Overlay.prototype.setOffset = function(offset) { + this.set(ol.OverlayProperty.OFFSET, offset); }; /** - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {Object} Object. + * Set the position for this overlay. If the position is `undefined` the + * overlay is hidden. + * @param {ol.Coordinate|undefined} position The spatial point that the overlay + * is anchored at. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.writeFeatureObject = goog.abstractMethod; +ol.Overlay.prototype.setPosition = function(position) { + this.set(ol.OverlayProperty.POSITION, position); +}; /** - * @inheritDoc + * Pan the map so that the overlay is entirely visible in the current viewport + * (if necessary). + * @private */ -ol.format.JSONFeature.prototype.writeFeatures = function( - features, opt_options) { - return goog.json.serialize(this.writeFeaturesObject(features, opt_options)); +ol.Overlay.prototype.panIntoView_ = function() { + goog.asserts.assert(this.autoPan_, 'this.autoPan_ should be true'); + var map = this.getMap(); + + if (!goog.isDef(map) || goog.isNull(map.getTargetElement())) { + return; + } + + var mapRect = this.getRect_(map.getTargetElement(), map.getSize()); + var element = this.getElement(); + goog.asserts.assert(goog.isDefAndNotNull(element), + 'element should be defined'); + var overlayRect = this.getRect_(element, + [ol.dom.outerWidth(element), ol.dom.outerHeight(element)]); + + var margin = this.autoPanMargin_; + if (!ol.extent.containsExtent(mapRect, overlayRect)) { + // the overlay is not completely inside the viewport, so pan the map + var offsetLeft = overlayRect[0] - mapRect[0]; + var offsetRight = mapRect[2] - overlayRect[2]; + var offsetTop = overlayRect[1] - mapRect[1]; + var offsetBottom = mapRect[3] - overlayRect[3]; + + var delta = [0, 0]; + if (offsetLeft < 0) { + // move map to the left + delta[0] = offsetLeft - margin; + } else if (offsetRight < 0) { + // move map to the right + delta[0] = Math.abs(offsetRight) + margin; + } + if (offsetTop < 0) { + // move map up + delta[1] = offsetTop - margin; + } else if (offsetBottom < 0) { + // move map down + delta[1] = Math.abs(offsetBottom) + margin; + } + + if (delta[0] !== 0 || delta[1] !== 0) { + var center = map.getView().getCenter(); + goog.asserts.assert(goog.isDef(center), 'center should be defined'); + var centerPx = map.getPixelFromCoordinate(center); + var newCenterPx = [ + centerPx[0] + delta[0], + centerPx[1] + delta[1] + ]; + + if (!goog.isNull(this.autoPanAnimation_)) { + this.autoPanAnimation_.source = center; + map.beforeRender(ol.animation.pan(this.autoPanAnimation_)); + } + map.getView().setCenter(map.getCoordinateFromPixel(newCenterPx)); + } + } }; /** - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {Object} Object. + * Get the extent of an element relative to the document + * @param {Element|undefined} element The element. + * @param {ol.Size|undefined} size The size of the element. + * @return {ol.Extent} + * @private */ -ol.format.JSONFeature.prototype.writeFeaturesObject = goog.abstractMethod; +ol.Overlay.prototype.getRect_ = function(element, size) { + goog.asserts.assert(goog.isDefAndNotNull(element), + 'element should be defined'); + goog.asserts.assert(goog.isDef(size), 'size should be defined'); + + var offset = goog.style.getPageOffset(element); + return [ + offset.x, + offset.y, + offset.x + size[0], + offset.y + size[1] + ]; +}; /** - * @inheritDoc + * Set the positioning for this overlay. + * @param {ol.OverlayPositioning} positioning how the overlay is + * positioned relative to its point on the map. + * @observable + * @api stable */ -ol.format.JSONFeature.prototype.writeGeometry = function( - geometry, opt_options) { - return goog.json.serialize(this.writeGeometryObject(geometry, opt_options)); +ol.Overlay.prototype.setPositioning = function(positioning) { + this.set(ol.OverlayProperty.POSITIONING, positioning); }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {Object} Object. + * @private */ -ol.format.JSONFeature.prototype.writeGeometryObject = goog.abstractMethod; +ol.Overlay.prototype.updatePixelPosition_ = function() { -// FIXME coordinate order -// FIXME reprojection + var map = this.getMap(); + var position = this.getPosition(); + if (!goog.isDef(map) || !map.isRendered() || !goog.isDef(position)) { + if (this.rendered_.visible) { + goog.style.setElementShown(this.element_, false); + this.rendered_.visible = false; + } + return; + } -goog.provide('ol.format.GeoJSON'); + var pixel = map.getPixelFromCoordinate(position); + goog.asserts.assert(!goog.isNull(pixel), 'pixel should not be null'); + var mapSize = map.getSize(); + goog.asserts.assert(goog.isDef(mapSize), 'mapSize should be defined'); + var style = this.element_.style; + var offset = this.getOffset(); + goog.asserts.assert(goog.isArray(offset), 'offset should be an array'); + var positioning = this.getPositioning(); + goog.asserts.assert(goog.isDef(positioning), + 'positioning should be defined'); + + var offsetX = offset[0]; + var offsetY = offset[1]; + if (positioning == ol.OverlayPositioning.BOTTOM_RIGHT || + positioning == ol.OverlayPositioning.CENTER_RIGHT || + positioning == ol.OverlayPositioning.TOP_RIGHT) { + if (this.rendered_.left_ !== '') { + this.rendered_.left_ = style.left = ''; + } + var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px'; + if (this.rendered_.right_ != right) { + this.rendered_.right_ = style.right = right; + } + } else { + if (this.rendered_.right_ !== '') { + this.rendered_.right_ = style.right = ''; + } + if (positioning == ol.OverlayPositioning.BOTTOM_CENTER || + positioning == ol.OverlayPositioning.CENTER_CENTER || + positioning == ol.OverlayPositioning.TOP_CENTER) { + offsetX -= goog.style.getSize(this.element_).width / 2; + } + var left = Math.round(pixel[0] + offsetX) + 'px'; + if (this.rendered_.left_ != left) { + this.rendered_.left_ = style.left = left; + } + } + if (positioning == ol.OverlayPositioning.BOTTOM_LEFT || + positioning == ol.OverlayPositioning.BOTTOM_CENTER || + positioning == ol.OverlayPositioning.BOTTOM_RIGHT) { + if (this.rendered_.top_ !== '') { + this.rendered_.top_ = style.top = ''; + } + var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px'; + if (this.rendered_.bottom_ != bottom) { + this.rendered_.bottom_ = style.bottom = bottom; + } + } else { + if (this.rendered_.bottom_ !== '') { + this.rendered_.bottom_ = style.bottom = ''; + } + if (positioning == ol.OverlayPositioning.CENTER_LEFT || + positioning == ol.OverlayPositioning.CENTER_CENTER || + positioning == ol.OverlayPositioning.CENTER_RIGHT) { + offsetY -= goog.style.getSize(this.element_).height / 2; + } + var top = Math.round(pixel[1] + offsetY) + 'px'; + if (this.rendered_.top_ != top) { + this.rendered_.top_ = style.top = top; + } + } + + if (!this.rendered_.visible) { + goog.style.setElementShown(this.element_, true); + this.rendered_.visible = true; + } + +}; + +goog.provide('ol.control.OverviewMap'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('ol.Feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.JSONFeature'); -goog.require('ol.geom.GeometryCollection'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.proj'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.math.Size'); +goog.require('goog.style'); +goog.require('ol.Collection'); +goog.require('ol.Map'); +goog.require('ol.MapEventType'); +goog.require('ol.Object'); +goog.require('ol.ObjectEventType'); +goog.require('ol.Overlay'); +goog.require('ol.OverlayPositioning'); +goog.require('ol.View'); +goog.require('ol.ViewProperty'); +goog.require('ol.control.Control'); +goog.require('ol.coordinate'); +goog.require('ol.css'); +goog.require('ol.extent'); /** - * @classdesc - * Feature format for reading and writing data in the GeoJSON format. - * + * Create a new control with a map acting as an overview map for an other + * defined map. * @constructor - * @extends {ol.format.JSONFeature} - * @param {olx.format.GeoJSONOptions=} opt_options Options. - * @api stable + * @extends {ol.control.Control} + * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options. + * @api */ -ol.format.GeoJSON = function(opt_options) { +ol.control.OverviewMap = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; - goog.base(this); + /** + * @type {boolean} + * @private + */ + this.collapsed_ = goog.isDef(options.collapsed) ? options.collapsed : true; /** - * @inheritDoc + * @private + * @type {boolean} */ - this.defaultDataProjection = ol.proj.get( - goog.isDefAndNotNull(options.defaultDataProjection) ? - options.defaultDataProjection : 'EPSG:4326'); + this.collapsible_ = goog.isDef(options.collapsible) ? + options.collapsible : true; + + if (!this.collapsible_) { + this.collapsed_ = false; + } + + var className = goog.isDef(options.className) ? + options.className : 'ol-overviewmap'; + var tipLabel = goog.isDef(options.tipLabel) ? + options.tipLabel : 'Overview map'; + + var collapseLabel = goog.isDef(options.collapseLabel) ? + options.collapseLabel : '\u00AB'; /** - * Name of the geometry attribute for features. - * @type {string|undefined} * @private + * @type {Node} */ - this.geometryName_ = options.geometryName; + this.collapseLabel_ = /** @type {Node} */ (goog.isString(collapseLabel) ? + goog.dom.createDom(goog.dom.TagName.SPAN, {}, collapseLabel) : + collapseLabel); + + var label = goog.isDef(options.label) ? options.label : '\u00BB'; + + /** + * @private + * @type {Node} + */ + this.label_ = /** @type {Node} */ (goog.isString(label) ? + goog.dom.createDom(goog.dom.TagName.SPAN, {}, label) : + label); + + var activeLabel = (this.collapsible_ && !this.collapsed_) ? + this.collapseLabel_ : this.label_; + var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'type': 'button', + 'title': tipLabel + }, activeLabel); + + goog.events.listen(button, goog.events.EventType.CLICK, + this.handleClick_, false, this); + + var ovmapDiv = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-map'); + + /** + * @type {ol.Map} + * @private + */ + this.ovmap_ = new ol.Map({ + controls: new ol.Collection(), + interactions: new ol.Collection(), + target: ovmapDiv + }); + var ovmap = this.ovmap_; + + if (goog.isDef(options.layers)) { + options.layers.forEach( + /** + * @param {ol.layer.Layer} layer Layer. + */ + function(layer) { + ovmap.addLayer(layer); + }, this); + } + var box = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-overviewmap-box'); + + /** + * @type {ol.Overlay} + * @private + */ + this.boxOverlay_ = new ol.Overlay({ + position: [0, 0], + positioning: ol.OverlayPositioning.BOTTOM_LEFT, + element: box + }); + this.ovmap_.addOverlay(this.boxOverlay_); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL + + (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') + + (this.collapsible_ ? '' : ' ol-uncollapsible'); + var element = goog.dom.createDom(goog.dom.TagName.DIV, + cssClasses, ovmapDiv, button); + + var render = goog.isDef(options.render) ? + options.render : ol.control.OverviewMap.render; + + goog.base(this, { + element: element, + render: render, + target: options.target + }); }; -goog.inherits(ol.format.GeoJSON, ol.format.JSONFeature); +goog.inherits(ol.control.OverviewMap, ol.control.Control); /** - * @const - * @type {Array.<string>} - * @private + * @inheritDoc + * @api */ -ol.format.GeoJSON.EXTENSIONS_ = ['.geojson']; +ol.control.OverviewMap.prototype.setMap = function(map) { + var oldMap = this.getMap(); + if (map === oldMap) { + return; + } + if (oldMap) { + var oldView = oldMap.getView(); + if (oldView) { + this.unbindView_(oldView); + } + } + goog.base(this, 'setMap', map); + + if (map) { + this.listenerKeys.push(goog.events.listen( + map, ol.ObjectEventType.PROPERTYCHANGE, + this.handleMapPropertyChange_, false, this)); + + // TODO: to really support map switching, this would need to be reworked + if (this.ovmap_.getLayers().getLength() === 0) { + this.ovmap_.setLayerGroup(map.getLayerGroup()); + } + + var view = map.getView(); + if (view) { + this.bindView_(view); + if (view.isDef()) { + this.ovmap_.updateSize(); + this.resetExtent_(); + } + } + } +}; /** - * @param {GeoJSONObject} object Object. - * @param {olx.format.ReadOptions=} opt_options Read options. + * Handle map property changes. This only deals with changes to the map's view. + * @param {ol.ObjectEvent} event The propertychange event. * @private - * @return {ol.geom.Geometry} Geometry. */ -ol.format.GeoJSON.readGeometry_ = function(object, opt_options) { - if (goog.isNull(object)) { - return null; +ol.control.OverviewMap.prototype.handleMapPropertyChange_ = function(event) { + if (event.key === ol.MapProperty.VIEW) { + var oldView = /** @type {ol.View} */ (event.oldValue); + if (oldView) { + this.unbindView_(oldView); + } + var newView = this.getMap().getView(); + this.bindView_(newView); } - var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type]; - goog.asserts.assert(goog.isDef(geometryReader)); - return /** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions( - geometryReader(object), false, opt_options)); }; /** - * @param {GeoJSONGeometryCollection} object Object. - * @param {olx.format.ReadOptions=} opt_options Read options. + * Register listeners for view property changes. + * @param {ol.View} view The view. * @private - * @return {ol.geom.GeometryCollection} Geometry collection. */ -ol.format.GeoJSON.readGeometryCollectionGeometry_ = function( - object, opt_options) { - goog.asserts.assert(object.type == 'GeometryCollection'); - var geometries = goog.array.map(object.geometries, - /** - * @param {GeoJSONObject} geometry Geometry. - * @return {ol.geom.Geometry} geometry Geometry. - */ - function(geometry) { - return ol.format.GeoJSON.readGeometry_(geometry, opt_options); - }); - return new ol.geom.GeometryCollection(geometries); +ol.control.OverviewMap.prototype.bindView_ = function(view) { + goog.events.listen(view, + ol.Object.getChangeEventType(ol.ViewProperty.ROTATION), + this.handleRotationChanged_, false, this); }; /** - * @param {GeoJSONGeometry} object Object. + * Unregister listeners for view property changes. + * @param {ol.View} view The view. * @private - * @return {ol.geom.Point} Point. */ -ol.format.GeoJSON.readPointGeometry_ = function(object) { - goog.asserts.assert(object.type == 'Point'); - return new ol.geom.Point(object.coordinates); +ol.control.OverviewMap.prototype.unbindView_ = function(view) { + goog.events.unlisten(view, + ol.Object.getChangeEventType(ol.ViewProperty.ROTATION), + this.handleRotationChanged_, false, this); }; /** - * @param {GeoJSONGeometry} object Object. + * Handle rotation changes to the main map. + * TODO: This should rotate the extent rectrangle instead of the + * overview map's view. * @private - * @return {ol.geom.LineString} LineString. */ -ol.format.GeoJSON.readLineStringGeometry_ = function(object) { - goog.asserts.assert(object.type == 'LineString'); - return new ol.geom.LineString(object.coordinates); +ol.control.OverviewMap.prototype.handleRotationChanged_ = function() { + this.ovmap_.getView().setRotation(this.getMap().getView().getRotation()); }; /** - * @param {GeoJSONGeometry} object Object. - * @private - * @return {ol.geom.MultiLineString} MultiLineString. + * Update the overview map element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.OverviewMap} + * @api */ -ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) { - goog.asserts.assert(object.type == 'MultiLineString'); - return new ol.geom.MultiLineString(object.coordinates); +ol.control.OverviewMap.render = function(mapEvent) { + this.validateExtent_(); + this.updateBox_(); }; /** - * @param {GeoJSONGeometry} object Object. + * Reset the overview map extent if the box size (width or + * height) is less than the size of the overview map size times minRatio + * or is greater than the size of the overview size times maxRatio. + * + * If the map extent was not reset, the box size can fits in the defined + * ratio sizes. This method then checks if is contained inside the overview + * map current extent. If not, recenter the overview map to the current + * main map center location. * @private - * @return {ol.geom.MultiPoint} MultiPoint. */ -ol.format.GeoJSON.readMultiPointGeometry_ = function(object) { - goog.asserts.assert(object.type == 'MultiPoint'); - return new ol.geom.MultiPoint(object.coordinates); +ol.control.OverviewMap.prototype.validateExtent_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + if (!map.isRendered() || !ovmap.isRendered()) { + return; + } + + var mapSize = map.getSize(); + goog.asserts.assertArray(mapSize, 'mapSize should be an array'); + + var view = map.getView(); + goog.asserts.assert(goog.isDef(view), 'view should be defined'); + var extent = view.calculateExtent(mapSize); + + var ovmapSize = ovmap.getSize(); + goog.asserts.assertArray(ovmapSize, 'ovmapSize should be an array'); + + var ovview = ovmap.getView(); + goog.asserts.assert(goog.isDef(ovview), 'ovview should be defined'); + var ovextent = ovview.calculateExtent(ovmapSize); + + var topLeftPixel = + ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent)); + var bottomRightPixel = + ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent)); + var boxSize = new goog.math.Size( + Math.abs(topLeftPixel[0] - bottomRightPixel[0]), + Math.abs(topLeftPixel[1] - bottomRightPixel[1])); + + var ovmapWidth = ovmapSize[0]; + var ovmapHeight = ovmapSize[1]; + + if (boxSize.width < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO || + boxSize.height < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO || + boxSize.width > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO || + boxSize.height > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) { + this.resetExtent_(); + } else if (!ol.extent.containsExtent(ovextent, extent)) { + this.recenter_(); + } }; /** - * @param {GeoJSONGeometry} object Object. + * Reset the overview map extent to half calculated min and max ratio times + * the extent of the main map. * @private - * @return {ol.geom.MultiPolygon} MultiPolygon. */ -ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) { - goog.asserts.assert(object.type == 'MultiPolygon'); - return new ol.geom.MultiPolygon(object.coordinates); +ol.control.OverviewMap.prototype.resetExtent_ = function() { + if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) { + return; + } + + var map = this.getMap(); + var ovmap = this.ovmap_; + + var mapSize = map.getSize(); + goog.asserts.assertArray(mapSize, 'mapSize should be an array'); + + var view = map.getView(); + goog.asserts.assert(goog.isDef(view), 'view should be defined'); + var extent = view.calculateExtent(mapSize); + + var ovmapSize = ovmap.getSize(); + goog.asserts.assertArray(ovmapSize, 'ovmapSize should be an array'); + + var ovview = ovmap.getView(); + goog.asserts.assert(goog.isDef(ovview), 'ovview should be defined'); + + // get how many times the current map overview could hold different + // box sizes using the min and max ratio, pick the step in the middle used + // to calculate the extent from the main map to set it to the overview map, + var steps = Math.log( + ol.OVERVIEWMAP_MAX_RATIO / ol.OVERVIEWMAP_MIN_RATIO) / Math.LN2; + var ratio = 1 / (Math.pow(2, steps / 2) * ol.OVERVIEWMAP_MIN_RATIO); + ol.extent.scaleFromCenter(extent, ratio); + ovview.fit(extent, ovmapSize); }; /** - * @param {GeoJSONGeometry} object Object. + * Set the center of the overview map to the map center without changing its + * resolution. * @private - * @return {ol.geom.Polygon} Polygon. */ -ol.format.GeoJSON.readPolygonGeometry_ = function(object) { - goog.asserts.assert(object.type == 'Polygon'); - return new ol.geom.Polygon(object.coordinates); +ol.control.OverviewMap.prototype.recenter_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + var view = map.getView(); + goog.asserts.assert(goog.isDef(view), 'view should be defined'); + + var ovview = ovmap.getView(); + goog.asserts.assert(goog.isDef(ovview), 'ovview should be defined'); + + ovview.setCenter(view.getCenter()); }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. + * Update the box using the main map extent * @private - * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry. */ -ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) { - var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()]; - goog.asserts.assert(goog.isDef(geometryWriter)); - return geometryWriter(/** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions(geometry, true, opt_options))); +ol.control.OverviewMap.prototype.updateBox_ = function() { + var map = this.getMap(); + var ovmap = this.ovmap_; + + if (!map.isRendered() || !ovmap.isRendered()) { + return; + } + + var mapSize = map.getSize(); + goog.asserts.assertArray(mapSize, 'mapSize should be an array'); + + var view = map.getView(); + goog.asserts.assert(goog.isDef(view), 'view should be defined'); + + var ovview = ovmap.getView(); + goog.asserts.assert(goog.isDef(ovview), 'ovview should be defined'); + + var ovmapSize = ovmap.getSize(); + goog.asserts.assertArray(ovmapSize, 'ovmapSize should be an array'); + + var rotation = view.getRotation(); + goog.asserts.assert(goog.isDef(rotation), 'rotation should be defined'); + + var overlay = this.boxOverlay_; + var box = this.boxOverlay_.getElement(); + var extent = view.calculateExtent(mapSize); + var ovresolution = ovview.getResolution(); + var bottomLeft = ol.extent.getBottomLeft(extent); + var topRight = ol.extent.getTopRight(extent); + + // set position using bottom left coordinates + var rotateBottomLeft = this.calculateCoordinateRotate_(rotation, bottomLeft); + overlay.setPosition(rotateBottomLeft); + + // set box size calculated from map extent size and overview map resolution + if (goog.isDefAndNotNull(box)) { + var boxWidth = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution); + var boxHeight = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution); + goog.style.setBorderBoxSize(box, new goog.math.Size( + boxWidth, boxHeight)); + } }; /** - * @param {ol.geom.Geometry} geometry Geometry. + * @param {number} rotation Target rotation. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor. * @private - * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection. */ -ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) { - return /** @type {GeoJSONGeometryCollection} */ ({ - 'type': 'GeometryCollection', - 'geometries': [] - }); -}; +ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function( + rotation, coordinate) { + var coordinateRotate; + var map = this.getMap(); + var view = map.getView(); + goog.asserts.assert(goog.isDef(view), 'view should be defined'); -/** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @private - * @return {GeoJSONGeometryCollection} GeoJSON geometry collection. - */ -ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function( - geometry, opt_options) { - goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection); - var geometries = goog.array.map( - geometry.getGeometriesArray(), function(geometry) { - return ol.format.GeoJSON.writeGeometry_(geometry, opt_options); - }); - return /** @type {GeoJSONGeometryCollection} */ ({ - 'type': 'GeometryCollection', - 'geometries': geometries - }); + var currentCenter = view.getCenter(); + + if (goog.isDef(currentCenter)) { + coordinateRotate = [ + coordinate[0] - currentCenter[0], + coordinate[1] - currentCenter[1] + ]; + ol.coordinate.rotate(coordinateRotate, rotation); + ol.coordinate.add(coordinateRotate, currentCenter); + } + return coordinateRotate; }; /** - * @param {ol.geom.Geometry} geometry Geometry. + * @param {goog.events.BrowserEvent} event The event to handle * @private - * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - return /** @type {GeoJSONGeometry} */ ({ - 'type': 'LineString', - 'coordinates': geometry.getCoordinates() - }); +ol.control.OverviewMap.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleToggle_(); }; /** - * @param {ol.geom.Geometry} geometry Geometry. * @private - * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString); - goog.asserts.assert( - geometry.getType() == ol.geom.GeometryType.MULTI_LINE_STRING); - return /** @type {GeoJSONGeometry} */ ({ - 'type': 'MultiLineString', - 'coordinates': geometry.getCoordinates() - }); +ol.control.OverviewMap.prototype.handleToggle_ = function() { + goog.dom.classlist.toggle(this.element, 'ol-collapsed'); + if (this.collapsed_) { + goog.dom.replaceNode(this.collapseLabel_, this.label_); + } else { + goog.dom.replaceNode(this.label_, this.collapseLabel_); + } + this.collapsed_ = !this.collapsed_; + + // manage overview map if it had not been rendered before and control + // is expanded + var ovmap = this.ovmap_; + if (!this.collapsed_ && !ovmap.isRendered()) { + ovmap.updateSize(); + this.resetExtent_(); + goog.events.listenOnce(ovmap, ol.MapEventType.POSTRENDER, + function(event) { + this.updateBox_(); + }, + false, this); + } }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @private - * @return {GeoJSONGeometry} GeoJSON geometry. + * Return `true` if the overview map is collapsible, `false` otherwise. + * @return {boolean} True if the widget is collapsible. + * @api stable */ -ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint); - return /** @type {GeoJSONGeometry} */ ({ - 'type': 'MultiPoint', - 'coordinates': geometry.getCoordinates() - }); +ol.control.OverviewMap.prototype.getCollapsible = function() { + return this.collapsible_; }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @private - * @return {GeoJSONGeometry} GeoJSON geometry. + * Set whether the overview map should be collapsible. + * @param {boolean} collapsible True if the widget is collapsible. + * @api stable */ -ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon); - return /** @type {GeoJSONGeometry} */ ({ - 'type': 'MultiPolygon', - 'coordinates': geometry.getCoordinates() - }); +ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) { + if (this.collapsible_ === collapsible) { + return; + } + this.collapsible_ = collapsible; + goog.dom.classlist.toggle(this.element, 'ol-uncollapsible'); + if (!collapsible && this.collapsed_) { + this.handleToggle_(); + } }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @private - * @return {GeoJSONGeometry} GeoJSON geometry. + * Collapse or expand the overview map according to the passed parameter. Will + * not do anything if the overview map isn't collapsible or if the current + * collapsed state is already the one requested. + * @param {boolean} collapsed True if the widget is collapsed. + * @api stable */ -ol.format.GeoJSON.writePointGeometry_ = function(geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - return /** @type {GeoJSONGeometry} */ ({ - 'type': 'Point', - 'coordinates': geometry.getCoordinates() - }); +ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) { + if (!this.collapsible_ || this.collapsed_ === collapsed) { + return; + } + this.handleToggle_(); }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @private - * @return {GeoJSONGeometry} GeoJSON geometry. + * @return {boolean} True if the widget is collapsed. + * @api stable */ -ol.format.GeoJSON.writePolygonGeometry_ = function(geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - return /** @type {GeoJSONGeometry} */ ({ - 'type': 'Polygon', - 'coordinates': geometry.getCoordinates() - }); +ol.control.OverviewMap.prototype.getCollapsed = function() { + return this.collapsed_; }; +goog.provide('ol.control.ScaleLine'); +goog.provide('ol.control.ScaleLineProperty'); +goog.provide('ol.control.ScaleLineUnits'); -/** - * @const - * @private - * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>} - */ -ol.format.GeoJSON.GEOMETRY_READERS_ = { - 'Point': ol.format.GeoJSON.readPointGeometry_, - 'LineString': ol.format.GeoJSON.readLineStringGeometry_, - 'Polygon': ol.format.GeoJSON.readPolygonGeometry_, - 'MultiPoint': ol.format.GeoJSON.readMultiPointGeometry_, - 'MultiLineString': ol.format.GeoJSON.readMultiLineStringGeometry_, - 'MultiPolygon': ol.format.GeoJSON.readMultiPolygonGeometry_, - 'GeometryCollection': ol.format.GeoJSON.readGeometryCollectionGeometry_ -}; +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.math'); +goog.require('goog.style'); +goog.require('ol.Object'); +goog.require('ol.TransformFunction'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.proj'); +goog.require('ol.proj.METERS_PER_UNIT'); +goog.require('ol.proj.Units'); +goog.require('ol.sphere.NORMAL'); /** - * @const - * @private - * @type {Object.<string, function(ol.geom.Geometry): (GeoJSONGeometry|GeoJSONGeometryCollection)>} + * @enum {string} */ -ol.format.GeoJSON.GEOMETRY_WRITERS_ = { - 'Point': ol.format.GeoJSON.writePointGeometry_, - 'LineString': ol.format.GeoJSON.writeLineStringGeometry_, - 'Polygon': ol.format.GeoJSON.writePolygonGeometry_, - 'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_, - 'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_, - 'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_, - 'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_, - 'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ +ol.control.ScaleLineProperty = { + UNITS: 'units' }; /** - * @inheritDoc + * Units for the scale line. Supported values are `'degrees'`, `'imperial'`, + * `'nautical'`, `'metric'`, `'us'`. + * @enum {string} + * @api stable */ -ol.format.GeoJSON.prototype.getExtensions = function() { - return ol.format.GeoJSON.EXTENSIONS_; +ol.control.ScaleLineUnits = { + DEGREES: 'degrees', + IMPERIAL: 'imperial', + NAUTICAL: 'nautical', + METRIC: 'metric', + US: 'us' }; + /** - * Read a feature from a GeoJSON Feature source. Only works for Feature, - * use `readFeatures` to read FeatureCollection source. + * @classdesc + * A control displaying rough x-axis distances, calculated for the center of the + * viewport. + * No scale line will be shown when the x-axis distance cannot be calculated in + * the view projection (e.g. at or beyond the poles in EPSG:4326). + * By default the scale line will show in the bottom left portion of the map, + * but this can be changed by using the css selector `.ol-scale-line`. * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ScaleLineOptions=} opt_options Scale line options. * @api stable */ -ol.format.GeoJSON.prototype.readFeature; +ol.control.ScaleLine = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + var className = goog.isDef(options.className) ? + options.className : 'ol-scale-line'; + + /** + * @private + * @type {Element} + */ + this.innerElement_ = goog.dom.createDom(goog.dom.TagName.DIV, + className + '-inner'); + + /** + * @private + * @type {Element} + */ + this.element_ = goog.dom.createDom(goog.dom.TagName.DIV, + className + ' ' + ol.css.CLASS_UNSELECTABLE, this.innerElement_); + + /** + * @private + * @type {?olx.ViewState} + */ + this.viewState_ = null; + + /** + * @private + * @type {number} + */ + this.minWidth_ = goog.isDef(options.minWidth) ? options.minWidth : 64; + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = false; + + /** + * @private + * @type {number|undefined} + */ + this.renderedWidth_ = undefined; + + /** + * @private + * @type {string} + */ + this.renderedHTML_ = ''; + + /** + * @private + * @type {?ol.TransformFunction} + */ + this.toEPSG4326_ = null; + + var render = goog.isDef(options.render) ? + options.render : ol.control.ScaleLine.render; + + goog.base(this, { + element: this.element_, + render: render, + target: options.target + }); + + goog.events.listen( + this, ol.Object.getChangeEventType(ol.control.ScaleLineProperty.UNITS), + this.handleUnitsChanged_, false, this); + + this.setUnits(/** @type {ol.control.ScaleLineUnits} */ (options.units) || + ol.control.ScaleLineUnits.METRIC); + +}; +goog.inherits(ol.control.ScaleLine, ol.control.Control); /** - * Read all features from a GeoJSON source. Works with both Feature and - * FeatureCollection sources. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @const + * @type {Array.<number>} */ -ol.format.GeoJSON.prototype.readFeatures; +ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5]; /** - * @inheritDoc + * Return the units to use in the scale line. + * @return {ol.control.ScaleLineUnits|undefined} The units to use in the scale + * line. + * @observable + * @api stable */ -ol.format.GeoJSON.prototype.readFeatureFromObject = function( - object, opt_options) { - var geoJSONFeature = /** @type {GeoJSONFeature} */ (object); - goog.asserts.assert(geoJSONFeature.type == 'Feature'); - var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry, - opt_options); - var feature = new ol.Feature(); - if (goog.isDef(this.geometryName_)) { - feature.setGeometryName(this.geometryName_); - } - feature.setGeometry(geometry); - if (goog.isDef(geoJSONFeature.id)) { - feature.setId(geoJSONFeature.id); - } - if (goog.isDef(geoJSONFeature.properties)) { - feature.setProperties(geoJSONFeature.properties); - } - return feature; +ol.control.ScaleLine.prototype.getUnits = function() { + return /** @type {ol.control.ScaleLineUnits|undefined} */ ( + this.get(ol.control.ScaleLineProperty.UNITS)); }; /** - * @inheritDoc + * Update the scale line element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.ScaleLine} + * @api */ -ol.format.GeoJSON.prototype.readFeaturesFromObject = function( - object, opt_options) { - var geoJSONObject = /** @type {GeoJSONObject} */ (object); - if (geoJSONObject.type == 'Feature') { - return [this.readFeatureFromObject(object, opt_options)]; - } else if (geoJSONObject.type == 'FeatureCollection') { - var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */ - (object); - /** @type {Array.<ol.Feature>} */ - var features = []; - var geoJSONFeatures = geoJSONFeatureCollection.features; - var i, ii; - for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) { - features.push(this.readFeatureFromObject(geoJSONFeatures[i], - opt_options)); - } - return features; +ol.control.ScaleLine.render = function(mapEvent) { + var frameState = mapEvent.frameState; + if (goog.isNull(frameState)) { + this.viewState_ = null; } else { - goog.asserts.fail(); - return []; + this.viewState_ = frameState.viewState; } + this.updateElement_(); }; /** - * Read a geometry from a GeoJSON source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.geom.Geometry} Geometry. - * @api stable - */ -ol.format.GeoJSON.prototype.readGeometry; - - -/** - * @inheritDoc + * @private */ -ol.format.GeoJSON.prototype.readGeometryFromObject = function( - object, opt_options) { - return ol.format.GeoJSON.readGeometry_( - /** @type {GeoJSONGeometry} */ (object), opt_options); +ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() { + this.updateElement_(); }; /** - * Read the projection from a GeoJSON source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.proj.Projection} Projection. + * Set the units to use in the scale line. + * @param {ol.control.ScaleLineUnits} units The units to use in the scale line. + * @observable * @api stable */ -ol.format.GeoJSON.prototype.readProjection; +ol.control.ScaleLine.prototype.setUnits = function(units) { + this.set(ol.control.ScaleLineProperty.UNITS, units); +}; /** - * @inheritDoc + * @private */ -ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) { - var geoJSONObject = /** @type {GeoJSONObject} */ (object); - var crs = geoJSONObject.crs; - if (goog.isDefAndNotNull(crs)) { - if (crs.type == 'name') { - return ol.proj.get(crs.properties.name); - } else if (crs.type == 'EPSG') { - // 'EPSG' is not part of the GeoJSON specification, but is generated by - // GeoServer. - // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996 - // is fixed and widely deployed. - return ol.proj.get('EPSG:' + crs.properties.code); - } else { - goog.asserts.fail(); - return null; +ol.control.ScaleLine.prototype.updateElement_ = function() { + var viewState = this.viewState_; + + if (goog.isNull(viewState)) { + if (this.renderedVisible_) { + goog.style.setElementShown(this.element_, false); + this.renderedVisible_ = false; } - } else { - return this.defaultDataProjection; + return; } -}; + var center = viewState.center; + var projection = viewState.projection; + var pointResolution = + projection.getPointResolution(viewState.resolution, center); + var projectionUnits = projection.getUnits(); -/** - * Encode a feature as a GeoJSON Feature string. - * - * @function - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions} options Write options. - * @return {string} GeoJSON. - * @api stable - */ -ol.format.GeoJSON.prototype.writeFeature; + var cosLatitude; + var units = this.getUnits(); + if (projectionUnits == ol.proj.Units.DEGREES && + (units == ol.control.ScaleLineUnits.METRIC || + units == ol.control.ScaleLineUnits.IMPERIAL || + units == ol.control.ScaleLineUnits.US || + units == ol.control.ScaleLineUnits.NAUTICAL)) { + // Convert pointResolution from degrees to meters + this.toEPSG4326_ = null; + cosLatitude = Math.cos(goog.math.toRadians(center[1])); + pointResolution *= Math.PI * cosLatitude * ol.sphere.NORMAL.radius / 180; + projectionUnits = ol.proj.Units.METERS; -/** - * Encode a feature as a GeoJSON Feature object. - * - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @api - * @return {Object} Object. - */ -ol.format.GeoJSON.prototype.writeFeatureObject = function( - feature, opt_options) { - opt_options = this.adaptOptions(opt_options); - var object = { - 'type': 'Feature' - }; - var id = feature.getId(); - if (goog.isDefAndNotNull(id)) { - goog.object.set(object, 'id', id); - } - var geometry = feature.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - goog.object.set( - object, 'geometry', - ol.format.GeoJSON.writeGeometry_(geometry, opt_options)); + } else if (projectionUnits != ol.proj.Units.DEGREES && + units == ol.control.ScaleLineUnits.DEGREES) { + + // Convert pointResolution from other units to degrees + if (goog.isNull(this.toEPSG4326_)) { + this.toEPSG4326_ = ol.proj.getTransformFromProjections( + projection, ol.proj.get('EPSG:4326')); + } + cosLatitude = Math.cos(goog.math.toRadians(this.toEPSG4326_(center)[1])); + var radius = ol.sphere.NORMAL.radius; + goog.asserts.assert(goog.isDef(ol.proj.METERS_PER_UNIT[projectionUnits]), + 'Meters per unit should be defined for the projection unit'); + radius /= ol.proj.METERS_PER_UNIT[projectionUnits]; + pointResolution *= 180 / (Math.PI * cosLatitude * radius); + projectionUnits = ol.proj.Units.DEGREES; + + } else { + this.toEPSG4326_ = null; } - var properties = feature.getProperties(); - goog.object.remove(properties, feature.getGeometryName()); - if (!goog.object.isEmpty(properties)) { - goog.object.set(object, 'properties', properties); + + goog.asserts.assert( + ((units == ol.control.ScaleLineUnits.METRIC || + units == ol.control.ScaleLineUnits.IMPERIAL || + units == ol.control.ScaleLineUnits.US || + units == ol.control.ScaleLineUnits.NAUTICAL) && + projectionUnits == ol.proj.Units.METERS) || + (units == ol.control.ScaleLineUnits.DEGREES && + projectionUnits == ol.proj.Units.DEGREES), + 'Scale line units and projection units should match'); + + var nominalCount = this.minWidth_ * pointResolution; + var suffix = ''; + if (units == ol.control.ScaleLineUnits.DEGREES) { + if (nominalCount < 1 / 60) { + suffix = '\u2033'; // seconds + pointResolution *= 3600; + } else if (nominalCount < 1) { + suffix = '\u2032'; // minutes + pointResolution *= 60; + } else { + suffix = '\u00b0'; // degrees + } + } else if (units == ol.control.ScaleLineUnits.IMPERIAL) { + if (nominalCount < 0.9144) { + suffix = 'in'; + pointResolution /= 0.0254; + } else if (nominalCount < 1609.344) { + suffix = 'ft'; + pointResolution /= 0.3048; + } else { + suffix = 'mi'; + pointResolution /= 1609.344; + } + } else if (units == ol.control.ScaleLineUnits.NAUTICAL) { + pointResolution /= 1852; + suffix = 'nm'; + } else if (units == ol.control.ScaleLineUnits.METRIC) { + if (nominalCount < 1) { + suffix = 'mm'; + pointResolution *= 1000; + } else if (nominalCount < 1000) { + suffix = 'm'; + } else { + suffix = 'km'; + pointResolution /= 1000; + } + } else if (units == ol.control.ScaleLineUnits.US) { + if (nominalCount < 0.9144) { + suffix = 'in'; + pointResolution *= 39.37; + } else if (nominalCount < 1609.344) { + suffix = 'ft'; + pointResolution /= 0.30480061; + } else { + suffix = 'mi'; + pointResolution /= 1609.3472; + } + } else { + goog.asserts.fail('Scale line element cannot be updated'); } - return object; -}; - - -/** - * Encode an array of features as GeoJSON. - * - * @function - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions} options Write options. - * @return {string} GeoJSON. - * @api stable - */ -ol.format.GeoJSON.prototype.writeFeatures; - -/** - * Encode an array of features as a GeoJSON object. - * - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {Object} GeoJSON Object. - * @api - */ -ol.format.GeoJSON.prototype.writeFeaturesObject = - function(features, opt_options) { - opt_options = this.adaptOptions(opt_options); - var objects = []; - var i, ii; - for (i = 0, ii = features.length; i < ii; ++i) { - objects.push(this.writeFeatureObject(features[i], opt_options)); + var i = 3 * Math.floor( + Math.log(this.minWidth_ * pointResolution) / Math.log(10)); + var count, width; + while (true) { + count = ol.control.ScaleLine.LEADING_DIGITS[i % 3] * + Math.pow(10, Math.floor(i / 3)); + width = Math.round(count / pointResolution); + if (isNaN(width)) { + goog.style.setElementShown(this.element_, false); + this.renderedVisible_ = false; + return; + } else if (width >= this.minWidth_) { + break; + } + ++i; } - return /** @type {GeoJSONFeatureCollection} */ ({ - 'type': 'FeatureCollection', - 'features': objects - }); -}; + var html = count + ' ' + suffix; + if (this.renderedHTML_ != html) { + this.innerElement_.innerHTML = html; + this.renderedHTML_ = html; + } -/** - * Encode a geometry as a GeoJSON string. - * - * @function - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions} options Write options. - * @return {string} GeoJSON. - * @api stable - */ -ol.format.GeoJSON.prototype.writeGeometry; + if (this.renderedWidth_ != width) { + this.innerElement_.style.width = width + 'px'; + this.renderedWidth_ = width; + } + if (!this.renderedVisible_) { + goog.style.setElementShown(this.element_, true); + this.renderedVisible_ = true; + } -/** - * Encode a geometry as a GeoJSON object. - * - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {GeoJSONGeometry|GeoJSONGeometryCollection} Object. - * @api - */ -ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry, - opt_options) { - return ol.format.GeoJSON.writeGeometry_(geometry, - this.adaptOptions(opt_options)); }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// Copyright 2005 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -82695,3547 +85106,3424 @@ ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry, // limitations under the License. /** - * @fileoverview - * XML utilities. + * @fileoverview Class to create objects which want to handle multiple events + * and have their listeners easily cleaned up via a dispose method. + * + * Example: + * <pre> + * function Something() { + * Something.base(this); + * + * ... set up object ... + * + * // Add event listeners + * this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar); + * this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand); + * this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse); + * this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover); + * this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover); + * } + * goog.inherits(Something, goog.events.EventHandler); + * + * Something.prototype.disposeInternal = function() { + * Something.base(this, 'disposeInternal'); + * goog.dom.removeNode(this.container); + * }; + * + * + * // Then elsewhere: + * + * var activeSomething = null; + * function openSomething() { + * activeSomething = new Something(); + * } + * + * function closeSomething() { + * if (activeSomething) { + * activeSomething.dispose(); // Remove event listeners + * activeSomething = null; + * } + * } + * </pre> * */ -goog.provide('goog.dom.xml'); +goog.provide('goog.events.EventHandler'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); +goog.require('goog.Disposable'); +goog.require('goog.events'); +goog.require('goog.object'); +goog.forwardDeclare('goog.events.EventWrapper'); -/** - * Max XML size for MSXML2. Used to prevent potential DoS attacks. - * @type {number} - */ -goog.dom.xml.MAX_XML_SIZE_KB = 2 * 1024; // In kB /** - * Max XML size for MSXML2. Used to prevent potential DoS attacks. - * @type {number} + * Super class for objects that want to easily manage a number of event + * listeners. It allows a short cut to listen and also provides a quick way + * to remove all events listeners belonging to this object. + * @param {SCOPE=} opt_scope Object in whose scope to call the listeners. + * @constructor + * @extends {goog.Disposable} + * @template SCOPE */ -goog.dom.xml.MAX_ELEMENT_DEPTH = 256; // Same default as MSXML6. - +goog.events.EventHandler = function(opt_scope) { + goog.Disposable.call(this); + // TODO(mknichel): Rename this to this.scope_ and fix the classes in google3 + // that access this private variable. :( + this.handler_ = opt_scope; -/** - * Creates an XML document appropriate for the current JS runtime - * @param {string=} opt_rootTagName The root tag name. - * @param {string=} opt_namespaceUri Namespace URI of the document element. - * @return {Document} The new document. - */ -goog.dom.xml.createDocument = function(opt_rootTagName, opt_namespaceUri) { - if (opt_namespaceUri && !opt_rootTagName) { - throw Error("Can't create document with namespace and no root tag"); - } - if (document.implementation && document.implementation.createDocument) { - return document.implementation.createDocument(opt_namespaceUri || '', - opt_rootTagName || '', - null); - } else if (typeof ActiveXObject != 'undefined') { - var doc = goog.dom.xml.createMsXmlDocument_(); - if (doc) { - if (opt_rootTagName) { - doc.appendChild(doc.createNode(goog.dom.NodeType.ELEMENT, - opt_rootTagName, - opt_namespaceUri || '')); - } - return doc; - } - } - throw Error('Your browser does not support creating new documents'); + /** + * Keys for events that are being listened to. + * @type {!Object<!goog.events.Key>} + * @private + */ + this.keys_ = {}; }; +goog.inherits(goog.events.EventHandler, goog.Disposable); /** - * Creates an XML document from a string - * @param {string} xml The text. - * @return {Document} XML document from the text. + * Utility array used to unify the cases of listening for an array of types + * and listening for a single event, without using recursion or allocating + * an array each time. + * @type {!Array<string>} + * @const + * @private */ -goog.dom.xml.loadXml = function(xml) { - if (typeof DOMParser != 'undefined') { - return new DOMParser().parseFromString(xml, 'application/xml'); - } else if (typeof ActiveXObject != 'undefined') { - var doc = goog.dom.xml.createMsXmlDocument_(); - doc.loadXML(xml); - return doc; - } - throw Error('Your browser does not support loading xml documents'); -}; +goog.events.EventHandler.typeArray_ = []; /** - * Serializes an XML document or subtree to string. - * @param {Document|Element} xml The document or the root node of the subtree. - * @return {string} The serialized XML. + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} + * opt_fn Optional callback function to be used as the listener or an object + * with handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ */ -goog.dom.xml.serialize = function(xml) { - // Compatible with Firefox, Opera and WebKit. - if (typeof XMLSerializer != 'undefined') { - return new XMLSerializer().serializeToString(xml); - } - // Compatible with Internet Explorer. - var text = xml.xml; - if (text) { - return text; - } - throw Error('Your browser does not support serializing XML documents'); +goog.events.EventHandler.prototype.listen = function( + src, type, opt_fn, opt_capture) { + return this.listen_(src, type, opt_fn, opt_capture); }; /** - * Selects a single node using an Xpath expression and a root node - * @param {Node} node The root node. - * @param {string} path Xpath selector. - * @return {Node} The selected node, or null if no matching node. + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| + * null|undefined} fn Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template T,EVENTOBJ */ -goog.dom.xml.selectSingleNode = function(node, path) { - if (typeof node.selectSingleNode != 'undefined') { - var doc = goog.dom.getOwnerDocument(node); - if (typeof doc.setProperty != 'undefined') { - doc.setProperty('SelectionLanguage', 'XPath'); - } - return node.selectSingleNode(path); - } else if (document.implementation.hasFeature('XPath', '3.0')) { - var doc = goog.dom.getOwnerDocument(node); - var resolver = doc.createNSResolver(doc.documentElement); - var result = doc.evaluate(path, node, resolver, - XPathResult.FIRST_ORDERED_NODE_TYPE, null); - return result.singleNodeValue; - } - return null; +goog.events.EventHandler.prototype.listenWithScope = function( + src, type, fn, capture, scope) { + // TODO(mknichel): Deprecate this function. + return this.listen_(src, type, fn, capture, scope); }; /** - * Selects multiple nodes using an Xpath expression and a root node - * @param {Node} node The root node. - * @param {string} path Xpath selector. - * @return {(NodeList,Array<Node>)} The selected nodes, or empty array if no - * matching nodes. + * Listen to an event on a Listenable. If the function is omitted then the + * EventHandler's handleEvent method will be used. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ + * @private */ -goog.dom.xml.selectNodes = function(node, path) { - if (typeof node.selectNodes != 'undefined') { - var doc = goog.dom.getOwnerDocument(node); - if (typeof doc.setProperty != 'undefined') { - doc.setProperty('SelectionLanguage', 'XPath'); - } - return node.selectNodes(path); - } else if (document.implementation.hasFeature('XPath', '3.0')) { - var doc = goog.dom.getOwnerDocument(node); - var resolver = doc.createNSResolver(doc.documentElement); - var nodes = doc.evaluate(path, node, resolver, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - var results = []; - var count = nodes.snapshotLength; - for (var i = 0; i < count; i++) { - results.push(nodes.snapshotItem(i)); +goog.events.EventHandler.prototype.listen_ = function(src, type, opt_fn, + opt_capture, + opt_scope) { + if (!goog.isArray(type)) { + if (type) { + goog.events.EventHandler.typeArray_[0] = type.toString(); } - return results; - } else { - return []; + type = goog.events.EventHandler.typeArray_; } -}; - + for (var i = 0; i < type.length; i++) { + var listenerObj = goog.events.listen( + src, type[i], opt_fn || this.handleEvent, + opt_capture || false, + opt_scope || this.handler_ || this); -/** - * Sets multiple attributes on an element. Differs from goog.dom.setProperties - * in that it exclusively uses the element's setAttributes method. Use this - * when you need to ensure that the exact property is available as an attribute - * and can be read later by the native getAttribute method. - * @param {!Element} element XML or DOM element to set attributes on. - * @param {!Object<string, string>} attributes Map of property:value pairs. - */ -goog.dom.xml.setAttributes = function(element, attributes) { - for (var key in attributes) { - if (attributes.hasOwnProperty(key)) { - element.setAttribute(key, attributes[key]); + if (!listenerObj) { + // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT + // (goog.events.CaptureSimulationMode) in IE8-, it will return null + // value. + return this; } - } -}; - -/** - * Creates an instance of the MSXML2.DOMDocument. - * @return {Document} The new document. - * @private - */ -goog.dom.xml.createMsXmlDocument_ = function() { - var doc = new ActiveXObject('MSXML2.DOMDocument'); - if (doc) { - // Prevent potential vulnerabilities exposed by MSXML2, see - // http://b/1707300 and http://wiki/Main/ISETeamXMLAttacks for details. - doc.resolveExternals = false; - doc.validateOnParse = false; - // Add a try catch block because accessing these properties will throw an - // error on unsupported MSXML versions. This affects Windows machines - // running IE6 or IE7 that are on XP SP2 or earlier without MSXML updates. - // See http://msdn.microsoft.com/en-us/library/ms766391(VS.85).aspx for - // specific details on which MSXML versions support these properties. - try { - doc.setProperty('ProhibitDTD', true); - doc.setProperty('MaxXMLSize', goog.dom.xml.MAX_XML_SIZE_KB); - doc.setProperty('MaxElementDepth', goog.dom.xml.MAX_ELEMENT_DEPTH); - } catch (e) { - // No-op. - } + var key = listenerObj.key; + this.keys_[key] = listenerObj; } - return doc; -}; - -// FIXME Remove ol.xml.makeParsersNS, and use ol.xml.makeStructureNS instead. - -goog.provide('ol.xml'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.xml'); -goog.require('goog.object'); -goog.require('goog.userAgent'); - - -/** - * When using {@link ol.xml.makeChildAppender} or - * {@link ol.xml.makeSimpleNodeFactory}, the top `objectStack` item needs to - * have this structure. - * @typedef {{node:Node}} - */ -ol.xml.NodeStackItem; - - -/** - * @typedef {function(Node, Array.<*>)} - */ -ol.xml.Parser; - - -/** - * @typedef {function(Node, *, Array.<*>)} - */ -ol.xml.Serializer; - - -/** - * This document should be used when creating nodes for XML serializations. This - * document is also used by {@link ol.xml.createElementNS} and - * {@link ol.xml.setAttributeNS} - * @const - * @type {Document} - */ -ol.xml.DOCUMENT = goog.dom.xml.createDocument(); - -/** - * @param {string} namespaceURI Namespace URI. - * @param {string} qualifiedName Qualified name. - * @return {Node} Node. - * @private - */ -ol.xml.createElementNS_ = function(namespaceURI, qualifiedName) { - return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName); + return this; }; /** - * @param {string} namespaceURI Namespace URI. - * @param {string} qualifiedName Qualified name. - * @return {Node} Node. - * @private + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired the + * event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ */ -ol.xml.createElementNSActiveX_ = function(namespaceURI, qualifiedName) { - if (goog.isNull(namespaceURI)) { - namespaceURI = ''; - } - return ol.xml.DOCUMENT.createNode(1, qualifiedName, namespaceURI); +goog.events.EventHandler.prototype.listenOnce = function( + src, type, opt_fn, opt_capture) { + return this.listenOnce_(src, type, opt_fn, opt_capture); }; /** - * @param {string} namespaceURI Namespace URI. - * @param {string} qualifiedName Qualified name. - * @return {Node} Node. - */ -ol.xml.createElementNS = - (document.implementation && document.implementation.createDocument) ? - ol.xml.createElementNS_ : ol.xml.createElementNSActiveX_; - - -/** - * Recursively grab all text content of child nodes into a single string. - * @param {Node} node Node. - * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line - * breaks. - * @return {string} All text content. - * @api + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired the + * event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}| + * null|undefined} fn Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template T,EVENTOBJ */ -ol.xml.getAllTextContent = function(node, normalizeWhitespace) { - return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join(''); +goog.events.EventHandler.prototype.listenOnceWithScope = function( + src, type, fn, capture, scope) { + // TODO(mknichel): Deprecate this function. + return this.listenOnce_(src, type, fn, capture, scope); }; /** - * @param {Node} node Node. - * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line - * breaks. - * @param {Array.<String|string>} accumulator Accumulator. + * Listen to an event on a Listenable. If the function is omitted, then the + * EventHandler's handleEvent method will be used. After the event has fired + * the event listener is removed from the target. If an array of event types is + * provided, each event type will be listened to once. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type to listen for or array of event types. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template EVENTOBJ * @private - * @return {Array.<String|string>} Accumulator. */ -ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) { - if (node.nodeType == goog.dom.NodeType.CDATA_SECTION || - node.nodeType == goog.dom.NodeType.TEXT) { - if (normalizeWhitespace) { - // FIXME understand why goog.dom.getTextContent_ uses String here - accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, '')); - } else { - accumulator.push(node.nodeValue); +goog.events.EventHandler.prototype.listenOnce_ = function( + src, type, opt_fn, opt_capture, opt_scope) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + this.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope); } } else { - var n; - for (n = node.firstChild; !goog.isNull(n); n = n.nextSibling) { - ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator); + var listenerObj = goog.events.listenOnce( + src, type, opt_fn || this.handleEvent, opt_capture, + opt_scope || this.handler_ || this); + if (!listenerObj) { + // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT + // (goog.events.CaptureSimulationMode) in IE8-, it will return null + // value. + return this; } - } - return accumulator; -}; + var key = listenerObj.key; + this.keys_[key] = listenerObj; + } -/** - * @param {Node} node Node. - * @private - * @return {string} Local name. - */ -ol.xml.getLocalName_ = function(node) { - return node.localName; + return this; }; /** - * @param {Node} node Node. - * @private - * @return {string} Local name. + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. */ -ol.xml.getLocalNameIE_ = function(node) { - var localName = node.localName; - if (goog.isDef(localName)) { - return localName; - } - var baseName = node.baseName; - goog.asserts.assert(goog.isDefAndNotNull(baseName)); - return baseName; +goog.events.EventHandler.prototype.listenWithWrapper = function( + src, wrapper, listener, opt_capt) { + // TODO(mknichel): Remove the opt_scope from this function and then + // templatize it. + return this.listenWithWrapper_(src, wrapper, listener, opt_capt); }; /** - * @param {Node} node Node. - * @return {string} Local name. + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null} + * listener Optional callback function to be used as the + * listener or an object with handleEvent function. + * @param {boolean|undefined} capture Optional whether to use capture phase. + * @param {T} scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. + * @template T */ -ol.xml.getLocalName = goog.userAgent.IE ? - ol.xml.getLocalNameIE_ : ol.xml.getLocalName_; +goog.events.EventHandler.prototype.listenWithWrapperAndScope = function( + src, wrapper, listener, capture, scope) { + // TODO(mknichel): Deprecate this function. + return this.listenWithWrapper_(src, wrapper, listener, capture, scope); +}; /** - * @param {?} value Value. + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback + * method, or an object with a handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @return {!goog.events.EventHandler<SCOPE>} This object, allowing for + * chaining of calls. * @private - * @return {boolean} Is document. */ -ol.xml.isDocument_ = function(value) { - return value instanceof Document; +goog.events.EventHandler.prototype.listenWithWrapper_ = function( + src, wrapper, listener, opt_capt, opt_scope) { + wrapper.listen(src, listener, opt_capt, opt_scope || this.handler_ || this, + this); + return this; }; /** - * @param {?} value Value. - * @private - * @return {boolean} Is document. + * @return {number} Number of listeners registered by this handler. */ -ol.xml.isDocumentIE_ = function(value) { - return goog.isObject(value) && value.nodeType == goog.dom.NodeType.DOCUMENT; +goog.events.EventHandler.prototype.getListenerCount = function() { + var count = 0; + for (var key in this.keys_) { + if (Object.prototype.hasOwnProperty.call(this.keys_, key)) { + count++; + } + } + return count; }; /** - * @param {?} value Value. - * @return {boolean} Is document. + * Unlistens on an event. + * @param {goog.events.ListenableType} src Event source. + * @param {string|Array<string>| + * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>} + * type Event type or array of event types to unlisten to. + * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn + * Optional callback function to be used as the listener or an object with + * handleEvent function. + * @param {boolean=} opt_capture Optional whether to use capture phase. + * @param {Object=} opt_scope Object in whose scope to call the listener. + * @return {!goog.events.EventHandler} This object, allowing for chaining of + * calls. + * @template EVENTOBJ */ -ol.xml.isDocument = goog.userAgent.IE ? - ol.xml.isDocumentIE_ : ol.xml.isDocument_; +goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn, + opt_capture, + opt_scope) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + this.unlisten(src, type[i], opt_fn, opt_capture, opt_scope); + } + } else { + var listener = goog.events.getListener(src, type, + opt_fn || this.handleEvent, + opt_capture, opt_scope || this.handler_ || this); + if (listener) { + goog.events.unlistenByKey(listener); + delete this.keys_[listener.key]; + } + } -/** - * @param {?} value Value. - * @private - * @return {boolean} Is node. - */ -ol.xml.isNode_ = function(value) { - return value instanceof Node; + return this; }; /** - * @param {?} value Value. - * @private - * @return {boolean} Is node. + * Removes an event listener which was added with listenWithWrapper(). + * + * @param {EventTarget|goog.events.EventTarget} src The target to stop + * listening to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @return {!goog.events.EventHandler} This object, allowing for chaining of + * calls. */ -ol.xml.isNodeIE_ = function(value) { - return goog.isObject(value) && goog.isDef(value.nodeType); +goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper, + listener, opt_capt, opt_scope) { + wrapper.unlisten(src, listener, opt_capt, + opt_scope || this.handler_ || this, this); + return this; }; /** - * @param {?} value Value. - * @return {boolean} Is node. + * Unlistens to all events. */ -ol.xml.isNode = goog.userAgent.IE ? ol.xml.isNodeIE_ : ol.xml.isNode_; +goog.events.EventHandler.prototype.removeAll = function() { + goog.object.forEach(this.keys_, goog.events.unlistenByKey); + this.keys_ = {}; +}; /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @return {string} Value - * @private + * Disposes of this EventHandler and removes all listeners that it registered. + * @override + * @protected */ -ol.xml.getAttributeNS_ = function(node, namespaceURI, name) { - return node.getAttributeNS(namespaceURI, name) || ''; +goog.events.EventHandler.prototype.disposeInternal = function() { + goog.events.EventHandler.superClass_.disposeInternal.call(this); + this.removeAll(); }; /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @return {string} Value - * @private + * Default event handler + * @param {goog.events.Event} e Event object. */ -ol.xml.getAttributeNSActiveX_ = function(node, namespaceURI, name) { - var attributeValue = ''; - var attributeNode = ol.xml.getAttributeNodeNS(node, namespaceURI, name); - if (goog.isDef(attributeNode)) { - attributeValue = attributeNode.nodeValue; - } - return attributeValue; +goog.events.EventHandler.prototype.handleEvent = function(e) { + throw Error('EventHandler.handleEvent not implemented'); }; +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @return {string} Value + * @fileoverview Bidi utility functions. + * */ -ol.xml.getAttributeNS = - (document.implementation && document.implementation.createDocument) ? - ol.xml.getAttributeNS_ : ol.xml.getAttributeNSActiveX_; +goog.provide('goog.style.bidi'); -/** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @return {?Node} Attribute node or null if none found. - * @private - */ -ol.xml.getAttributeNodeNS_ = function(node, namespaceURI, name) { - return node.getAttributeNodeNS(namespaceURI, name); -}; +goog.require('goog.dom'); +goog.require('goog.style'); +goog.require('goog.userAgent'); /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @return {?Node} Attribute node or null if none found. - * @private + * Returns the normalized scrollLeft position for a scrolled element. + * @param {Element} element The scrolled element. + * @return {number} The number of pixels the element is scrolled. 0 indicates + * that the element is not scrolled at all (which, in general, is the + * left-most position in ltr and the right-most position in rtl). */ -ol.xml.getAttributeNodeNSActiveX_ = function(node, namespaceURI, name) { - var attributeNode = null; - var attributes = node.attributes; - var potentialNode, fullName; - for (var i = 0, len = attributes.length; i < len; ++i) { - potentialNode = attributes[i]; - if (potentialNode.namespaceURI == namespaceURI) { - fullName = (potentialNode.prefix) ? - (potentialNode.prefix + ':' + name) : name; - if (fullName == potentialNode.nodeName) { - attributeNode = potentialNode; - break; - } +goog.style.bidi.getScrollLeft = function(element) { + var isRtl = goog.style.isRightToLeft(element); + if (isRtl && goog.userAgent.GECKO) { + // ScrollLeft starts at 0 and then goes negative as the element is scrolled + // towards the left. + return -element.scrollLeft; + } else if (isRtl && + !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) { + // ScrollLeft starts at the maximum positive value and decreases towards + // 0 as the element is scrolled towards the left. However, for overflow + // visible, there is no scrollLeft and the value always stays correctly at 0 + var overflowX = goog.style.getComputedOverflowX(element); + if (overflowX == 'visible') { + return element.scrollLeft; + } else { + return element.scrollWidth - element.clientWidth - element.scrollLeft; } } - return attributeNode; + // ScrollLeft behavior is identical in rtl and ltr, it starts at 0 and + // increases as the element is scrolled away from the start. + return element.scrollLeft; }; /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @return {?Node} Attribute node or null if none found. + * Returns the "offsetStart" of an element, analagous to offsetLeft but + * normalized for right-to-left environments and various browser + * inconsistencies. This value returned can always be passed to setScrollOffset + * to scroll to an element's left edge in a left-to-right offsetParent or + * right edge in a right-to-left offsetParent. + * + * For example, here offsetStart is 10px in an LTR environment and 5px in RTL: + * + * <pre> + * | xxxxxxxxxx | + * ^^^^^^^^^^ ^^^^ ^^^^^ + * 10px elem 5px + * </pre> + * + * If an element is positioned before the start of its offsetParent, the + * startOffset may be negative. This can be used with setScrollOffset to + * reliably scroll to an element: + * + * <pre> + * var scrollOffset = goog.style.bidi.getOffsetStart(element); + * goog.style.bidi.setScrollOffset(element.offsetParent, scrollOffset); + * </pre> + * + * @see setScrollOffset + * + * @param {Element} element The element for which we need to determine the + * offsetStart position. + * @return {number} The offsetStart for that element. */ -ol.xml.getAttributeNodeNS = - (document.implementation && document.implementation.createDocument) ? - ol.xml.getAttributeNodeNS_ : ol.xml.getAttributeNodeNSActiveX_; +goog.style.bidi.getOffsetStart = function(element) { + var offsetLeftForReal = element.offsetLeft; + + // The element might not have an offsetParent. + // For example, the node might not be attached to the DOM tree, + // and position:fixed children do not have an offset parent. + // Just try to do the best we can with what we have. + var bestParent = element.offsetParent; + if (!bestParent && goog.style.getComputedPosition(element) == 'fixed') { + bestParent = goog.dom.getOwnerDocument(element).documentElement; + } -/** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @param {string|number} value Value. - * @private - */ -ol.xml.setAttributeNS_ = function(node, namespaceURI, name, value) { - node.setAttributeNS(namespaceURI, name, value); + // Just give up in this case. + if (!bestParent) { + return offsetLeftForReal; + } + + if (goog.userAgent.GECKO) { + // When calculating an element's offsetLeft, Firefox erroneously subtracts + // the border width from the actual distance. So we need to add it back. + var borderWidths = goog.style.getBorderBox(bestParent); + offsetLeftForReal += borderWidths.left; + } else if (goog.userAgent.isDocumentModeOrHigher(8) && + !goog.userAgent.isDocumentModeOrHigher(9)) { + // When calculating an element's offsetLeft, IE8/9-Standards Mode + // erroneously adds the border width to the actual distance. So we need to + // subtract it. + var borderWidths = goog.style.getBorderBox(bestParent); + offsetLeftForReal -= borderWidths.left; + } + + if (goog.style.isRightToLeft(bestParent)) { + // Right edge of the element relative to the left edge of its parent. + var elementRightOffset = offsetLeftForReal + element.offsetWidth; + + // Distance from the parent's right edge to the element's right edge. + return bestParent.clientWidth - elementRightOffset; + } + + return offsetLeftForReal; }; /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @param {string|number} value Value. - * @private + * Sets the element's scrollLeft attribute so it is correctly scrolled by + * offsetStart pixels. This takes into account whether the element is RTL and + * the nuances of different browsers. To scroll to the "beginning" of an + * element use getOffsetStart to obtain the element's offsetStart value and then + * pass the value to setScrollOffset. + * @see getOffsetStart + * @param {Element} element The element to set scrollLeft on. + * @param {number} offsetStart The number of pixels to scroll the element. + * If this value is < 0, 0 is used. */ -ol.xml.setAttributeNSActiveX_ = function(node, namespaceURI, name, value) { - if (!goog.isNull(namespaceURI)) { - var attribute = node.ownerDocument.createNode(2, name, namespaceURI); - attribute.nodeValue = value; - node.setAttributeNode(attribute); +goog.style.bidi.setScrollOffset = function(element, offsetStart) { + offsetStart = Math.max(offsetStart, 0); + // In LTR and in "mirrored" browser RTL (such as IE), we set scrollLeft to + // the number of pixels to scroll. + // Otherwise, in RTL, we need to account for different browser behavior. + if (!goog.style.isRightToLeft(element)) { + element.scrollLeft = offsetStart; + } else if (goog.userAgent.GECKO) { + // Negative scroll-left positions in RTL. + element.scrollLeft = -offsetStart; + } else if (!(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8'))) { + // Take the current scrollLeft value and move to the right by the + // offsetStart to get to the left edge of the element, and then by + // the clientWidth of the element to get to the right edge. + element.scrollLeft = + element.scrollWidth - offsetStart - element.clientWidth; } else { - node.setAttribute(name, value); + element.scrollLeft = offsetStart; } }; /** - * @param {Node} node Node. - * @param {?string} namespaceURI Namespace URI. - * @param {string} name Attribute name. - * @param {string|number} value Value. + * Sets the element's left style attribute in LTR or right style attribute in + * RTL. Also clears the left attribute in RTL and the right attribute in LTR. + * @param {Element} elem The element to position. + * @param {number} left The left position in LTR; will be set as right in RTL. + * @param {?number} top The top position. If null only the left/right is set. + * @param {boolean} isRtl Whether we are in RTL mode. */ -ol.xml.setAttributeNS = - (document.implementation && document.implementation.createDocument) ? - ol.xml.setAttributeNS_ : ol.xml.setAttributeNSActiveX_; +goog.style.bidi.setPosition = function(elem, left, top, isRtl) { + if (!goog.isNull(top)) { + elem.style.top = top + 'px'; + } + if (isRtl) { + elem.style.right = left + 'px'; + elem.style.left = ''; + } else { + elem.style.left = left + 'px'; + elem.style.right = ''; + } +}; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Parse an XML string to a XML Document - * @param {string} xml XML. - * @return {Document} Document. - * @api + * @fileoverview Drag Utilities. + * + * Provides extensible functionality for drag & drop behaviour. + * + * @see ../demos/drag.html + * @see ../demos/dragger.html */ -ol.xml.parse = function(xml) { - return new DOMParser().parseFromString(xml, 'application/xml'); -}; -/** - * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)} - * valueReader Value reader. - * @param {T=} opt_this The object to use as `this` in `valueReader`. - * @return {ol.xml.Parser} Parser. - * @template T - */ -ol.xml.makeArrayExtender = function(valueReader, opt_this) { - return ( - /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - */ - function(node, objectStack) { - var value = valueReader.call(opt_this, node, objectStack); - if (goog.isDef(value)) { - goog.asserts.assert(goog.isArray(value)); - var array = /** @type {Array.<*>} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(array)); - goog.array.extend(array, value); - } - }); -}; +goog.provide('goog.fx.DragEvent'); +goog.provide('goog.fx.Dragger'); +goog.provide('goog.fx.Dragger.EventType'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.math.Coordinate'); +goog.require('goog.math.Rect'); +goog.require('goog.style'); +goog.require('goog.style.bidi'); +goog.require('goog.userAgent'); + /** - * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. - * @param {T=} opt_this The object to use as `this` in `valueReader`. - * @return {ol.xml.Parser} Parser. - * @template T + * A class that allows mouse or touch-based dragging (moving) of an element + * + * @param {Element} target The element that will be dragged. + * @param {Element=} opt_handle An optional handle to control the drag, if null + * the target is used. + * @param {goog.math.Rect=} opt_limits Object containing left, top, width, + * and height. + * + * @extends {goog.events.EventTarget} + * @constructor */ -ol.xml.makeArrayPusher = function(valueReader, opt_this) { - return ( - /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - */ - function(node, objectStack) { - var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, - node, objectStack); - if (goog.isDef(value)) { - var array = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isArray(array)); - array.push(value); - } - }); -}; +goog.fx.Dragger = function(target, opt_handle, opt_limits) { + goog.events.EventTarget.call(this); + this.target = target; + this.handle = opt_handle || target; + this.limits = opt_limits || new goog.math.Rect(NaN, NaN, NaN, NaN); + this.document_ = goog.dom.getOwnerDocument(target); + this.eventHandler_ = new goog.events.EventHandler(this); + this.registerDisposable(this.eventHandler_); -/** - * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. - * @param {T=} opt_this The object to use as `this` in `valueReader`. - * @return {ol.xml.Parser} Parser. - * @template T - */ -ol.xml.makeReplacer = function(valueReader, opt_this) { - return ( - /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - */ - function(node, objectStack) { - var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, - node, objectStack); - if (goog.isDef(value)) { - objectStack[objectStack.length - 1] = value; - } - }); + // Add listener. Do not use the event handler here since the event handler is + // used for listeners added and removed during the drag operation. + goog.events.listen(this.handle, + [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN], + this.startDrag, false, this); }; +goog.inherits(goog.fx.Dragger, goog.events.EventTarget); +// Dragger is meant to be extended, but defines most properties on its +// prototype, thus making it unsuitable for sealing. +goog.tagUnsealableClass(goog.fx.Dragger); /** - * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. - * @param {string=} opt_property Property. - * @param {T=} opt_this The object to use as `this` in `valueReader`. - * @return {ol.xml.Parser} Parser. - * @template T + * Whether setCapture is supported by the browser. + * @type {boolean} + * @private */ -ol.xml.makeObjectPropertyPusher = - function(valueReader, opt_property, opt_this) { - goog.asserts.assert(goog.isDef(valueReader)); - return ( - /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - */ - function(node, objectStack) { - var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, - node, objectStack); - if (goog.isDef(value)) { - var object = /** @type {Object} */ - (objectStack[objectStack.length - 1]); - var property = goog.isDef(opt_property) ? - opt_property : node.localName; - goog.asserts.assert(goog.isObject(object)); - var array = goog.object.setIfUndefined(object, property, []); - array.push(value); - } - }); -}; +goog.fx.Dragger.HAS_SET_CAPTURE_ = + // IE and Gecko after 1.9.3 has setCapture + // WebKit does not yet: https://bugs.webkit.org/show_bug.cgi?id=27330 + goog.userAgent.IE || + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.3'); /** - * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader. - * @param {string=} opt_property Property. - * @param {T=} opt_this The object to use as `this` in `valueReader`. - * @return {ol.xml.Parser} Parser. - * @template T + * Creates copy of node being dragged. This is a utility function to be used + * wherever it is inappropriate for the original source to follow the mouse + * cursor itself. + * + * @param {Element} sourceEl Element to copy. + * @return {!Element} The clone of {@code sourceEl}. */ -ol.xml.makeObjectPropertySetter = - function(valueReader, opt_property, opt_this) { - goog.asserts.assert(goog.isDef(valueReader)); - return ( - /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - */ - function(node, objectStack) { - var value = valueReader.call(goog.isDef(opt_this) ? opt_this : this, - node, objectStack); - if (goog.isDef(value)) { - var object = /** @type {Object} */ - (objectStack[objectStack.length - 1]); - var property = goog.isDef(opt_property) ? - opt_property : node.localName; - goog.asserts.assert(goog.isObject(object)); - goog.object.set(object, property, value); - } - }); +goog.fx.Dragger.cloneNode = function(sourceEl) { + var clonedEl = /** @type {Element} */ (sourceEl.cloneNode(true)), + origTexts = sourceEl.getElementsByTagName('textarea'), + dragTexts = clonedEl.getElementsByTagName('textarea'); + // Cloning does not copy the current value of textarea elements, so correct + // this manually. + for (var i = 0; i < origTexts.length; i++) { + dragTexts[i].value = origTexts[i].value; + } + switch (sourceEl.tagName.toLowerCase()) { + case 'tr': + return goog.dom.createDom( + 'table', null, goog.dom.createDom('tbody', null, clonedEl)); + case 'td': + case 'th': + return goog.dom.createDom( + 'table', null, goog.dom.createDom('tbody', null, goog.dom.createDom( + 'tr', null, clonedEl))); + case 'textarea': + clonedEl.value = sourceEl.value; + default: + return clonedEl; + } }; /** - * @param {Array.<string>} namespaceURIs Namespace URIs. - * @param {Object.<string, ol.xml.Parser>} parsers Parsers. - * @param {Object.<string, Object.<string, ol.xml.Parser>>=} opt_parsersNS - * ParsersNS. - * @return {Object.<string, Object.<string, ol.xml.Parser>>} Parsers NS. + * Constants for event names. + * @enum {string} */ -ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) { - return /** @type {Object.<string, Object.<string, ol.xml.Parser>>} */ ( - ol.xml.makeStructureNS(namespaceURIs, parsers, opt_parsersNS)); +goog.fx.Dragger.EventType = { + // The drag action was canceled before the START event. Possible reasons: + // disabled dragger, dragging with the right mouse button or releasing the + // button before reaching the hysteresis distance. + EARLY_CANCEL: 'earlycancel', + START: 'start', + BEFOREDRAG: 'beforedrag', + DRAG: 'drag', + END: 'end' }; /** - * Creates a serializer that appends nodes written by its `nodeWriter` to its - * designated parent. The parent is the `node` of the - * {@link ol.xml.NodeStackItem} at the top of the `objectStack`. - * @param {function(this: T, Node, V, Array.<*>)} - * nodeWriter Node writer. - * @param {T=} opt_this The object to use as `this` in `nodeWriter`. - * @return {ol.xml.Serializer} Serializer. - * @template T, V + * Reference to drag target element. + * @type {Element} */ -ol.xml.makeChildAppender = function(nodeWriter, opt_this) { - return function(node, value, objectStack) { - nodeWriter.call(goog.isDef(opt_this) ? opt_this : this, - node, value, objectStack); - var parent = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(parent)); - var parentNode = parent.node; - goog.asserts.assert(ol.xml.isNode(parentNode) || - ol.xml.isDocument(parentNode)); - parentNode.appendChild(node); - }; -}; +goog.fx.Dragger.prototype.target; /** - * Creates a serializer that calls the provided `nodeWriter` from - * {@link ol.xml.serialize}. This can be used by the parent writer to have the - * 'nodeWriter' called with an array of values when the `nodeWriter` was - * designed to serialize a single item. An example would be a LineString - * geometry writer, which could be reused for writing MultiLineString - * geometries. - * @param {function(this: T, Node, V, Array.<*>)} - * nodeWriter Node writer. - * @param {T=} opt_this The object to use as `this` in `nodeWriter`. - * @return {ol.xml.Serializer} Serializer. - * @template T, V + * Reference to the handler that initiates the drag. + * @type {Element} */ -ol.xml.makeArraySerializer = function(nodeWriter, opt_this) { - var serializersNS, nodeFactory; - return function(node, value, objectStack) { - if (!goog.isDef(serializersNS)) { - serializersNS = {}; - var serializers = {}; - goog.object.set(serializers, node.localName, nodeWriter); - goog.object.set(serializersNS, node.namespaceURI, serializers); - nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName); - } - ol.xml.serialize(serializersNS, nodeFactory, value, objectStack); - }; -}; +goog.fx.Dragger.prototype.handle; /** - * Creates a node factory which can use the `opt_keys` passed to - * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names, - * or a fixed node name. The namespace of the created nodes can either be fixed, - * or the parent namespace will be used. - * @param {string=} opt_nodeName Fixed node name which will be used for all - * created nodes. If not provided, the 3rd argument to the resulting node - * factory needs to be provided and will be the nodeName. - * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for - * all created nodes. If not provided, the namespace of the parent node will - * be used. - * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory. + * Object representing the limits of the drag region. + * @type {goog.math.Rect} */ -ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) { - var fixedNodeName = opt_nodeName; - return ( - /** - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node} Node. - */ - function(value, objectStack, opt_nodeName) { - var context = objectStack[objectStack.length - 1]; - var node = context.node; - goog.asserts.assert(ol.xml.isNode(node) || ol.xml.isDocument(node)); - var nodeName = fixedNodeName; - if (!goog.isDef(nodeName)) { - nodeName = opt_nodeName; - } - var namespaceURI = opt_namespaceURI; - if (!goog.isDef(opt_namespaceURI)) { - namespaceURI = node.namespaceURI; - } - goog.asserts.assert(goog.isDef(nodeName)); - return ol.xml.createElementNS(namespaceURI, nodeName); - } - ); -}; +goog.fx.Dragger.prototype.limits; /** - * A node factory that creates a node using the parent's `namespaceURI` and the - * `nodeName` passed by {@link ol.xml.serialize} or - * {@link ol.xml.pushSerializeAndPop} to the node factory. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} + * Whether the element is rendered right-to-left. We initialize this lazily. + * @type {boolean|undefined}} + * @private */ -ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory(); +goog.fx.Dragger.prototype.rightToLeft_; /** - * Creates an array of `values` to be used with {@link ol.xml.serialize} or - * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as - * `opt_key` argument. - * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can - * be a subset of the `orderedKeys`. - * @param {Array.<string>} orderedKeys Keys in the order of the sequence. - * @return {Array.<V>} Values in the order of the sequence. The resulting array - * has the same length as the `orderedKeys` array. Values that are not - * present in `object` will be `undefined` in the resulting array. - * @template V + * Current x position of mouse or touch relative to viewport. + * @type {number} */ -ol.xml.makeSequence = function(object, orderedKeys) { - var length = orderedKeys.length; - var sequence = new Array(length); - for (var i = 0; i < length; ++i) { - sequence[i] = object[orderedKeys[i]]; - } - return sequence; -}; +goog.fx.Dragger.prototype.clientX = 0; /** - * Creates a namespaced structure, using the same values for each namespace. - * This can be used as a starting point for versioned parsers, when only a few - * values are version specific. - * @param {Array.<string>} namespaceURIs Namespace URIs. - * @param {T} structure Structure. - * @param {Object.<string, T>=} opt_structureNS Namespaced structure to add to. - * @return {Object.<string, T>} Namespaced structure. - * @template T + * Current y position of mouse or touch relative to viewport. + * @type {number} */ -ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) { - /** - * @type {Object.<string, *>} - */ - var structureNS = goog.isDef(opt_structureNS) ? opt_structureNS : {}; - var i, ii; - for (i = 0, ii = namespaceURIs.length; i < ii; ++i) { - structureNS[namespaceURIs[i]] = structure; - } - return structureNS; -}; +goog.fx.Dragger.prototype.clientY = 0; /** - * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS - * Parsers by namespace. - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @param {*=} opt_this The object to use as `this`. + * Current x position of mouse or touch relative to screen. Deprecated because + * it doesn't take into affect zoom level or pixel density. + * @type {number} + * @deprecated Consider switching to clientX instead. */ -ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) { - var n; - for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { - var parsers = parsersNS[n.namespaceURI]; - if (goog.isDef(parsers)) { - var parser = parsers[n.localName]; - if (goog.isDef(parser)) { - parser.call(opt_this, n, objectStack); - } - } - } -}; +goog.fx.Dragger.prototype.screenX = 0; /** - * @param {T} object Object. - * @param {Object.<string, Object.<string, ol.xml.Parser>>} parsersNS - * Parsers by namespace. - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @param {*=} opt_this The object to use as `this`. - * @return {T|undefined} Object. - * @template T + * Current y position of mouse or touch relative to screen. Deprecated because + * it doesn't take into affect zoom level or pixel density. + * @type {number} + * @deprecated Consider switching to clientY instead. */ -ol.xml.pushParseAndPop = function( - object, parsersNS, node, objectStack, opt_this) { - objectStack.push(object); - ol.xml.parseNode(parsersNS, node, objectStack, opt_this); - return objectStack.pop(); -}; +goog.fx.Dragger.prototype.screenY = 0; /** - * Walks through an array of `values` and calls a serializer for each value. - * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS - * Namespaced serializers. - * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory - * Node factory. The `nodeFactory` creates the node whose namespace and name - * will be used to choose a node writer from `serializersNS`. This - * separation allows us to decide what kind of node to create, depending on - * the value we want to serialize. An example for this would be different - * geometry writers based on the geometry type. - * @param {Array.<*>} values Values to serialize. An example would be an array - * of {@link ol.Feature} instances. - * @param {Array.<*>} objectStack Node stack. - * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the - * `nodeFactory`. This is used for serializing object literals where the - * node name relates to the property key. The array length of `opt_keys` has - * to match the length of `values`. For serializing a sequence, `opt_keys` - * determines the order of the sequence. - * @param {T=} opt_this The object to use as `this` for the node factory and - * serializers. - * @template T + * The x position where the first mousedown or touchstart occurred. + * @type {number} */ -ol.xml.serialize = function( - serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { - var length = (goog.isDef(opt_keys) ? opt_keys : values).length; - var value, node; - for (var i = 0; i < length; ++i) { - value = values[i]; - if (goog.isDef(value)) { - node = nodeFactory.call(opt_this, value, objectStack, - goog.isDef(opt_keys) ? opt_keys[i] : undefined); - if (goog.isDef(node)) { - serializersNS[node.namespaceURI][node.localName] - .call(opt_this, node, value, objectStack); - } - } - } -}; +goog.fx.Dragger.prototype.startX = 0; /** - * @param {O} object Object. - * @param {Object.<string, Object.<string, ol.xml.Serializer>>} serializersNS - * Namespaced serializers. - * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory - * Node factory. The `nodeFactory` creates the node whose namespace and name - * will be used to choose a node writer from `serializersNS`. This - * separation allows us to decide what kind of node to create, depending on - * the value we want to serialize. An example for this would be different - * geometry writers based on the geometry type. - * @param {Array.<*>} values Values to serialize. An example would be an array - * of {@link ol.Feature} instances. - * @param {Array.<*>} objectStack Node stack. - * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the - * `nodeFactory`. This is used for serializing object literals where the - * node name relates to the property key. The array length of `opt_keys` has - * to match the length of `values`. For serializing a sequence, `opt_keys` - * determines the order of the sequence. - * @param {T=} opt_this The object to use as `this` for the node factory and - * serializers. - * @return {O|undefined} Object. - * @template O, T + * The y position where the first mousedown or touchstart occurred. + * @type {number} */ -ol.xml.pushSerializeAndPop = function(object, - serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) { - objectStack.push(object); - ol.xml.serialize( - serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this); - return objectStack.pop(); -}; - -goog.provide('ol.format.XMLFeature'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.dom.xml'); -goog.require('ol.format.Feature'); -goog.require('ol.format.FormatType'); -goog.require('ol.proj'); -goog.require('ol.xml'); - +goog.fx.Dragger.prototype.startY = 0; /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for XML feature formats. - * - * @constructor - * @extends {ol.format.Feature} + * Current x position of drag relative to target's parent. + * @type {number} */ -ol.format.XMLFeature = function() { - goog.base(this); -}; -goog.inherits(ol.format.XMLFeature, ol.format.Feature); +goog.fx.Dragger.prototype.deltaX = 0; /** - * @inheritDoc + * Current y position of drag relative to target's parent. + * @type {number} */ -ol.format.XMLFeature.prototype.getType = function() { - return ol.format.FormatType.XML; -}; +goog.fx.Dragger.prototype.deltaY = 0; /** - * @inheritDoc + * The current page scroll value. + * @type {goog.math.Coordinate} */ -ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) { - if (ol.xml.isDocument(source)) { - return this.readFeatureFromDocument( - /** @type {Document} */ (source), opt_options); - } else if (ol.xml.isNode(source)) { - return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readFeatureFromDocument(doc, opt_options); - } else { - goog.asserts.fail(); - return null; - } -}; +goog.fx.Dragger.prototype.pageScroll; /** - * @param {Document} doc Document. - * @param {olx.format.ReadOptions=} opt_options Options. - * @return {ol.Feature} Feature. + * Whether dragging is currently enabled. + * @type {boolean} + * @private */ -ol.format.XMLFeature.prototype.readFeatureFromDocument = function( - doc, opt_options) { - var features = this.readFeaturesFromDocument(doc, opt_options); - if (features.length > 0) { - return features[0]; - } else { - return null; - } -}; +goog.fx.Dragger.prototype.enabled_ = true; /** - * @param {Node} node Node. - * @param {olx.format.ReadOptions=} opt_options Options. - * @return {ol.Feature} Feature. + * Whether object is currently being dragged. + * @type {boolean} + * @private */ -ol.format.XMLFeature.prototype.readFeatureFromNode = goog.abstractMethod; +goog.fx.Dragger.prototype.dragging_ = false; /** - * @inheritDoc + * The amount of distance, in pixels, after which a mousedown or touchstart is + * considered a drag. + * @type {number} + * @private */ -ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) { - if (ol.xml.isDocument(source)) { - return this.readFeaturesFromDocument( - /** @type {Document} */ (source), opt_options); - } else if (ol.xml.isNode(source)) { - return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readFeaturesFromDocument(doc, opt_options); - } else { - goog.asserts.fail(); - return []; - } -}; +goog.fx.Dragger.prototype.hysteresisDistanceSquared_ = 0; /** - * @param {Document} doc Document. - * @param {olx.format.ReadOptions=} opt_options Options. - * @protected - * @return {Array.<ol.Feature>} Features. + * Timestamp of when the mousedown or touchstart occurred. + * @type {number} + * @private */ -ol.format.XMLFeature.prototype.readFeaturesFromDocument = function( - doc, opt_options) { - /** @type {Array.<ol.Feature>} */ - var features = []; - var n; - for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - goog.array.extend(features, this.readFeaturesFromNode(n, opt_options)); - } - } - return features; -}; +goog.fx.Dragger.prototype.mouseDownTime_ = 0; /** - * @param {Node} node Node. - * @param {olx.format.ReadOptions=} opt_options Options. - * @protected - * @return {Array.<ol.Feature>} Features. + * Reference to a document object to use for the events. + * @type {Document} + * @private */ -ol.format.XMLFeature.prototype.readFeaturesFromNode = goog.abstractMethod; +goog.fx.Dragger.prototype.document_; /** - * @inheritDoc + * The SCROLL event target used to make drag element follow scrolling. + * @type {EventTarget} + * @private */ -ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) { - if (ol.xml.isDocument(source)) { - return this.readGeometryFromDocument( - /** @type {Document} */ (source), opt_options); - } else if (ol.xml.isNode(source)) { - return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readGeometryFromDocument(doc, opt_options); - } else { - goog.asserts.fail(); - return null; - } -}; +goog.fx.Dragger.prototype.scrollTarget_; /** - * @param {Document} doc Document. - * @param {olx.format.ReadOptions=} opt_options Options. - * @protected - * @return {ol.geom.Geometry} Geometry. + * Whether IE drag events cancelling is on. + * @type {boolean} + * @private */ -ol.format.XMLFeature.prototype.readGeometryFromDocument = goog.abstractMethod; +goog.fx.Dragger.prototype.ieDragStartCancellingOn_ = false; /** - * @param {Node} node Node. - * @param {olx.format.ReadOptions=} opt_options Options. - * @protected - * @return {ol.geom.Geometry} Geometry. + * Whether the dragger implements the changes described in http://b/6324964, + * making it truly RTL. This is a temporary flag to allow clients to transition + * to the new behavior at their convenience. At some point it will be the + * default. + * @type {boolean} + * @private */ -ol.format.XMLFeature.prototype.readGeometryFromNode = goog.abstractMethod; +goog.fx.Dragger.prototype.useRightPositioningForRtl_ = false; /** - * @inheritDoc + * Turns on/off true RTL behavior. This should be called immediately after + * construction. This is a temporary flag to allow clients to transition + * to the new component at their convenience. At some point true will be the + * default. + * @param {boolean} useRightPositioningForRtl True if "right" should be used for + * positioning, false if "left" should be used for positioning. */ -ol.format.XMLFeature.prototype.readProjection = function(source) { - if (ol.xml.isDocument(source)) { - return this.readProjectionFromDocument(/** @type {Document} */ (source)); - } else if (ol.xml.isNode(source)) { - return this.readProjectionFromNode(/** @type {Node} */ (source)); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readProjectionFromDocument(doc); - } else { - goog.asserts.fail(); - return null; - } +goog.fx.Dragger.prototype.enableRightPositioningForRtl = + function(useRightPositioningForRtl) { + this.useRightPositioningForRtl_ = useRightPositioningForRtl; }; /** - * @param {Document} doc Document. - * @protected - * @return {ol.proj.Projection} Projection. + * Returns the event handler, intended for subclass use. + * @return {!goog.events.EventHandler<T>} The event handler. + * @this T + * @template T */ -ol.format.XMLFeature.prototype.readProjectionFromDocument = goog.abstractMethod; +goog.fx.Dragger.prototype.getHandler = function() { + // TODO(user): templated "this" values currently result in "this" being + // "unknown" in the body of the function. + var self = /** @type {goog.fx.Dragger} */ (this); + return self.eventHandler_; +}; /** - * @param {Node} node Node. - * @protected - * @return {ol.proj.Projection} Projection. + * Sets (or reset) the Drag limits after a Dragger is created. + * @param {goog.math.Rect?} limits Object containing left, top, width, + * height for new Dragger limits. If target is right-to-left and + * enableRightPositioningForRtl(true) is called, then rect is interpreted as + * right, top, width, and height. */ -ol.format.XMLFeature.prototype.readProjectionFromNode = goog.abstractMethod; +goog.fx.Dragger.prototype.setLimits = function(limits) { + this.limits = limits || new goog.math.Rect(NaN, NaN, NaN, NaN); +}; /** - * @inheritDoc + * Sets the distance the user has to drag the element before a drag operation is + * started. + * @param {number} distance The number of pixels after which a mousedown and + * move is considered a drag. */ -ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) { - var node = this.writeFeatureNode(feature, opt_options); - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return goog.dom.xml.serialize(/** @type {Element} */(node)); +goog.fx.Dragger.prototype.setHysteresis = function(distance) { + this.hysteresisDistanceSquared_ = Math.pow(distance, 2); }; /** - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions=} opt_options Options. - * @protected - * @return {Node} Node. + * Gets the distance the user has to drag the element before a drag operation is + * started. + * @return {number} distance The number of pixels after which a mousedown and + * move is considered a drag. */ -ol.format.XMLFeature.prototype.writeFeatureNode = goog.abstractMethod; +goog.fx.Dragger.prototype.getHysteresis = function() { + return Math.sqrt(this.hysteresisDistanceSquared_); +}; /** - * @inheritDoc + * Sets the SCROLL event target to make drag element follow scrolling. + * + * @param {EventTarget} scrollTarget The event target that dispatches SCROLL + * events. */ -ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) { - var node = this.writeFeaturesNode(features, opt_options); - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return goog.dom.xml.serialize(/** @type {Element} */(node)); +goog.fx.Dragger.prototype.setScrollTarget = function(scrollTarget) { + this.scrollTarget_ = scrollTarget; }; /** - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {Node} Node. + * Enables cancelling of built-in IE drag events. + * @param {boolean} cancelIeDragStart Whether to enable cancelling of IE + * dragstart event. */ -ol.format.XMLFeature.prototype.writeFeaturesNode = goog.abstractMethod; +goog.fx.Dragger.prototype.setCancelIeDragStart = function(cancelIeDragStart) { + this.ieDragStartCancellingOn_ = cancelIeDragStart; +}; /** - * @inheritDoc + * @return {boolean} Whether the dragger is enabled. */ -ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) { - var node = this.writeGeometryNode(geometry, opt_options); - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return goog.dom.xml.serialize(/** @type {Element} */(node)); +goog.fx.Dragger.prototype.getEnabled = function() { + return this.enabled_; }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {Node} Node. + * Set whether dragger is enabled + * @param {boolean} enabled Whether dragger is enabled. */ -ol.format.XMLFeature.prototype.writeGeometryNode = goog.abstractMethod; +goog.fx.Dragger.prototype.setEnabled = function(enabled) { + this.enabled_ = enabled; +}; -// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part -// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect -// envelopes/extents, only geometries! -goog.provide('ol.format.GMLBase'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('ol.Feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.XMLFeature'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.LinearRing'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.proj'); -goog.require('ol.xml'); +/** @override */ +goog.fx.Dragger.prototype.disposeInternal = function() { + goog.fx.Dragger.superClass_.disposeInternal.call(this); + goog.events.unlisten(this.handle, + [goog.events.EventType.TOUCHSTART, goog.events.EventType.MOUSEDOWN], + this.startDrag, false, this); + this.cleanUpAfterDragging_(); + this.target = null; + this.handle = null; +}; /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Feature base format for reading and writing data in the GML format. - * This class cannot be instantiate, it contains only base content that - * is shared with versioned format classes ol.format.GML2 and - * ol.format.GML3. - * - * @constructor - * @param {olx.format.GMLOptions=} opt_options - * Optional configuration object. - * @extends {ol.format.XMLFeature} - * @api + * Whether the DOM element being manipulated is rendered right-to-left. + * @return {boolean} True if the DOM element is rendered right-to-left, false + * otherwise. + * @private */ -ol.format.GMLBase = function(opt_options) { - var options = /** @type {olx.format.GMLOptions} */ - (goog.isDef(opt_options) ? opt_options : {}); +goog.fx.Dragger.prototype.isRightToLeft_ = function() { + if (!goog.isDef(this.rightToLeft_)) { + this.rightToLeft_ = goog.style.isRightToLeft(this.target); + } + return this.rightToLeft_; +}; - /** - * @protected - * @type {string} - */ - this.featureType = options.featureType; - /** - * @protected - * @type {string} - */ - this.featureNS = options.featureNS; +/** + * Event handler that is used to start the drag + * @param {goog.events.BrowserEvent} e Event object. + */ +goog.fx.Dragger.prototype.startDrag = function(e) { + var isMouseDown = e.type == goog.events.EventType.MOUSEDOWN; - /** - * @protected - * @type {string} - */ - this.srsName = options.srsName; + // Dragger.startDrag() can be called by AbstractDragDrop with a mousemove + // event and IE does not report pressed mouse buttons on mousemove. Also, + // it does not make sense to check for the button if the user is already + // dragging. - /** - * @protected - * @type {string} - */ - this.schemaLocation = ''; + if (this.enabled_ && !this.dragging_ && + (!isMouseDown || e.isMouseActionButton())) { + this.maybeReinitTouchEvent_(e); + if (this.hysteresisDistanceSquared_ == 0) { + if (this.fireDragStart_(e)) { + this.dragging_ = true; + e.preventDefault(); + } else { + // If the start drag is cancelled, don't setup for a drag. + return; + } + } else { + // Need to preventDefault for hysteresis to prevent page getting selected. + e.preventDefault(); + } + this.setupDragHandlers(); - goog.base(this); + this.clientX = this.startX = e.clientX; + this.clientY = this.startY = e.clientY; + this.screenX = e.screenX; + this.screenY = e.screenY; + this.computeInitialPosition(); + this.pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll(); + + this.mouseDownTime_ = goog.now(); + } else { + this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL); + } }; -goog.inherits(ol.format.GMLBase, ol.format.XMLFeature); /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Array.<ol.Feature>} Features. - * @private + * Sets up event handlers when dragging starts. + * @protected */ -ol.format.GMLBase.prototype.readFeatures_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - var localName = ol.xml.getLocalName(node); - var features; - if (localName == 'FeatureCollection') { - features = ol.xml.pushParseAndPop(null, - this.FEATURE_COLLECTION_PARSERS, node, - objectStack, this); - } else if (localName == 'featureMembers' || localName == 'featureMember') { - var context = objectStack[0]; - goog.asserts.assert(goog.isObject(context)); - var featureType = goog.object.get(context, 'featureType'); - if (!goog.isDef(featureType) && !goog.isNull(node.firstElementChild)) { - var member = node.firstElementChild; - featureType = member.nodeName.split(':').pop(); - goog.object.set(context, 'featureType', featureType); - goog.object.set(context, 'featureNS', member.namespaceURI); - } - var parsers = {}; - var parsersNS = {}; - parsers[featureType] = (localName == 'featureMembers') ? - ol.xml.makeArrayPusher(this.readFeatureElement, this) : - ol.xml.makeReplacer(this.readFeatureElement, this); - parsersNS[goog.object.get(context, 'featureNS')] = parsers; - features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack); +goog.fx.Dragger.prototype.setupDragHandlers = function() { + var doc = this.document_; + var docEl = doc.documentElement; + // Use bubbling when we have setCapture since we got reports that IE has + // problems with the capturing events in combination with setCapture. + var useCapture = !goog.fx.Dragger.HAS_SET_CAPTURE_; + + this.eventHandler_.listen(doc, + [goog.events.EventType.TOUCHMOVE, goog.events.EventType.MOUSEMOVE], + this.handleMove_, useCapture); + this.eventHandler_.listen(doc, + [goog.events.EventType.TOUCHEND, goog.events.EventType.MOUSEUP], + this.endDrag, useCapture); + + if (goog.fx.Dragger.HAS_SET_CAPTURE_) { + docEl.setCapture(false); + this.eventHandler_.listen(docEl, + goog.events.EventType.LOSECAPTURE, + this.endDrag); + } else { + // Make sure we stop the dragging if the window loses focus. + // Don't use capture in this listener because we only want to end the drag + // if the actual window loses focus. Since blur events do not bubble we use + // a bubbling listener on the window. + this.eventHandler_.listen(goog.dom.getWindow(doc), + goog.events.EventType.BLUR, + this.endDrag); } - if (!goog.isDef(features)) { - features = []; + + if (goog.userAgent.IE && this.ieDragStartCancellingOn_) { + // Cancel IE's 'ondragstart' event. + this.eventHandler_.listen(doc, goog.events.EventType.DRAGSTART, + goog.events.Event.preventDefault); + } + + if (this.scrollTarget_) { + this.eventHandler_.listen(this.scrollTarget_, goog.events.EventType.SCROLL, + this.onScroll_, useCapture); } - return features; }; /** - * @type {Object.<string, Object.<string, Object>>} + * Fires a goog.fx.Dragger.EventType.START event. + * @param {goog.events.BrowserEvent} e Browser event that triggered the drag. + * @return {boolean} False iff preventDefault was called on the DragEvent. + * @private */ -ol.format.GMLBase.prototype.FEATURE_COLLECTION_PARSERS = Object({ - 'http://www.opengis.net/gml': { - 'featureMember': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.readFeatures_), - 'featureMembers': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readFeatures_) +goog.fx.Dragger.prototype.fireDragStart_ = function(e) { + return this.dispatchEvent(new goog.fx.DragEvent( + goog.fx.Dragger.EventType.START, this, e.clientX, e.clientY, e)); +}; + + +/** + * Unregisters the event handlers that are only active during dragging, and + * releases mouse capture. + * @private + */ +goog.fx.Dragger.prototype.cleanUpAfterDragging_ = function() { + this.eventHandler_.removeAll(); + if (goog.fx.Dragger.HAS_SET_CAPTURE_) { + this.document_.releaseCapture(); } -}); +}; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.Geometry|undefined} Geometry. + * Event handler that is used to end the drag. + * @param {goog.events.BrowserEvent} e Event object. + * @param {boolean=} opt_dragCanceled Whether the drag has been canceled. */ -ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) { - var context = objectStack[0]; - goog.asserts.assert(goog.isObject(context)); - goog.object.set(context, 'srsName', - node.firstElementChild.getAttribute('srsName')); - var geometry = ol.xml.pushParseAndPop(/** @type {ol.geom.Geometry} */(null), - this.GEOMETRY_PARSERS_, node, objectStack, this); - if (goog.isDefAndNotNull(geometry)) { - return /** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions(geometry, false, context)); +goog.fx.Dragger.prototype.endDrag = function(e, opt_dragCanceled) { + this.cleanUpAfterDragging_(); + + if (this.dragging_) { + this.maybeReinitTouchEvent_(e); + this.dragging_ = false; + + var x = this.limitX(this.deltaX); + var y = this.limitY(this.deltaY); + var dragCanceled = opt_dragCanceled || + e.type == goog.events.EventType.TOUCHCANCEL; + this.dispatchEvent(new goog.fx.DragEvent( + goog.fx.Dragger.EventType.END, this, e.clientX, e.clientY, e, x, y, + dragCanceled)); } else { - return undefined; + this.dispatchEvent(goog.fx.Dragger.EventType.EARLY_CANCEL); } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.Feature} Feature. + * Event handler that is used to end the drag by cancelling it. + * @param {goog.events.BrowserEvent} e Event object. */ -ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) { - var n; - var fid = node.getAttribute('fid') || - ol.xml.getAttributeNS(node, 'http://www.opengis.net/gml', 'id'); - var values = {}, geometryName; - for (n = node.firstElementChild; !goog.isNull(n); - n = n.nextElementSibling) { - var localName = ol.xml.getLocalName(n); - // Assume attribute elements have one child node and that the child - // is a text node. Otherwise assume it is a geometry node. - if (n.childNodes.length === 0 || - (n.childNodes.length === 1 && - n.firstChild.nodeType === 3)) { - var value = ol.xml.getAllTextContent(n, false); - if (goog.string.isEmpty(value)) { - value = undefined; - } - values[localName] = value; - } else { - // boundedBy is an extent and must not be considered as a geometry - if (localName !== 'boundedBy') { - geometryName = localName; - } - values[localName] = this.readGeometryElement(n, objectStack); - } - } - var feature = new ol.Feature(values); - if (goog.isDef(geometryName)) { - feature.setGeometryName(geometryName); - } - if (fid) { - feature.setId(fid); - } - return feature; +goog.fx.Dragger.prototype.endDragCancel = function(e) { + this.endDrag(e, true); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.Point|undefined} Point. + * Re-initializes the event with the first target touch event or, in the case + * of a stop event, the last changed touch. + * @param {goog.events.BrowserEvent} e A TOUCH... event. + * @private */ -ol.format.GMLBase.prototype.readPoint = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Point'); - var flatCoordinates = - this.readFlatCoordinatesFromNode_(node, objectStack); - if (goog.isDefAndNotNull(flatCoordinates)) { - var point = new ol.geom.Point(null); - goog.asserts.assert(flatCoordinates.length == 3); - point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); - return point; +goog.fx.Dragger.prototype.maybeReinitTouchEvent_ = function(e) { + var type = e.type; + + if (type == goog.events.EventType.TOUCHSTART || + type == goog.events.EventType.TOUCHMOVE) { + e.init(e.getBrowserEvent().targetTouches[0], e.currentTarget); + } else if (type == goog.events.EventType.TOUCHEND || + type == goog.events.EventType.TOUCHCANCEL) { + e.init(e.getBrowserEvent().changedTouches[0], e.currentTarget); } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.MultiPoint|undefined} MultiPoint. + * Event handler that is used on mouse / touch move to update the drag + * @param {goog.events.BrowserEvent} e Event object. + * @private */ -ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MultiPoint'); - var coordinates = ol.xml.pushParseAndPop( - /** @type {Array.<Array.<number>>} */ ([]), - this.MULTIPOINT_PARSERS_, node, objectStack, this); - if (goog.isDef(coordinates)) { - return new ol.geom.MultiPoint(coordinates); - } else { - return undefined; +goog.fx.Dragger.prototype.handleMove_ = function(e) { + if (this.enabled_) { + this.maybeReinitTouchEvent_(e); + // dx in right-to-left cases is relative to the right. + var sign = this.useRightPositioningForRtl_ && + this.isRightToLeft_() ? -1 : 1; + var dx = sign * (e.clientX - this.clientX); + var dy = e.clientY - this.clientY; + this.clientX = e.clientX; + this.clientY = e.clientY; + this.screenX = e.screenX; + this.screenY = e.screenY; + + if (!this.dragging_) { + var diffX = this.startX - this.clientX; + var diffY = this.startY - this.clientY; + var distance = diffX * diffX + diffY * diffY; + if (distance > this.hysteresisDistanceSquared_) { + if (this.fireDragStart_(e)) { + this.dragging_ = true; + } else { + // DragListGroup disposes of the dragger if BEFOREDRAGSTART is + // canceled. + if (!this.isDisposed()) { + this.endDrag(e); + } + return; + } + } + } + + var pos = this.calculatePosition_(dx, dy); + var x = pos.x; + var y = pos.y; + + if (this.dragging_) { + + var rv = this.dispatchEvent(new goog.fx.DragEvent( + goog.fx.Dragger.EventType.BEFOREDRAG, this, e.clientX, e.clientY, + e, x, y)); + + // Only do the defaultAction and dispatch drag event if predrag didn't + // prevent default + if (rv) { + this.doDrag(e, x, y, false); + e.preventDefault(); + } + } } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.MultiLineString|undefined} MultiLineString. + * Calculates the drag position. + * + * @param {number} dx The horizontal movement delta. + * @param {number} dy The vertical movement delta. + * @return {!goog.math.Coordinate} The newly calculated drag element position. + * @private */ -ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MultiLineString'); - var lineStrings = ol.xml.pushParseAndPop( - /** @type {Array.<ol.geom.LineString>} */ ([]), - this.MULTILINESTRING_PARSERS_, node, objectStack, this); - if (goog.isDef(lineStrings)) { - var multiLineString = new ol.geom.MultiLineString(null); - multiLineString.setLineStrings(lineStrings); - return multiLineString; - } else { - return undefined; - } -}; +goog.fx.Dragger.prototype.calculatePosition_ = function(dx, dy) { + // Update the position for any change in body scrolling + var pageScroll = goog.dom.getDomHelper(this.document_).getDocumentScroll(); + dx += pageScroll.x - this.pageScroll.x; + dy += pageScroll.y - this.pageScroll.y; + this.pageScroll = pageScroll; + this.deltaX += dx; + this.deltaY += dy; -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. - */ -ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MultiPolygon'); - var polygons = ol.xml.pushParseAndPop( - /** @type {Array.<ol.geom.Polygon>} */ ([]), - this.MULTIPOLYGON_PARSERS_, node, objectStack, this); - if (goog.isDef(polygons)) { - var multiPolygon = new ol.geom.MultiPolygon(null); - multiPolygon.setPolygons(polygons); - return multiPolygon; - } else { - return undefined; - } + var x = this.limitX(this.deltaX); + var y = this.limitY(this.deltaY); + return new goog.math.Coordinate(x, y); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * Event handler for scroll target scrolling. + * @param {goog.events.BrowserEvent} e The event. * @private */ -ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'pointMember' || - node.localName == 'pointMembers'); - ol.xml.parseNode(this.POINTMEMBER_PARSERS_, - node, objectStack, this); +goog.fx.Dragger.prototype.onScroll_ = function(e) { + var pos = this.calculatePosition_(0, 0); + e.clientX = this.clientX; + e.clientY = this.clientY; + this.doDrag(e, pos.x, pos.y, true); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * @param {goog.events.BrowserEvent} e The closure object + * representing the browser event that caused a drag event. + * @param {number} x The new horizontal position for the drag element. + * @param {number} y The new vertical position for the drag element. + * @param {boolean} dragFromScroll Whether dragging was caused by scrolling + * the associated scroll target. + * @protected */ -ol.format.GMLBase.prototype.lineStringMemberParser_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'lineStringMember' || - node.localName == 'lineStringMembers'); - ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_, - node, objectStack, this); +goog.fx.Dragger.prototype.doDrag = function(e, x, y, dragFromScroll) { + this.defaultAction(x, y); + this.dispatchEvent(new goog.fx.DragEvent( + goog.fx.Dragger.EventType.DRAG, this, e.clientX, e.clientY, e, x, y)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Returns the 'real' x after limits are applied (allows for some + * limits to be undefined). + * @param {number} x X-coordinate to limit. + * @return {number} The 'real' X-coordinate after limits are applied. */ -ol.format.GMLBase.prototype.polygonMemberParser_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'polygonMember' || - node.localName == 'polygonMembers'); - ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node, - objectStack, this); +goog.fx.Dragger.prototype.limitX = function(x) { + var rect = this.limits; + var left = !isNaN(rect.left) ? rect.left : null; + var width = !isNaN(rect.width) ? rect.width : 0; + var maxX = left != null ? left + width : Infinity; + var minX = left != null ? left : -Infinity; + return Math.min(maxX, Math.max(minX, x)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.LineString|undefined} LineString. + * Returns the 'real' y after limits are applied (allows for some + * limits to be undefined). + * @param {number} y Y-coordinate to limit. + * @return {number} The 'real' Y-coordinate after limits are applied. */ -ol.format.GMLBase.prototype.readLineString = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LineString'); - var flatCoordinates = - this.readFlatCoordinatesFromNode_(node, objectStack); - if (goog.isDefAndNotNull(flatCoordinates)) { - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); - return lineString; - } else { - return undefined; - } +goog.fx.Dragger.prototype.limitY = function(y) { + var rect = this.limits; + var top = !isNaN(rect.top) ? rect.top : null; + var height = !isNaN(rect.height) ? rect.height : 0; + var maxY = top != null ? top + height : Infinity; + var minY = top != null ? top : -Infinity; + return Math.min(maxY, Math.max(minY, y)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>|undefined} LinearRing flat coordinates. + * Overridable function for computing the initial position of the target + * before dragging begins. + * @protected */ -ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LinearRing'); - var ring = ol.xml.pushParseAndPop(/** @type {Array.<number>} */(null), - this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, - objectStack, this); - if (goog.isDefAndNotNull(ring)) { - return ring; - } else { - return undefined; - } +goog.fx.Dragger.prototype.computeInitialPosition = function() { + this.deltaX = this.useRightPositioningForRtl_ ? + goog.style.bidi.getOffsetStart(this.target) : this.target.offsetLeft; + this.deltaY = this.target.offsetTop; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.LinearRing|undefined} LinearRing. + * Overridable function for handling the default action of the drag behaviour. + * Normally this is simply moving the element to x,y though in some cases it + * might be used to resize the layer. This is basically a shortcut to + * implementing a default ondrag event handler. + * @param {number} x X-coordinate for target element. In right-to-left, x this + * is the number of pixels the target should be moved to from the right. + * @param {number} y Y-coordinate for target element. */ -ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LinearRing'); - var flatCoordinates = - this.readFlatCoordinatesFromNode_(node, objectStack); - if (goog.isDef(flatCoordinates)) { - var ring = new ol.geom.LinearRing(null); - ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); - return ring; +goog.fx.Dragger.prototype.defaultAction = function(x, y) { + if (this.useRightPositioningForRtl_ && this.isRightToLeft_()) { + this.target.style.right = x + 'px'; } else { - return undefined; + this.target.style.left = x + 'px'; } + this.target.style.top = y + 'px'; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.geom.Polygon|undefined} Polygon. + * @return {boolean} Whether the dragger is currently in the midst of a drag. */ -ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Polygon'); - var flatLinearRings = ol.xml.pushParseAndPop( - /** @type {Array.<Array.<number>>} */ ([null]), - this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); - if (goog.isDef(flatLinearRings) && - !goog.isNull(flatLinearRings[0])) { - var polygon = new ol.geom.Polygon(null); - var flatCoordinates = flatLinearRings[0]; - var ends = [flatCoordinates.length]; - var i, ii; - for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { - goog.array.extend(flatCoordinates, flatLinearRings[i]); - ends.push(flatCoordinates.length); - } - polygon.setFlatCoordinates( - ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); - return polygon; - } else { - return undefined; - } +goog.fx.Dragger.prototype.isDragging = function() { + return this.dragging_; }; + /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>} Flat coordinates. + * Object representing a drag event + * @param {string} type Event type. + * @param {goog.fx.Dragger} dragobj Drag object initiating event. + * @param {number} clientX X-coordinate relative to the viewport. + * @param {number} clientY Y-coordinate relative to the viewport. + * @param {goog.events.BrowserEvent} browserEvent The closure object + * representing the browser event that caused this drag event. + * @param {number=} opt_actX Optional actual x for drag if it has been limited. + * @param {number=} opt_actY Optional actual y for drag if it has been limited. + * @param {boolean=} opt_dragCanceled Whether the drag has been canceled. + * @constructor + * @extends {goog.events.Event} */ -ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop( - null, - this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, - objectStack, this)); +goog.fx.DragEvent = function(type, dragobj, clientX, clientY, browserEvent, + opt_actX, opt_actY, opt_dragCanceled) { + goog.events.Event.call(this, type); + + /** + * X-coordinate relative to the viewport + * @type {number} + */ + this.clientX = clientX; + + /** + * Y-coordinate relative to the viewport + * @type {number} + */ + this.clientY = clientY; + + /** + * The closure object representing the browser event that caused this drag + * event. + * @type {goog.events.BrowserEvent} + */ + this.browserEvent = browserEvent; + + /** + * The real x-position of the drag if it has been limited + * @type {number} + */ + this.left = goog.isDef(opt_actX) ? opt_actX : dragobj.deltaX; + + /** + * The real y-position of the drag if it has been limited + * @type {number} + */ + this.top = goog.isDef(opt_actY) ? opt_actY : dragobj.deltaY; + + /** + * Reference to the drag object for this event + * @type {goog.fx.Dragger} + */ + this.dragger = dragobj; + + /** + * Whether drag was canceled with this event. Used to differentiate between + * a legitimate drag END that can result in an action and a drag END which is + * a result of a drag cancelation. For now it can happen 1) with drag END + * event on FireFox when user drags the mouse out of the window, 2) with + * drag END event on IE7 which is generated on MOUSEMOVE event when user + * moves the mouse into the document after the mouse button has been + * released, 3) when TOUCHCANCEL is raised instead of TOUCHEND (on touch + * events). + * @type {boolean} + */ + this.dragCanceled = !!opt_dragCanceled; }; +goog.inherits(goog.fx.DragEvent, goog.events.Event); +// FIXME should possibly show tooltip when dragging? -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'pointMember': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.pointMemberParser_), - 'pointMembers': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.pointMemberParser_) - } -}); +goog.provide('ol.control.ZoomSlider'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.fx.DragEvent'); +goog.require('goog.fx.Dragger'); +goog.require('goog.fx.Dragger.EventType'); +goog.require('goog.math'); +goog.require('goog.math.Rect'); +goog.require('goog.style'); +goog.require('ol.Size'); +goog.require('ol.ViewHint'); +goog.require('ol.animation'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'lineStringMember': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.lineStringMemberParser_), - 'lineStringMembers': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.lineStringMemberParser_) - } -}); /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @classdesc + * A slider type of control for zooming. + * + * Example: + * + * map.addControl(new ol.control.ZoomSlider()); + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options. + * @api stable */ -ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'polygonMember': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.polygonMemberParser_), - 'polygonMembers': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.polygonMemberParser_) - } -}); +ol.control.ZoomSlider = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'Point': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_) - } -}); + /** + * Will hold the current resolution of the view. + * + * @type {number|undefined} + * @private + */ + this.currentResolution_ = undefined; + /** + * The direction of the slider. Will be determined from actual display of the + * container and defaults to ol.control.ZoomSlider.direction.VERTICAL. + * + * @type {ol.control.ZoomSlider.direction} + * @private + */ + this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'LineString': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.readLineString) - } -}); + /** + * The calculated thumb size (border box plus margins). Set when initSlider_ + * is called. + * @type {ol.Size} + * @private + */ + this.thumbSize_ = null; + /** + * Whether the slider is initialized. + * @type {boolean} + * @private + */ + this.sliderInitialized_ = false; -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'Polygon': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.readPolygon) - } -}); + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 200; + + var className = goog.isDef(options.className) ? + options.className : 'ol-zoomslider'; + var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV, + [className + '-thumb', ol.css.CLASS_UNSELECTABLE]); + var containerElement = goog.dom.createDom(goog.dom.TagName.DIV, + [className, ol.css.CLASS_UNSELECTABLE, ol.css.CLASS_CONTROL], + thumbElement); + + /** + * @type {goog.fx.Dragger} + * @private + */ + this.dragger_ = new goog.fx.Dragger(thumbElement); + this.registerDisposable(this.dragger_); + + goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.START, + this.handleDraggerStart_, false, this); + goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.DRAG, + this.handleDraggerDrag_, false, this); + goog.events.listen(this.dragger_, goog.fx.Dragger.EventType.END, + this.handleDraggerEnd_, false, this); + + goog.events.listen(containerElement, goog.events.EventType.CLICK, + this.handleContainerClick_, false, this); + goog.events.listen(thumbElement, goog.events.EventType.CLICK, + goog.events.Event.stopPropagation); + + var render = goog.isDef(options.render) ? + options.render : ol.control.ZoomSlider.render; + + goog.base(this, { + element: containerElement, + render: render + }); +}; +goog.inherits(ol.control.ZoomSlider, ol.control.Control); /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @protected + * The enum for available directions. + * + * @enum {number} */ -ol.format.GMLBase.prototype.RING_PARSERS = Object({ - 'http://www.opengis.net/gml' : { - 'LinearRing': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readFlatLinearRing_) - } -}); +ol.control.ZoomSlider.direction = { + VERTICAL: 0, + HORIZONTAL: 1 +}; /** * @inheritDoc */ -ol.format.GMLBase.prototype.readGeometryFromNode = - function(node, opt_options) { - var geometry = this.readGeometryElement(node, - [this.getReadOptions(node, goog.isDef(opt_options) ? opt_options : {})]); - return goog.isDef(geometry) ? geometry : null; +ol.control.ZoomSlider.prototype.setMap = function(map) { + goog.base(this, 'setMap', map); + if (!goog.isNull(map)) { + map.render(); + } }; /** - * Read all features from a GML FeatureCollection. + * Initializes the slider element. This will determine and set this controls + * direction_ and also constrain the dragging of the thumb to always be within + * the bounds of the container. * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Options. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @private */ -ol.format.GMLBase.prototype.readFeatures; +ol.control.ZoomSlider.prototype.initSlider_ = function() { + var container = this.element; + var containerSize = goog.style.getSize(container); + var thumb = goog.dom.getFirstElementChild(container); + var thumbMargins = goog.style.getMarginBox(thumb); + var thumbBorderBoxSize = goog.style.getBorderBoxSize(thumb); + var thumbWidth = thumbBorderBoxSize.width + + thumbMargins.right + thumbMargins.left; + var thumbHeight = thumbBorderBoxSize.height + + thumbMargins.top + thumbMargins.bottom; + this.thumbSize_ = [thumbWidth, thumbHeight]; -/** - * @inheritDoc - */ -ol.format.GMLBase.prototype.readFeaturesFromNode = - function(node, opt_options) { - var options = { - featureType: this.featureType, - featureNS: this.featureNS - }; - if (goog.isDef(opt_options)) { - goog.object.extend(options, this.getReadOptions(node, opt_options)); + var width = containerSize.width - thumbWidth; + var height = containerSize.height - thumbHeight; + + var limits; + if (containerSize.width > containerSize.height) { + this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL; + limits = new goog.math.Rect(0, 0, width, 0); + } else { + this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; + limits = new goog.math.Rect(0, 0, 0, height); } - return this.readFeatures_(node, [options]); + this.dragger_.setLimits(limits); + this.sliderInitialized_ = true; }; /** - * @inheritDoc + * Update the zoomslider element. + * @param {ol.MapEvent} mapEvent Map event. + * @this {ol.control.ZoomSlider} + * @api */ -ol.format.GMLBase.prototype.readProjectionFromNode = function(node) { - return ol.proj.get(goog.isDef(this.srsName_) ? this.srsName_ : - node.firstElementChild.getAttribute('srsName')); +ol.control.ZoomSlider.render = function(mapEvent) { + if (goog.isNull(mapEvent.frameState)) { + return; + } + goog.asserts.assert(goog.isDefAndNotNull(mapEvent.frameState.viewState), + 'viewState should be defined'); + if (!this.sliderInitialized_) { + this.initSlider_(); + } + var res = mapEvent.frameState.viewState.resolution; + if (res !== this.currentResolution_) { + this.currentResolution_ = res; + this.setThumbPosition_(res); + } }; -goog.provide('ol.format.XSD'); - -goog.require('goog.asserts'); -goog.require('goog.string'); -goog.require('ol.xml'); - /** - * @const - * @type {string} + * @param {goog.events.BrowserEvent} browserEvent The browser event to handle. + * @private */ -ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema'; +ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) { + var map = this.getMap(); + var view = map.getView(); + var currentResolution = view.getResolution(); + goog.asserts.assert(goog.isDef(currentResolution), + 'currentResolution should be defined'); + map.beforeRender(ol.animation.zoom({ + resolution: currentResolution, + duration: this.duration_, + easing: ol.easing.easeOut + })); + var relativePosition = this.getRelativePosition_( + browserEvent.offsetX - this.thumbSize_[0] / 2, + browserEvent.offsetY - this.thumbSize_[1] / 2); + var resolution = this.getResolutionForPosition_(relativePosition); + view.setResolution(view.constrainResolution(resolution)); +}; /** - * @param {Node} node Node. - * @return {boolean|undefined} Boolean. + * Handle dragger start events. + * @param {goog.fx.DragEvent} event The drag event. + * @private */ -ol.format.XSD.readBoolean = function(node) { - var s = ol.xml.getAllTextContent(node, false); - return ol.format.XSD.readBooleanString(s); +ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) { + this.getMap().getView().setHint(ol.ViewHint.INTERACTING, 1); }; /** - * @param {string} string String. - * @return {boolean|undefined} Boolean. + * Handle dragger drag events. + * + * @param {goog.fx.DragEvent} event The drag event. + * @private */ -ol.format.XSD.readBooleanString = function(string) { - var m = /^\s*(true|1)|(false|0)\s*$/.exec(string); - if (m) { - return goog.isDef(m[1]) || false; - } else { - return undefined; - } +ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) { + var relativePosition = this.getRelativePosition_(event.left, event.top); + this.currentResolution_ = this.getResolutionForPosition_(relativePosition); + this.getMap().getView().setResolution(this.currentResolution_); }; /** - * @param {Node} node Node. - * @return {number|undefined} DateTime in seconds. + * Handle dragger end events. + * @param {goog.fx.DragEvent} event The drag event. + * @private */ -ol.format.XSD.readDateTime = function(node) { - var s = ol.xml.getAllTextContent(node, false); - var re = - /^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/; - var m = re.exec(s); - if (m) { - var year = parseInt(m[1], 10); - var month = parseInt(m[2], 10) - 1; - var day = parseInt(m[3], 10); - var hour = parseInt(m[4], 10); - var minute = parseInt(m[5], 10); - var second = parseInt(m[6], 10); - var dateTime = Date.UTC(year, month, day, hour, minute, second) / 1000; - if (m[7] != 'Z') { - var sign = m[8] == '-' ? -1 : 1; - dateTime += sign * 60 * parseInt(m[9], 10); - if (goog.isDef(m[10])) { - dateTime += sign * 60 * 60 * parseInt(m[10], 10); - } - } - return dateTime; - } else { - return undefined; - } +ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) { + var map = this.getMap(); + var view = map.getView(); + view.setHint(ol.ViewHint.INTERACTING, -1); + goog.asserts.assert(goog.isDef(this.currentResolution_), + 'this.currentResolution_ should be defined'); + map.beforeRender(ol.animation.zoom({ + resolution: this.currentResolution_, + duration: this.duration_, + easing: ol.easing.easeOut + })); + var resolution = view.constrainResolution(this.currentResolution_); + view.setResolution(resolution); }; /** - * @param {Node} node Node. - * @return {number|undefined} Decimal. + * Positions the thumb inside its container according to the given resolution. + * + * @param {number} res The res. + * @private */ -ol.format.XSD.readDecimal = function(node) { - var s = ol.xml.getAllTextContent(node, false); - return ol.format.XSD.readDecimalString(s); +ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) { + var position = this.getPositionForResolution_(res); + var dragger = this.dragger_; + var thumb = goog.dom.getFirstElementChild(this.element); + + if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) { + var left = dragger.limits.left + dragger.limits.width * position; + goog.style.setPosition(thumb, left); + } else { + var top = dragger.limits.top + dragger.limits.height * position; + goog.style.setPosition(thumb, dragger.limits.left, top); + } }; /** - * @param {string} string String. - * @return {number|undefined} Decimal. + * Calculates the relative position of the thumb given x and y offsets. The + * relative position scales from 0 to 1. The x and y offsets are assumed to be + * in pixel units within the dragger limits. + * + * @param {number} x Pixel position relative to the left of the slider. + * @param {number} y Pixel position relative to the top of the slider. + * @return {number} The relative position of the thumb. + * @private */ -ol.format.XSD.readDecimalString = function(string) { - // FIXME check spec - var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string); - if (m) { - return parseFloat(m[1]); +ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) { + var draggerLimits = this.dragger_.limits; + var amount; + if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) { + amount = (x - draggerLimits.left) / draggerLimits.width; } else { - return undefined; + amount = (y - draggerLimits.top) / draggerLimits.height; } + return goog.math.clamp(amount, 0, 1); }; /** - * @param {Node} node Node. - * @return {number|undefined} Non negative integer. + * Calculates the corresponding resolution of the thumb given its relative + * position (where 0 is the minimum and 1 is the maximum). + * + * @param {number} position The relative position of the thumb. + * @return {number} The corresponding resolution. + * @private */ -ol.format.XSD.readNonNegativeInteger = function(node) { - var s = ol.xml.getAllTextContent(node, false); - return ol.format.XSD.readNonNegativeIntegerString(s); +ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) { + var fn = this.getMap().getView().getResolutionForValueFunction(); + return fn(1 - position); }; /** - * @param {string} string String. - * @return {number|undefined} Non negative integer. + * Determines the relative position of the slider for the given resolution. A + * relative position of 0 corresponds to the minimum view resolution. A + * relative position of 1 corresponds to the maximum view resolution. + * + * @param {number} res The resolution. + * @return {number} The relative position value (between 0 and 1). + * @private */ -ol.format.XSD.readNonNegativeIntegerString = function(string) { - var m = /^\s*(\d+)\s*$/.exec(string); - if (m) { - return parseInt(m[1], 10); - } else { - return undefined; - } +ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) { + var fn = this.getMap().getView().getValueForResolutionFunction(); + return 1 - fn(res); }; +goog.provide('ol.control.ZoomToExtent'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); -/** - * @param {Node} node Node. - * @return {string|undefined} String. - */ -ol.format.XSD.readString = function(node) { - var s = ol.xml.getAllTextContent(node, false); - return goog.string.trim(s); -}; /** - * @param {Node} node Node to append a TextNode with the boolean to. - * @param {boolean} bool Boolean. + * @classdesc + * A button control which, when pressed, changes the map view to a specific + * extent. To style this control use the css selector `.ol-zoom-extent`. + * + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.ZoomToExtentOptions=} opt_options Options. + * @api stable */ -ol.format.XSD.writeBooleanTextNode = function(node, bool) { - ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0'); -}; +ol.control.ZoomToExtent = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; + + /** + * @type {ol.Extent} + * @private + */ + this.extent_ = goog.isDef(options.extent) ? options.extent : null; + var className = goog.isDef(options.className) ? options.className : + 'ol-zoom-extent'; -/** - * @param {Node} node Node to append a TextNode with the dateTime to. - * @param {number} dateTime DateTime in seconds. - */ -ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) { - var date = new Date(dateTime * 1000); - var string = date.getUTCFullYear() + '-' + - goog.string.padNumber(date.getUTCMonth() + 1, 2) + '-' + - goog.string.padNumber(date.getUTCDate(), 2) + 'T' + - goog.string.padNumber(date.getUTCHours(), 2) + ':' + - goog.string.padNumber(date.getUTCMinutes(), 2) + ':' + - goog.string.padNumber(date.getUTCSeconds(), 2) + 'Z'; - node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); + var label = goog.isDef(options.label) ? options.label : 'E'; + var tipLabel = goog.isDef(options.tipLabel) ? + options.tipLabel : 'Fit to extent'; + var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'type': 'button', + 'title': tipLabel + }, label); + + goog.events.listen(button, goog.events.EventType.CLICK, + this.handleClick_, false, this); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); + + goog.base(this, { + element: element, + target: options.target + }); }; +goog.inherits(ol.control.ZoomToExtent, ol.control.Control); /** - * @param {Node} node Node to append a TextNode with the decimal to. - * @param {number} decimal Decimal. + * @param {goog.events.BrowserEvent} event The event to handle + * @private */ -ol.format.XSD.writeDecimalTextNode = function(node, decimal) { - var string = decimal.toPrecision(); - node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +ol.control.ZoomToExtent.prototype.handleClick_ = function(event) { + event.preventDefault(); + this.handleZoomToExtent_(); }; /** - * @param {Node} node Node to append a TextNode with the decimal to. - * @param {number} nonNegativeInteger Non negative integer. + * @private */ -ol.format.XSD.writeNonNegativeIntegerTextNode = - function(node, nonNegativeInteger) { - goog.asserts.assert(nonNegativeInteger >= 0); - goog.asserts.assert(nonNegativeInteger == (nonNegativeInteger | 0)); - var string = nonNegativeInteger.toString(); - node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() { + var map = this.getMap(); + var view = map.getView(); + var extent = goog.isNull(this.extent_) ? + view.getProjection().getExtent() : this.extent_; + var size = map.getSize(); + goog.asserts.assert(goog.isDef(size), 'size should be defined'); + view.fit(extent, size); }; +goog.provide('ol.DeviceOrientation'); +goog.provide('ol.DeviceOrientationProperty'); + +goog.require('goog.events'); +goog.require('goog.math'); +goog.require('ol.Object'); +goog.require('ol.has'); + /** - * @param {Node} node Node to append a TextNode with the string to. - * @param {string} string String. + * @enum {string} */ -ol.format.XSD.writeStringTextNode = function(node, string) { - node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +ol.DeviceOrientationProperty = { + ALPHA: 'alpha', + BETA: 'beta', + GAMMA: 'gamma', + HEADING: 'heading', + TRACKING: 'tracking' }; -goog.provide('ol.format.GML'); -goog.provide('ol.format.GML3'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('ol.Feature'); -goog.require('ol.extent'); -goog.require('ol.format.Feature'); -goog.require('ol.format.GMLBase'); -goog.require('ol.format.XSD'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.LinearRing'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.proj'); -goog.require('ol.xml'); - /** * @classdesc - * Feature format for reading and writing data in the GML format - * version 3.1.1. - * Currently only supports GML 3.1.1 Simple Features profile. + * The ol.DeviceOrientation class provides access to information from + * DeviceOrientation events. See the [HTML 5 DeviceOrientation Specification]( + * http://www.w3.org/TR/orientation-event/) for more details. + * + * Many new computers, and especially mobile phones + * and tablets, provide hardware support for device orientation. Web + * developers targeting mobile devices will be especially interested in this + * class. + * + * Device orientation data are relative to a common starting point. For mobile + * devices, the starting point is to lay your phone face up on a table with the + * top of the phone pointing north. This represents the zero state. All + * angles are then relative to this state. For computers, it is the same except + * the screen is open at 90 degrees. + * + * Device orientation is reported as three angles - `alpha`, `beta`, and + * `gamma` - relative to the starting position along the three planar axes X, Y + * and Z. The X axis runs from the left edge to the right edge through the + * middle of the device. Similarly, the Y axis runs from the bottom to the top + * of the device through the middle. The Z axis runs from the back to the front + * through the middle. In the starting position, the X axis points to the + * right, the Y axis points away from you and the Z axis points straight up + * from the device lying flat. + * + * The three angles representing the device orientation are relative to the + * three axes. `alpha` indicates how much the device has been rotated around the + * Z axis, which is commonly interpreted as the compass heading (see note + * below). `beta` indicates how much the device has been rotated around the X + * axis, or how much it is tilted from front to back. `gamma` indicates how + * much the device has been rotated around the Y axis, or how much it is tilted + * from left to right. + * + * For most browsers, the `alpha` value returns the compass heading so if the + * device points north, it will be 0. With Safari on iOS, the 0 value of + * `alpha` is calculated from when device orientation was first requested. + * ol.DeviceOrientation provides the `heading` property which normalizes this + * behavior across all browsers for you. + * + * It is important to note that the HTML 5 DeviceOrientation specification + * indicates that `alpha`, `beta` and `gamma` are in degrees while the + * equivalent properties in ol.DeviceOrientation are in radians for consistency + * with all other uses of angles throughout OpenLayers. + * + * @see http://www.w3.org/TR/orientation-event/ + * + * To get notified of device orientation changes, register a listener for the + * generic `change` event on your `ol.DeviceOrientation` instance. * * @constructor - * @param {olx.format.GMLOptions=} opt_options - * Optional configuration object. - * @extends {ol.format.GMLBase} + * @extends {ol.Object} + * @param {olx.DeviceOrientationOptions=} opt_options Options. * @api */ -ol.format.GML3 = function(opt_options) { - var options = /** @type {olx.format.GMLOptions} */ - (goog.isDef(opt_options) ? opt_options : {}); - - goog.base(this, options); +ol.DeviceOrientation = function(opt_options) { - /** - * @private - * @type {boolean} - */ - this.surface_ = goog.isDef(options.surface) ? - options.surface : false; + goog.base(this); - /** - * @private - * @type {boolean} - */ - this.curve_ = goog.isDef(options.curve) ? - options.curve : false; + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {boolean} + * @type {goog.events.Key} */ - this.multiCurve_ = goog.isDef(options.multiCurve) ? - options.multiCurve : true; + this.listenerKey_ = null; - /** - * @private - * @type {boolean} - */ - this.multiSurface_ = goog.isDef(options.multiSurface) ? - options.multiSurface : true; + goog.events.listen(this, + ol.Object.getChangeEventType(ol.DeviceOrientationProperty.TRACKING), + this.handleTrackingChanged_, false, this); - /** - * @inheritDoc - */ - this.schemaLocation = goog.isDef(options.schemaLocation) ? - options.schemaLocation : ol.format.GML3.schemaLocation_; + this.setTracking(goog.isDef(options.tracking) ? options.tracking : false); }; -goog.inherits(ol.format.GML3, ol.format.GMLBase); +goog.inherits(ol.DeviceOrientation, ol.Object); /** - * @const - * @type {string} - * @private + * @inheritDoc */ -ol.format.GML3.schemaLocation_ = 'http://www.opengis.net/gml ' + - 'http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' + - '1.0.0/gmlsf.xsd'; +ol.DeviceOrientation.prototype.disposeInternal = function() { + this.setTracking(false); + goog.base(this, 'disposeInternal'); +}; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. * @private - * @return {ol.geom.MultiLineString|undefined} MultiLineString. + * @param {goog.events.BrowserEvent} browserEvent Event. */ -ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MultiCurve'); - var lineStrings = ol.xml.pushParseAndPop( - /** @type {Array.<ol.geom.LineString>} */ ([]), - this.MULTICURVE_PARSERS_, node, objectStack, this); - if (goog.isDef(lineStrings)) { - var multiLineString = new ol.geom.MultiLineString(null); - multiLineString.setLineStrings(lineStrings); - return multiLineString; - } else { - return undefined; +ol.DeviceOrientation.prototype.orientationChange_ = function(browserEvent) { + var event = /** @type {DeviceOrientationEvent} */ + (browserEvent.getBrowserEvent()); + if (goog.isDefAndNotNull(event.alpha)) { + var alpha = goog.math.toRadians(event.alpha); + this.set(ol.DeviceOrientationProperty.ALPHA, alpha); + // event.absolute is undefined in iOS. + if (goog.isBoolean(event.absolute) && event.absolute) { + this.set(ol.DeviceOrientationProperty.HEADING, alpha); + } else if (goog.isDefAndNotNull(event.webkitCompassHeading) && + goog.isDefAndNotNull(event.webkitCompassAccuracy) && + event.webkitCompassAccuracy != -1) { + var heading = goog.math.toRadians(event.webkitCompassHeading); + this.set(ol.DeviceOrientationProperty.HEADING, heading); + } + } + if (goog.isDefAndNotNull(event.beta)) { + this.set(ol.DeviceOrientationProperty.BETA, + goog.math.toRadians(event.beta)); + } + if (goog.isDefAndNotNull(event.gamma)) { + this.set(ol.DeviceOrientationProperty.GAMMA, + goog.math.toRadians(event.gamma)); } + this.changed(); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. + * Rotation around the device z-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * standard Z axis. + * @observable + * @api */ -ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MultiSurface'); - var polygons = ol.xml.pushParseAndPop( - /** @type {Array.<ol.geom.Polygon>} */ ([]), - this.MULTISURFACE_PARSERS_, node, objectStack, this); - if (goog.isDef(polygons)) { - var multiPolygon = new ol.geom.MultiPolygon(null); - multiPolygon.setPolygons(polygons); - return multiPolygon; - } else { - return undefined; - } +ol.DeviceOrientation.prototype.getAlpha = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientationProperty.ALPHA)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Rotation around the device x-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * planar X axis. + * @observable + * @api */ -ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'curveMember' || - node.localName == 'curveMembers'); - ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this); +ol.DeviceOrientation.prototype.getBeta = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientationProperty.BETA)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Rotation around the device y-axis (in radians). + * @return {number|undefined} The euler angle in radians of the device from the + * planar Y axis. + * @observable + * @api */ -ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'surfaceMember' || - node.localName == 'surfaceMembers'); - ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_, - node, objectStack, this); +ol.DeviceOrientation.prototype.getGamma = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientationProperty.GAMMA)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<(Array.<number>)>|undefined} flat coordinates. + * The heading of the device relative to north (in radians). + * @return {number|undefined} The heading of the device relative to north, in + * radians, normalizing for different browser behavior. + * @observable + * @api */ -ol.format.GML3.prototype.readPatch_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'patches'); - return ol.xml.pushParseAndPop( - /** @type {Array.<Array.<number>>} */ ([null]), - this.PATCHES_PARSERS_, node, objectStack, this); +ol.DeviceOrientation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.DeviceOrientationProperty.HEADING)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>|undefined} flat coordinates. + * Determine if orientation is being tracked. + * @return {boolean} Changes in device orientation are being tracked. + * @observable + * @api */ -ol.format.GML3.prototype.readSegment_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'segments'); - return ol.xml.pushParseAndPop( - /** @type {Array.<number>} */ ([null]), - this.SEGMENTS_PARSERS_, node, objectStack, this); +ol.DeviceOrientation.prototype.getTracking = function() { + return /** @type {boolean} */ ( + this.get(ol.DeviceOrientationProperty.TRACKING)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. * @private - * @return {Array.<(Array.<number>)>|undefined} flat coordinates. */ -ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'PolygonPatch'); - return ol.xml.pushParseAndPop( - /** @type {Array.<Array.<number>>} */ ([null]), - this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); +ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() { + if (ol.has.DEVICE_ORIENTATION) { + var tracking = this.getTracking(); + if (tracking && goog.isNull(this.listenerKey_)) { + this.listenerKey_ = goog.events.listen(goog.global, 'deviceorientation', + this.orientationChange_, false, this); + } else if (!tracking && !goog.isNull(this.listenerKey_)) { + goog.events.unlistenByKey(this.listenerKey_); + this.listenerKey_ = null; + } + } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>|undefined} flat coordinates. + * Enable or disable tracking of device orientation events. + * @param {boolean} tracking The status of tracking changes to alpha, beta and + * gamma. If true, changes are tracked and reported immediately. + * @observable + * @api */ -ol.format.GML3.prototype.readLineStringSegment_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LineStringSegment'); - return ol.xml.pushParseAndPop( - /** @type {Array.<number>} */ ([null]), - this.GEOMETRY_FLAT_COORDINATES_PARSERS_, - node, objectStack, this); +ol.DeviceOrientation.prototype.setTracking = function(tracking) { + this.set(ol.DeviceOrientationProperty.TRACKING, tracking); }; +goog.provide('ol.Ellipsoid'); + +goog.require('goog.math'); +goog.require('ol.Coordinate'); + + /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * @constructor + * @param {number} a Major radius. + * @param {number} flattening Flattening. */ -ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'interior'); - var flatLinearRing = ol.xml.pushParseAndPop( - /** @type {Array.<number>|undefined} */ (undefined), - this.RING_PARSERS, node, objectStack, this); - if (goog.isDef(flatLinearRing)) { - var flatLinearRings = /** @type {Array.<Array.<number>>} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(flatLinearRings)); - goog.asserts.assert(flatLinearRings.length > 0); - flatLinearRings.push(flatLinearRing); - } +ol.Ellipsoid = function(a, flattening) { + + /** + * @const + * @type {number} + */ + this.a = a; + + /** + * @const + * @type {number} + */ + this.flattening = flattening; + + /** + * @const + * @type {number} + */ + this.b = this.a * (1 - this.flattening); + + /** + * @const + * @type {number} + */ + this.eSquared = 2 * flattening - flattening * flattening; + + /** + * @const + * @type {number} + */ + this.e = Math.sqrt(this.eSquared); + }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {{distance: number, initialBearing: number, finalBearing: number}} + * Vincenty. */ -ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'exterior'); - var flatLinearRing = ol.xml.pushParseAndPop( - /** @type {Array.<number>|undefined} */ (undefined), - this.RING_PARSERS, node, objectStack, this); - if (goog.isDef(flatLinearRing)) { - var flatLinearRings = /** @type {Array.<Array.<number>>} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(flatLinearRings)); - goog.asserts.assert(flatLinearRings.length > 0); - flatLinearRings[0] = flatLinearRing; +ol.Ellipsoid.prototype.vincenty = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var minDeltaLambda = goog.isDef(opt_minDeltaLambda) ? + opt_minDeltaLambda : 1e-12; + var maxIterations = goog.isDef(opt_maxIterations) ? + opt_maxIterations : 100; + var f = this.flattening; + var lat1 = goog.math.toRadians(c1[1]); + var lat2 = goog.math.toRadians(c2[1]); + var deltaLon = goog.math.toRadians(c2[0] - c1[0]); + var U1 = Math.atan((1 - f) * Math.tan(lat1)); + var cosU1 = Math.cos(U1); + var sinU1 = Math.sin(U1); + var U2 = Math.atan((1 - f) * Math.tan(lat2)); + var cosU2 = Math.cos(U2); + var sinU2 = Math.sin(U2); + var lambda = deltaLon; + var cosSquaredAlpha, sinAlpha; + var cosLambda, deltaLambda = Infinity, sinLambda; + var cos2SigmaM, cosSigma, sigma, sinSigma; + var i; + for (i = maxIterations; i > 0; --i) { + cosLambda = Math.cos(lambda); + sinLambda = Math.sin(lambda); + var x = cosU2 * sinLambda; + var y = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; + sinSigma = Math.sqrt(x * x + y * y); + if (sinSigma === 0) { + return { + distance: 0, + initialBearing: 0, + finalBearing: 0 + }; + } + cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; + sigma = Math.atan2(sinSigma, cosSigma); + sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; + cosSquaredAlpha = 1 - sinAlpha * sinAlpha; + cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSquaredAlpha; + if (isNaN(cos2SigmaM)) { + cos2SigmaM = 0; + } + var C = f / 16 * cosSquaredAlpha * (4 + f * (4 - 3 * cosSquaredAlpha)); + var lambdaPrime = deltaLon + (1 - C) * f * sinAlpha * (sigma + + C * sinSigma * (cos2SigmaM + + C * cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1))); + deltaLambda = Math.abs(lambdaPrime - lambda); + lambda = lambdaPrime; + if (deltaLambda < minDeltaLambda) { + break; + } + } + if (i === 0) { + return { + distance: NaN, + finalBearing: NaN, + initialBearing: NaN + }; } + var aSquared = this.a * this.a; + var bSquared = this.b * this.b; + var uSquared = cosSquaredAlpha * (aSquared - bSquared) / bSquared; + var A = 1 + uSquared / 16384 * + (4096 + uSquared * (uSquared * (320 - 175 * uSquared) - 768)); + var B = uSquared / 1024 * + (256 + uSquared * (uSquared * (74 - 47 * uSquared) - 128)); + var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * + (cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1) - + B / 6 * cos2SigmaM * (4 * sinSigma * sinSigma - 3) * + (4 * cos2SigmaM * cos2SigmaM - 3))); + cosLambda = Math.cos(lambda); + sinLambda = Math.sin(lambda); + var alpha1 = Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + var alpha2 = Math.atan2(cosU1 * sinLambda, + cosU1 * sinU2 * cosLambda - sinU1 * cosU2); + return { + distance: this.b * A * (sigma - deltaSigma), + initialBearing: goog.math.toDegrees(alpha1), + finalBearing: goog.math.toDegrees(alpha2) + }; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {ol.geom.Polygon|undefined} Polygon. + * Returns the distance from c1 to c2 using Vincenty. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {number} Vincenty distance. */ -ol.format.GML3.prototype.readSurface_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Surface'); - var flatLinearRings = ol.xml.pushParseAndPop( - /** @type {Array.<Array.<number>>} */ ([null]), - this.SURFACE_PARSERS_, node, objectStack, this); - if (goog.isDef(flatLinearRings) && - !goog.isNull(flatLinearRings[0])) { - var polygon = new ol.geom.Polygon(null); - var flatCoordinates = flatLinearRings[0]; - var ends = [flatCoordinates.length]; - var i, ii; - for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { - goog.array.extend(flatCoordinates, flatLinearRings[i]); - ends.push(flatCoordinates.length); - } - polygon.setFlatCoordinates( - ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); - return polygon; - } else { - return undefined; - } +ol.Ellipsoid.prototype.vincentyDistance = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); + return vincenty.distance; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {ol.geom.LineString|undefined} LineString. + * Returns the final bearing from c1 to c2 using Vincenty. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {number} Initial bearing. */ -ol.format.GML3.prototype.readCurve_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Curve'); - var flatCoordinates = ol.xml.pushParseAndPop( - /** @type {Array.<number>} */ ([null]), - this.CURVE_PARSERS_, node, objectStack, this); - if (goog.isDef(flatCoordinates)) { - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); - return lineString; - } else { - return undefined; - } +ol.Ellipsoid.prototype.vincentyFinalBearing = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); + return vincenty.finalBearing; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {ol.Extent|undefined} Envelope. + * Returns the initial bearing from c1 to c2 using Vincenty. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {number} Initial bearing. */ -ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Envelope'); - var flatCoordinates = ol.xml.pushParseAndPop( - /** @type {Array.<number>} */ ([null]), - this.ENVELOPE_PARSERS_, node, objectStack, this); - return ol.extent.createOrUpdate(flatCoordinates[1][0], - flatCoordinates[1][1], flatCoordinates[2][0], - flatCoordinates[2][1]); +ol.Ellipsoid.prototype.vincentyInitialBearing = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); + return vincenty.initialBearing; }; +goog.provide('ol.ellipsoid.WGS84'); + +goog.require('ol.Ellipsoid'); + /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>|undefined} Flat coordinates. + * @const + * @type {ol.Ellipsoid} */ -ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) { - var s = ol.xml.getAllTextContent(node, false); - var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/; - /** @type {Array.<number>} */ - var flatCoordinates = []; - var m; - while ((m = re.exec(s))) { - flatCoordinates.push(parseFloat(m[1])); - s = s.substr(m[0].length); - } - if (s !== '') { - return undefined; - } - var context = objectStack[0]; - goog.asserts.assert(goog.isObject(context)); - var containerSrs = goog.object.get(context, 'srsName'); - var axisOrientation = 'enu'; - if (!goog.isNull(containerSrs)) { - var proj = ol.proj.get(containerSrs); - axisOrientation = proj.getAxisOrientation(); - } - if (axisOrientation === 'neu') { - var i, ii; - for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) { - var y = flatCoordinates[i]; - var x = flatCoordinates[i + 1]; - flatCoordinates[i] = x; - flatCoordinates[i + 1] = y; - } - } - var len = flatCoordinates.length; - if (len == 2) { - flatCoordinates.push(0); - } - if (len === 0) { - return undefined; - } - return flatCoordinates; -}; +ol.ellipsoid.WGS84 = new ol.Ellipsoid(6378137, 1 / 298.257223563); + +goog.provide('ol.format.Feature'); + +goog.require('ol.geom.Geometry'); +goog.require('ol.proj'); + /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>|undefined} Flat coordinates. + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for feature formats. + * {ol.format.Feature} subclasses provide the ability to decode and encode + * {@link ol.Feature} objects from a variety of commonly used geospatial + * file formats. See the documentation for each format for more details. + * + * @constructor + * @api stable */ -ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) { - var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); - var context = objectStack[0]; - goog.asserts.assert(goog.isObject(context)); - var containerSrs = goog.object.get(context, 'srsName'); - var containerDimension = node.parentNode.getAttribute('srsDimension'); - var axisOrientation = 'enu'; - if (!goog.isNull(containerSrs)) { - var proj = ol.proj.get(containerSrs); - axisOrientation = proj.getAxisOrientation(); - } - var coords = s.split(/\s+/); - // The "dimension" attribute is from the GML 3.0.1 spec. - var dim = 2; - if (!goog.isNull(node.getAttribute('srsDimension'))) { - dim = ol.format.XSD.readNonNegativeIntegerString( - node.getAttribute('srsDimension')); - } else if (!goog.isNull(node.getAttribute('dimension'))) { - dim = ol.format.XSD.readNonNegativeIntegerString( - node.getAttribute('dimension')); - } else if (!goog.isNull(containerDimension)) { - dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); - } - var x, y, z; - var flatCoordinates = []; - for (var i = 0, ii = coords.length; i < ii; i += dim) { - x = parseFloat(coords[i]); - y = parseFloat(coords[i + 1]); - z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; - if (axisOrientation.substr(0, 2) === 'en') { - flatCoordinates.push(x, y, z); - } else { - flatCoordinates.push(y, x, z); - } - } - return flatCoordinates; +ol.format.Feature = function() { + + /** + * @protected + * @type {ol.proj.Projection} + */ + this.defaultDataProjection = null; }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {Array.<string>} Extensions. */ -ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_), - 'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_) - } -}); +ol.format.Feature.prototype.getExtensions = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Adds the data projection to the read options. + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {olx.format.ReadOptions|undefined} Options. + * @protected */ -ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'interior': ol.format.GML3.prototype.interiorParser_, - 'exterior': ol.format.GML3.prototype.exteriorParser_ +ol.format.Feature.prototype.getReadOptions = function(source, opt_options) { + var options; + if (goog.isDef(opt_options)) { + options = { + dataProjection: goog.isDef(opt_options.dataProjection) ? + opt_options.dataProjection : this.readProjection(source), + featureProjection: opt_options.featureProjection + }; } -}); + return this.adaptOptions(options); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Sets the `defaultDataProjection` on the options, if no `dataProjection` + * is set. + * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options + * Options. + * @protected + * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined} + * Updated options. */ -ol.format.GML3.prototype.GEOMETRY_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), - 'MultiPoint': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readMultiPoint), - 'LineString': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readLineString), - 'MultiLineString': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readMultiLineString), - 'LinearRing' : ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readLinearRing), - 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), - 'MultiPolygon': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readMultiPolygon), - 'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_), - 'MultiSurface': ol.xml.makeReplacer( - ol.format.GML3.prototype.readMultiSurface_), - 'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_), - 'MultiCurve': ol.xml.makeReplacer( - ol.format.GML3.prototype.readMultiCurve_), - 'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_) +ol.format.Feature.prototype.adaptOptions = function(options) { + var updatedOptions; + if (goog.isDef(options)) { + updatedOptions = { + featureProjection: options.featureProjection, + dataProjection: goog.isDefAndNotNull(options.dataProjection) ? + options.dataProjection : this.defaultDataProjection, + rightHanded: options.rightHanded + }; } -}); + return updatedOptions; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {ol.format.FormatType} Format. */ -ol.format.GML3.prototype.MULTICURVE_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'curveMember': ol.xml.makeArrayPusher( - ol.format.GML3.prototype.curveMemberParser_), - 'curveMembers': ol.xml.makeArrayPusher( - ol.format.GML3.prototype.curveMemberParser_) - } -}); +ol.format.Feature.prototype.getType = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Read a single feature from a source. + * + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. */ -ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'surfaceMember': ol.xml.makeArrayPusher( - ol.format.GML3.prototype.surfaceMemberParser_), - 'surfaceMembers': ol.xml.makeArrayPusher( - ol.format.GML3.prototype.surfaceMemberParser_) - } -}); +ol.format.Feature.prototype.readFeature = goog.abstractMethod; + + +/** + * Read all features from a source. + * + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + */ +ol.format.Feature.prototype.readFeatures = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Read a single geometry from a source. + * + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. */ -ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'LineString': ol.xml.makeArrayPusher( - ol.format.GMLBase.prototype.readLineString), - 'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_) - } -}); +ol.format.Feature.prototype.readGeometry = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Read the projection from a source. + * + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. */ -ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon), - 'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_) - } -}); +ol.format.Feature.prototype.readProjection = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Encode a feature in this format. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. */ -ol.format.GML3.prototype.SURFACE_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_) - } -}); +ol.format.Feature.prototype.writeFeature = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Encode an array of features in this format. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. */ -ol.format.GML3.prototype.CURVE_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_) - } -}); +ol.format.Feature.prototype.writeFeatures = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Write a single geometry in this format. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. */ -ol.format.GML3.prototype.ENVELOPE_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'lowerCorner': ol.xml.makeArrayPusher( - ol.format.GML3.prototype.readFlatPosList_), - 'upperCorner': ol.xml.makeArrayPusher( - ol.format.GML3.prototype.readFlatPosList_) - } -}); +ol.format.Feature.prototype.writeGeometry = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. + * @param {boolean} write Set to true for writing, false for reading. + * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options + * Options. + * @return {ol.geom.Geometry|ol.Extent} Transformed geometry. + * @protected */ -ol.format.GML3.prototype.PATCHES_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'PolygonPatch': ol.xml.makeReplacer( - ol.format.GML3.prototype.readPolygonPatch_) +ol.format.Feature.transformWithOptions = function( + geometry, write, opt_options) { + var featureProjection = goog.isDef(opt_options) ? + ol.proj.get(opt_options.featureProjection) : null; + var dataProjection = goog.isDef(opt_options) ? + ol.proj.get(opt_options.dataProjection) : null; + if (!goog.isNull(featureProjection) && !goog.isNull(dataProjection) && + !ol.proj.equivalent(featureProjection, dataProjection)) { + if (geometry instanceof ol.geom.Geometry) { + return (write ? geometry.clone() : geometry).transform( + write ? featureProjection : dataProjection, + write ? dataProjection : featureProjection); + } else { + // FIXME this is necessary because ol.format.GML treats extents + // as geometries + return ol.proj.transformExtent( + write ? geometry.slice() : geometry, + write ? featureProjection : dataProjection, + write ? dataProjection : featureProjection); + } + } else { + return geometry; } -}); +}; + +goog.provide('ol.format.JSONFeature'); + +goog.require('goog.asserts'); +goog.require('goog.json'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); + /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for JSON feature formats. + * + * @constructor + * @extends {ol.format.Feature} */ -ol.format.GML3.prototype.SEGMENTS_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'LineStringSegment': ol.xml.makeReplacer( - ol.format.GML3.prototype.readLineStringSegment_) - } -}); +ol.format.JSONFeature = function() { + goog.base(this); +}; +goog.inherits(ol.format.JSONFeature, ol.format.Feature); /** - * @param {Node} node Node. - * @param {ol.geom.Point} value Point geometry. - * @param {Array.<*>} objectStack Node stack. + * @param {Document|Node|Object|string} source Source. * @private + * @return {Object} Object. */ -ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - var axisOrientation = 'enu'; - if (goog.isDefAndNotNull(srsName)) { - axisOrientation = ol.proj.get(srsName).getAxisOrientation(); - } - var point = value.getCoordinates(); - var coords; - // only 2d for simple features profile - if (axisOrientation.substr(0, 2) === 'en') { - coords = (point[0] + ' ' + point[1]); +ol.format.JSONFeature.prototype.getObject_ = function(source) { + if (goog.isObject(source)) { + return source; + } else if (goog.isString(source)) { + var object = goog.json.parse(source); + return goog.isDef(object) ? object : null; } else { - coords = (point[1] + ' ' + point[0]); + goog.asserts.fail(); + return null; } - ol.format.XSD.writeStringTextNode(node, coords); }; /** - * @param {Array.<number>} point Point geometry. - * @param {string=} opt_srsName Optional srsName - * @return {string} - * @private + * @inheritDoc */ -ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) { - var axisOrientation = 'enu'; - if (goog.isDefAndNotNull(opt_srsName)) { - axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation(); - } - return ((axisOrientation.substr(0, 2) === 'en') ? - point[0] + ' ' + point[1] : - point[1] + ' ' + point[0]); +ol.format.JSONFeature.prototype.getType = function() { + return ol.format.FormatType.JSON; }; /** - * @param {Node} node Node. - * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - // only 2d for simple features profile - var points = value.getCoordinates(); - var len = points.length; - var parts = new Array(len); - var point; - for (var i = 0; i < len; ++i) { - point = points[i]; - parts[i] = this.getCoords_(point, srsName); - } - ol.format.XSD.writeStringTextNode(node, parts.join(' ')); +ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) { + return this.readFeatureFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); }; /** - * @param {Node} node Node. - * @param {ol.geom.Point} geometry Point geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - if (goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - var pos = ol.xml.createElementNS(node.namespaceURI, 'pos'); - node.appendChild(pos); - this.writePos_(pos, geometry, objectStack); +ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) { + return this.readFeaturesFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); }; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.Feature} Feature. */ -ol.format.GML3.ENVELOPE_SERIALIZERS_ = { - 'http://www.opengis.net/gml': { - 'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) - } -}; +ol.format.JSONFeature.prototype.readFeatureFromObject = goog.abstractMethod; /** - * @param {Node} node Node. - * @param {ol.Extent} extent Extent. - * @param {Array.<*>} objectStack Node stack. + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {Array.<ol.Feature>} Features. */ -ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) { - goog.asserts.assert(extent.length == 4); - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - if (goog.isDef(srsName)) { - node.setAttribute('srsName', srsName); - } - var keys = ['lowerCorner', 'upperCorner']; - var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]]; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, - values, - objectStack, keys, this); -}; +ol.format.JSONFeature.prototype.readFeaturesFromObject = goog.abstractMethod; /** - * @param {Node} node Node. - * @param {ol.geom.LinearRing} geometry LinearRing geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writeLinearRing_ = - function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - if (goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); - node.appendChild(posList); - this.writePosList_(posList, geometry, objectStack); +ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) { + return this.readGeometryFromObject( + this.getObject_(source), this.getReadOptions(source, opt_options)); }; /** - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node} Node. - * @private + * @param {Object} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.geom.Geometry} Geometry. */ -ol.format.GML3.prototype.RING_NODE_FACTORY_ = - function(value, objectStack, opt_nodeName) { - var context = objectStack[objectStack.length - 1]; - var parentNode = context.node; - goog.asserts.assert(goog.isObject(context)); - var exteriorWritten = goog.object.get(context, 'exteriorWritten'); - if (!goog.isDef(exteriorWritten)) { - goog.object.set(context, 'exteriorWritten', true); - } - return ol.xml.createElementNS(parentNode.namespaceURI, - goog.isDef(exteriorWritten) ? 'interior' : 'exterior'); -}; +ol.format.JSONFeature.prototype.readGeometryFromObject = goog.abstractMethod; /** - * @param {Node} node Node. - * @param {ol.geom.Polygon} geometry Polygon geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writeSurfaceOrPolygon_ = - function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - if (node.nodeName !== 'PolygonPatch' && goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') { - var rings = geometry.getLinearRings(); - ol.xml.pushSerializeAndPop( - {node: node, srsName: srsName}, - ol.format.GML3.RING_SERIALIZERS_, - this.RING_NODE_FACTORY_, - rings, objectStack, undefined, this); - } else if (node.nodeName === 'Surface') { - var patches = ol.xml.createElementNS(node.namespaceURI, 'patches'); - node.appendChild(patches); - this.writeSurfacePatches_( - patches, geometry, objectStack); - } +ol.format.JSONFeature.prototype.readProjection = function(source) { + return this.readProjectionFromObject(this.getObject_(source)); }; /** - * @param {Node} node Node. - * @param {ol.geom.LineString} geometry LineString geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @param {Object} object Object. + * @protected + * @return {ol.proj.Projection} Projection. */ -ol.format.GML3.prototype.writeCurveOrLineString_ = - function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - if (node.nodeName !== 'LineStringSegment' && - goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - if (node.nodeName === 'LineString' || - node.nodeName === 'LineStringSegment') { - var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); - node.appendChild(posList); - this.writePosList_(posList, geometry, objectStack); - } else if (node.nodeName === 'Curve') { - var segments = ol.xml.createElementNS(node.namespaceURI, 'segments'); - node.appendChild(segments); - this.writeCurveSegments_(segments, - geometry, objectStack); - } -}; +ol.format.JSONFeature.prototype.readProjectionFromObject = goog.abstractMethod; /** - * @param {Node} node Node. - * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = - function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - var surface = goog.object.get(context, 'surface'); - if (goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - var polygons = geometry.getPolygons(); - ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface}, - ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_, - this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons, - objectStack, undefined, this); +ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) { + return goog.json.serialize(this.writeFeatureObject(feature, opt_options)); }; /** - * @param {Node} node Node. - * @param {ol.geom.MultiPoint} geometry MultiPoint geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. */ -ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry, - objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - if (goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - var points = geometry.getPoints(); - ol.xml.pushSerializeAndPop({node: node, srsName: srsName}, - ol.format.GML3.POINTMEMBER_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('pointMember'), points, - objectStack, undefined, this); -}; +ol.format.JSONFeature.prototype.writeFeatureObject = goog.abstractMethod; /** - * @param {Node} node Node. - * @param {ol.geom.MultiLineString} geometry MultiLineString geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writeMultiCurveOrLineString_ = - function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var srsName = goog.object.get(context, 'srsName'); - var curve = goog.object.get(context, 'curve'); - if (goog.isDefAndNotNull(srsName)) { - node.setAttribute('srsName', srsName); - } - var lines = geometry.getLineStrings(); - ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve}, - ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_, - this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines, - objectStack, undefined, this); +ol.format.JSONFeature.prototype.writeFeatures = function( + features, opt_options) { + return goog.json.serialize(this.writeFeaturesObject(features, opt_options)); }; /** - * @param {Node} node Node. - * @param {ol.geom.LinearRing} ring LinearRing geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. */ -ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) { - var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing'); - node.appendChild(linearRing); - this.writeLinearRing_(linearRing, ring, objectStack); -}; +ol.format.JSONFeature.prototype.writeFeaturesObject = goog.abstractMethod; /** - * @param {Node} node Node. - * @param {ol.geom.Polygon} polygon Polygon geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @inheritDoc */ -ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = - function(node, polygon, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var child = this.GEOMETRY_NODE_FACTORY_( - polygon, objectStack); - if (goog.isDef(child)) { - node.appendChild(child); - this.writeSurfaceOrPolygon_(child, polygon, objectStack); - } +ol.format.JSONFeature.prototype.writeGeometry = function( + geometry, opt_options) { + return goog.json.serialize(this.writeGeometryObject(geometry, opt_options)); }; /** - * @param {Node} node Node. - * @param {ol.geom.Point} point Point geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. */ -ol.format.GML3.prototype.writePointMember_ = - function(node, point, objectStack) { - var child = ol.xml.createElementNS(node.namespaceURI, 'Point'); - node.appendChild(child); - this.writePoint_(child, point, objectStack); -}; +ol.format.JSONFeature.prototype.writeGeometryObject = goog.abstractMethod; + +goog.provide('ol.format.EsriJSON'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.extent'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.proj'); + /** - * @param {Node} node Node. - * @param {ol.geom.LineString} line LineString geometry. - * @param {Array.<*>} objectStack Node stack. - * @private + * @classdesc + * Feature format for reading and writing data in the EsriJSON format. + * + * @constructor + * @extends {ol.format.JSONFeature} + * @param {olx.format.EsriJSONOptions=} opt_options Options. + * @api */ -ol.format.GML3.prototype.writeLineStringOrCurveMember_ = - function(node, line, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack); - if (goog.isDef(child)) { - node.appendChild(child); - this.writeCurveOrLineString_(child, line, objectStack); - } +ol.format.EsriJSON = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + goog.base(this); + + /** + * Name of the geometry attribute for features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + }; +goog.inherits(ol.format.EsriJSON, ol.format.JSONFeature); /** - * @param {Node} node Node. - * @param {ol.geom.Polygon} polygon Polygon geometry. - * @param {Array.<*>} objectStack Node stack. + * @param {EsriJSONGeometry} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. * @private + * @return {ol.geom.Geometry} Geometry. */ -ol.format.GML3.prototype.writeSurfacePatches_ = - function(node, polygon, objectStack) { - var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch'); - node.appendChild(child); - this.writeSurfaceOrPolygon_(child, polygon, objectStack); +ol.format.EsriJSON.readGeometry_ = function(object, opt_options) { + if (goog.isNull(object)) { + return null; + } + var type; + if (goog.isNumber(object.x) && goog.isNumber(object.y)) { + type = ol.geom.GeometryType.POINT; + } else if (goog.isDefAndNotNull(object.points)) { + type = ol.geom.GeometryType.MULTI_POINT; + } else if (goog.isDefAndNotNull(object.paths)) { + if (object.paths.length === 1) { + type = ol.geom.GeometryType.LINE_STRING; + } else { + type = ol.geom.GeometryType.MULTI_LINE_STRING; + } + } else if (goog.isDefAndNotNull(object.rings)) { + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + var rings = ol.format.EsriJSON.convertRings_(object.rings, layout); + object = /** @type {EsriJSONGeometry} */(goog.object.clone(object)); + if (rings.length === 1) { + type = ol.geom.GeometryType.POLYGON; + object.rings = rings[0]; + } else { + type = ol.geom.GeometryType.MULTI_POLYGON; + object.rings = rings; + } + } + goog.asserts.assert(goog.isDef(type), 'geometry type should be defined'); + var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type]; + goog.asserts.assert(goog.isDef(geometryReader), + 'geometryReader should be defined'); + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + geometryReader(object), false, opt_options)); }; /** - * @param {Node} node Node. - * @param {ol.geom.LineString} line LineString geometry. - * @param {Array.<*>} objectStack Node stack. + * Determines inner and outer rings. + * Checks if any polygons in this array contain any other polygons in this + * array. It is used for checking for holes. + * Logic inspired by: https://github.com/Esri/terraformer-arcgis-parser + * @param {Array.<!Array.<!Array.<number>>>} rings Rings. + * @param {ol.geom.GeometryLayout} layout Geometry layout. * @private + * @return {Array.<!Array.<!Array.<number>>>} Transoformed rings. */ -ol.format.GML3.prototype.writeCurveSegments_ = - function(node, line, objectStack) { - var child = ol.xml.createElementNS(node.namespaceURI, - 'LineStringSegment'); - node.appendChild(child); - this.writeCurveOrLineString_(child, line, objectStack); +ol.format.EsriJSON.convertRings_ = function(rings, layout) { + var outerRings = []; + var holes = []; + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + var flatRing = goog.array.flatten(rings[i]); + // is this ring an outer ring? is it clockwise? + var clockwise = ol.geom.flat.orient.linearRingIsClockwise(flatRing, 0, + flatRing.length, layout.length); + if (clockwise) { + outerRings.push([rings[i]]); + } else { + holes.push(rings[i]); + } + } + while (holes.length) { + var hole = holes.shift(); + var matched = false; + // loop over all outer rings and see if they contain our hole. + for (i = outerRings.length - 1; i >= 0; i--) { + var outerRing = outerRings[i][0]; + if (ol.extent.containsExtent(new ol.geom.LinearRing( + outerRing).getExtent(), + new ol.geom.LinearRing(hole).getExtent())) { + // the hole is contained push it into our polygon + outerRings[i].push(hole); + matched = true; + break; + } + } + if (!matched) { + // no outer rings contain this hole turn it into and outer + // ring (reverse it) + outerRings.push([hole.reverse()]); + } + } + return outerRings; }; /** - * @param {Node} node Node. - * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. - * @param {Array.<*>} objectStack Node stack. + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} Point. */ -ol.format.GML3.prototype.writeGeometryElement = - function(node, geometry, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var item = goog.object.clone(context); - item.node = node; - var value; - if (goog.isArray(geometry)) { - if (goog.isDef(context.dataProjection)) { - value = ol.proj.transformExtent( - geometry, context.featureProjection, context.dataProjection); - } else { - value = geometry; - } +ol.format.EsriJSON.readPointGeometry_ = function(object) { + goog.asserts.assert(goog.isNumber(object.x), 'object.x should be number'); + goog.asserts.assert(goog.isNumber(object.y), 'object.y should be number'); + var point; + if (goog.isDefAndNotNull(object.m) && goog.isDefAndNotNull(object.z)) { + point = new ol.geom.Point([object.x, object.y, object.z, object.m], + ol.geom.GeometryLayout.XYZM); + } else if (goog.isDefAndNotNull(object.z)) { + point = new ol.geom.Point([object.x, object.y, object.z], + ol.geom.GeometryLayout.XYZ); + } else if (goog.isDefAndNotNull(object.m)) { + point = new ol.geom.Point([object.x, object.y, object.m], + ol.geom.GeometryLayout.XYM); } else { - goog.asserts.assertInstanceof(geometry, ol.geom.Geometry); - value = - ol.format.Feature.transformWithOptions(geometry, true, context); + point = new ol.geom.Point([object.x, object.y]); } - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - (item), ol.format.GML3.GEOMETRY_SERIALIZERS_, - this.GEOMETRY_NODE_FACTORY_, [value], - objectStack, undefined, this); + return point; }; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Node stack. + * @param {EsriJSONGeometry} object Object. + * @private + * @return {ol.geom.Geometry} LineString. */ -ol.format.GML3.prototype.writeFeatureElement = - function(node, feature, objectStack) { - var fid = feature.getId(); - if (goog.isDef(fid)) { - node.setAttribute('fid', fid); - } - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var featureNS = goog.object.get(context, 'featureNS'); - var geometryName = feature.getGeometryName(); - if (!goog.isDef(context.serializers)) { - context.serializers = {}; - context.serializers[featureNS] = {}; - } - var properties = feature.getProperties(); - var keys = [], values = []; - for (var key in properties) { - var value = properties[key]; - if (!goog.isNull(value)) { - keys.push(key); - values.push(value); - if (key == geometryName) { - if (!(key in context.serializers[featureNS])) { - context.serializers[featureNS][key] = ol.xml.makeChildAppender( - this.writeGeometryElement, this); - } - } else { - if (!(key in context.serializers[featureNS])) { - context.serializers[featureNS][key] = ol.xml.makeChildAppender( - ol.format.XSD.writeStringTextNode); - } - } - } - } - var item = goog.object.clone(context); - item.node = node; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - (item), context.serializers, - ol.xml.makeSimpleNodeFactory(undefined, featureNS), - values, - objectStack, keys); +ol.format.EsriJSON.readLineStringGeometry_ = function(object) { + goog.asserts.assert(goog.isArray(object.paths), + 'object.paths should be an array'); + goog.asserts.assert(object.paths.length === 1, + 'object.paths array length should be 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.LineString(object.paths[0], layout); }; /** - * @param {Node} node Node. - * @param {Array.<ol.Feature>} features Features. - * @param {Array.<*>} objectStack Node stack. + * @param {EsriJSONGeometry} object Object. * @private + * @return {ol.geom.Geometry} MultiLineString. */ -ol.format.GML3.prototype.writeFeatureMembers_ = - function(node, features, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var featureType = goog.object.get(context, 'featureType'); - var featureNS = goog.object.get(context, 'featureNS'); - var serializers = {}; - serializers[featureNS] = {}; - serializers[featureNS][featureType] = ol.xml.makeChildAppender( - this.writeFeatureElement, this); - var item = goog.object.clone(context); - item.node = node; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - (item), - serializers, - ol.xml.makeSimpleNodeFactory(featureType, featureNS), features, - objectStack); +ol.format.EsriJSON.readMultiLineStringGeometry_ = function(object) { + goog.asserts.assert(goog.isArray(object.paths), + 'object.paths should be an array'); + goog.asserts.assert(object.paths.length > 1, + 'object.paths array length should be more than 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiLineString(object.paths, layout); }; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @param {EsriJSONGeometry} object Object. * @private + * @return {ol.geom.GeometryLayout} The geometry layout to use. */ -ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = { - 'http://www.opengis.net/gml': { - 'surfaceMember': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeSurfaceOrPolygonMember_), - 'polygonMember': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeSurfaceOrPolygonMember_) +ol.format.EsriJSON.getGeometryLayout_ = function(object) { + var layout = ol.geom.GeometryLayout.XY; + if (object.hasZ === true && object.hasM === true) { + layout = ol.geom.GeometryLayout.XYZM; + } else if (object.hasZ === true) { + layout = ol.geom.GeometryLayout.XYZ; + } else if (object.hasM === true) { + layout = ol.geom.GeometryLayout.XYM; } + return layout; }; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @param {EsriJSONGeometry} object Object. * @private + * @return {ol.geom.Geometry} MultiPoint. */ -ol.format.GML3.POINTMEMBER_SERIALIZERS_ = { - 'http://www.opengis.net/gml': { - 'pointMember': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writePointMember_) - } +ol.format.EsriJSON.readMultiPointGeometry_ = function(object) { + goog.asserts.assert(goog.isDefAndNotNull(object.points), + 'object.points should be defined'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiPoint(object.points, layout); }; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @param {EsriJSONGeometry} object Object. * @private + * @return {ol.geom.Geometry} MultiPolygon. */ -ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = { - 'http://www.opengis.net/gml': { - 'lineStringMember': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeLineStringOrCurveMember_), - 'curveMember': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeLineStringOrCurveMember_) - } +ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) { + goog.asserts.assert(goog.isDefAndNotNull(object.rings)); + goog.asserts.assert(object.rings.length > 1, + 'object.rings should have length larger than 1'); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.MultiPolygon( + /** @type {Array.<Array.<Array.<Array.<number>>>>} */(object.rings), + layout); }; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @param {EsriJSONGeometry} object Object. * @private + * @return {ol.geom.Geometry} Polygon. */ -ol.format.GML3.RING_SERIALIZERS_ = { - 'http://www.opengis.net/gml': { - 'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_), - 'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_) - } +ol.format.EsriJSON.readPolygonGeometry_ = function(object) { + goog.asserts.assert(goog.isDefAndNotNull(object.rings)); + var layout = ol.format.EsriJSON.getGeometryLayout_(object); + return new ol.geom.Polygon(object.rings, layout); }; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {EsriJSONGeometry} EsriJSON geometry. */ -ol.format.GML3.GEOMETRY_SERIALIZERS_ = { - 'http://www.opengis.net/gml': { - 'Curve': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeCurveOrLineString_), - 'MultiCurve': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeMultiCurveOrLineString_), - 'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_), - 'MultiPoint': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeMultiPoint_), - 'LineString': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeCurveOrLineString_), - 'MultiLineString': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeMultiCurveOrLineString_), - 'LinearRing': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeLinearRing_), - 'Polygon': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeSurfaceOrPolygon_), - 'MultiPolygon': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), - 'Surface': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeSurfaceOrPolygon_), - 'MultiSurface': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), - 'Envelope': ol.xml.makeChildAppender( - ol.format.GML3.prototype.writeEnvelope) +ol.format.EsriJSON.writePointGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + var coordinates = geometry.getCoordinates(); + var layout = geometry.getLayout(); + if (layout === ol.geom.GeometryLayout.XYZ) { + return /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + z: coordinates[2] + }); + } else if (layout === ol.geom.GeometryLayout.XYM) { + return /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + m: coordinates[2] + }); + } else if (layout === ol.geom.GeometryLayout.XYZM) { + return /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1], + z: coordinates[2], + m: coordinates[3] + }); + } else if (layout === ol.geom.GeometryLayout.XY) { + return /** @type {EsriJSONPoint} */ ({ + x: coordinates[0], + y: coordinates[1] + }); + } else { + goog.asserts.fail('Unknown geometry layout'); } }; /** - * @const - * @type {Object.<string, string>} + * @param {ol.geom.SimpleGeometry} geometry Geometry. * @private + * @return {Object} Object with boolean hasZ and hasM keys. */ -ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = { - 'MultiLineString': 'lineStringMember', - 'MultiCurve': 'curveMember', - 'MultiPolygon': 'polygonMember', - 'MultiSurface': 'surfaceMember' +ol.format.EsriJSON.getHasZM_ = function(geometry) { + var layout = geometry.getLayout(); + return { + hasZ: (layout === ol.geom.GeometryLayout.XYZ || + layout === ol.geom.GeometryLayout.XYZM), + hasM: (layout === ol.geom.GeometryLayout.XYM || + layout === ol.geom.GeometryLayout.XYZM) + }; }; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {EsriJSONPolyline} EsriJSON geometry. */ -ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = - function(value, objectStack, opt_nodeName) { - var parentNode = objectStack[objectStack.length - 1].node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - return ol.xml.createElementNS('http://www.opengis.net/gml', - ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]); +ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + var hasZM = ol.format.EsriJSON.getHasZM_(geometry); + return /** @type {EsriJSONPolyline} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + paths: [geometry.getCoordinates()] + }); }; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private - */ -ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = - function(value, objectStack, opt_nodeName) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var multiSurface = goog.object.get(context, 'multiSurface'); - var surface = goog.object.get(context, 'surface'); - var curve = goog.object.get(context, 'curve'); - var multiCurve = goog.object.get(context, 'multiCurve'); - var parentNode = objectStack[objectStack.length - 1].node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - var nodeName; - if (!goog.isArray(value)) { - goog.asserts.assertInstanceof(value, ol.geom.Geometry); - nodeName = value.getType(); - if (nodeName === 'MultiPolygon' && multiSurface === true) { - nodeName = 'MultiSurface'; - } else if (nodeName === 'Polygon' && surface === true) { - nodeName = 'Surface'; - } else if (nodeName === 'LineString' && curve === true) { - nodeName = 'Curve'; - } else if (nodeName === 'MultiLineString' && multiCurve === true) { - nodeName = 'MultiCurve'; - } - } else { - nodeName = 'Envelope'; - } - return ol.xml.createElementNS('http://www.opengis.net/gml', - nodeName); + * @return {EsriJSONPolygon} EsriJSON geometry. + */ +ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, + 'geometry should be an ol.geom.Polygon'); + // Esri geometries use the left-hand rule + var hasZM = ol.format.EsriJSON.getHasZM_(geometry); + return /** @type {EsriJSONPolygon} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + rings: geometry.getCoordinates(false) + }); }; /** - * Encode a geometry in GML 3.1.1 Simple Features. - * * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {Node} Node. - * @api + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolyline} EsriJSON geometry. */ -ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) { - opt_options = this.adaptOptions(opt_options); - var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom'); - var context = {node: geom, srsName: this.srsName, - curve: this.curve_, surface: this.surface_, - multiSurface: this.multiSurface_, multiCurve: this.multiCurve_}; - if (goog.isDef(opt_options)) { - goog.object.extend(context, opt_options); - } - this.writeGeometryElement(geom, geometry, [context]); - return geom; +ol.format.EsriJSON.writeMultiLineStringGeometry_ = + function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, + 'geometry should be an ol.geom.MultiLineString'); + var hasZM = ol.format.EsriJSON.getHasZM_(geometry); + return /** @type {EsriJSONPolyline} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + paths: geometry.getCoordinates() + }); }; /** - * Encode an array of features in GML 3.1.1 Simple Features. - * - * @function - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {string} Result. - * @api stable + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONMultipoint} EsriJSON geometry. */ -ol.format.GML3.prototype.writeFeatures; +ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint, + 'geometry should be an ol.geom.MultiPoint'); + var hasZM = ol.format.EsriJSON.getHasZM_(geometry); + return /** @type {EsriJSONMultipoint} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + points: geometry.getCoordinates() + }); +}; /** - * Encode an array of features in the GML 3.1.1 format as an XML node. - * - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {Node} Node. - * @api + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {EsriJSONPolygon} EsriJSON geometry. */ -ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) { - opt_options = this.adaptOptions(opt_options); - var node = ol.xml.createElementNS('http://www.opengis.net/gml', - 'featureMembers'); - ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:schemaLocation', this.schemaLocation); - var context = { - srsName: this.srsName, - curve: this.curve_, - surface: this.surface_, - multiSurface: this.multiSurface_, - multiCurve: this.multiCurve_, - featureNS: this.featureNS, - featureType: this.featureType - }; - if (goog.isDef(opt_options)) { - goog.object.extend(context, opt_options); - } - this.writeFeatureMembers_(node, features, [context]); - return node; +ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry, + opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon, + 'geometry should be an ol.geom.MultiPolygon'); + var hasZM = ol.format.EsriJSON.getHasZM_(geometry); + var coordinates = geometry.getCoordinates(false); + var output = []; + for (var i = 0; i < coordinates.length; i++) { + for (var x = coordinates[i].length - 1; x >= 0; x--) { + output.push(coordinates[i][x]); + } + } + return /** @type {EsriJSONPolygon} */ ({ + hasZ: hasZM.hasZ, + hasM: hasZM.hasM, + rings: output + }); }; +/** + * @const + * @private + * @type {Object.<ol.geom.GeometryType, function(EsriJSONGeometry): ol.geom.Geometry>} + */ +ol.format.EsriJSON.GEOMETRY_READERS_ = {}; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POINT] = + ol.format.EsriJSON.readPointGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.LINE_STRING] = + ol.format.EsriJSON.readLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POLYGON] = + ol.format.EsriJSON.readPolygonGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POINT] = + ol.format.EsriJSON.readMultiPointGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_LINE_STRING] = + ol.format.EsriJSON.readMultiLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] = + ol.format.EsriJSON.readMultiPolygonGeometry_; + /** - * @classdesc - * Feature format for reading and writing data in the GML format - * version 3.1.1. - * Currently only supports GML 3.1.1 Simple Features profile. - * - * @constructor - * @param {olx.format.GMLOptions=} opt_options - * Optional configuration object. - * @extends {ol.format.GMLBase} - * @api stable + * @const + * @private + * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>} */ -ol.format.GML = ol.format.GML3; +ol.format.EsriJSON.GEOMETRY_WRITERS_ = {}; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POINT] = + ol.format.EsriJSON.writePointGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.LINE_STRING] = + ol.format.EsriJSON.writeLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POLYGON] = + ol.format.EsriJSON.writePolygonGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POINT] = + ol.format.EsriJSON.writeMultiPointGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_LINE_STRING] = + ol.format.EsriJSON.writeMultiLineStringGeometry_; +ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POLYGON] = + ol.format.EsriJSON.writeMultiPolygonGeometry_; /** - * Encode an array of features in GML 3.1.1 Simple Features. + * Read a feature from a EsriJSON Feature source. Only works for Feature, + * use `readFeatures` to read FeatureCollection source. * * @function - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {string} Result. - * @api stable + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api */ -ol.format.GML.prototype.writeFeatures; +ol.format.EsriJSON.prototype.readFeature; /** - * Encode an array of features in the GML 3.1.1 format as an XML node. + * Read all features from a EsriJSON source. Works with both Feature and + * FeatureCollection sources. * * @function - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {Node} Node. + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. * @api */ -ol.format.GML.prototype.writeFeaturesNode; +ol.format.EsriJSON.prototype.readFeatures; -goog.provide('ol.format.GML2'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('ol.extent'); -goog.require('ol.format.GML'); -goog.require('ol.format.GMLBase'); -goog.require('ol.format.XSD'); -goog.require('ol.proj'); -goog.require('ol.xml'); +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readFeatureFromObject = function( + object, opt_options) { + var esriJSONFeature = /** @type {EsriJSONFeature} */ (object); + goog.asserts.assert(goog.isDefAndNotNull(esriJSONFeature.geometry) || + goog.isDefAndNotNull(esriJSONFeature.compressedGeometry) || + goog.isDefAndNotNull(esriJSONFeature.attributes), + 'geometry, compressedGeometry or attributes should be defined'); + var geometry = ol.format.EsriJSON.readGeometry_(esriJSONFeature.geometry, + opt_options); + var feature = new ol.Feature(); + if (goog.isDef(this.geometryName_)) { + feature.setGeometryName(this.geometryName_); + } + feature.setGeometry(geometry); + if (goog.isDef(opt_options) && goog.isDef(opt_options.idField) && + goog.isDef(esriJSONFeature.attributes[opt_options.idField])) { + goog.asserts.assert( + goog.isNumber(esriJSONFeature.attributes[opt_options.idField]), + 'objectIdFieldName value should be a number'); + feature.setId(/** @type {number} */( + esriJSONFeature.attributes[opt_options.idField])); + } + if (goog.isDef(esriJSONFeature.attributes)) { + feature.setProperties(esriJSONFeature.attributes); + } + return feature; +}; + +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + var esriJSONObject = /** @type {EsriJSONObject} */ (object); + var options = goog.isDef(opt_options) ? opt_options : {}; + if (goog.isDefAndNotNull(esriJSONObject.features)) { + var esriJSONFeatureCollection = /** @type {EsriJSONFeatureCollection} */ + (object); + /** @type {Array.<ol.Feature>} */ + var features = []; + var esriJSONFeatures = esriJSONFeatureCollection.features; + var i, ii; + options.idField = object.objectIdFieldName; + for (i = 0, ii = esriJSONFeatures.length; i < ii; ++i) { + features.push(this.readFeatureFromObject(esriJSONFeatures[i], + options)); + } + return features; + } else { + return [this.readFeatureFromObject(object, options)]; + } +}; /** - * @classdesc - * Feature format for reading and writing data in the GML format, - * version 2.1.2. + * Read a geometry from a EsriJSON source. * - * @constructor - * @param {olx.format.GMLOptions=} opt_options Optional configuration object. - * @extends {ol.format.GMLBase} + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. * @api */ -ol.format.GML2 = function(opt_options) { - var options = /** @type {olx.format.GMLOptions} */ - (goog.isDef(opt_options) ? opt_options : {}); - - goog.base(this, options); +ol.format.EsriJSON.prototype.readGeometry; - /** - * @inheritDoc - */ - this.schemaLocation = goog.isDef(options.schemaLocation) ? - options.schemaLocation : ol.format.GML2.schemaLocation_; +/** + * @inheritDoc + */ +ol.format.EsriJSON.prototype.readGeometryFromObject = function( + object, opt_options) { + return ol.format.EsriJSON.readGeometry_( + /** @type {EsriJSONGeometry} */ (object), opt_options); }; -goog.inherits(ol.format.GML2, ol.format.GMLBase); /** - * @const - * @type {string} - * @private + * Read the projection from a EsriJSON source. + * + * @function + * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api */ -ol.format.GML2.schemaLocation_ = 'http://www.opengis.net/gml ' + - 'http://schemas.opengis.net/gml/2.1.2/feature.xsd'; +ol.format.EsriJSON.prototype.readProjection; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<number>|undefined} Flat coordinates. + * @inheritDoc */ -ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) { - var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); - var context = objectStack[0]; - goog.asserts.assert(goog.isObject(context)); - var containerSrs = goog.object.get(context, 'srsName'); - var containerDimension = node.parentNode.getAttribute('srsDimension'); - var axisOrientation = 'enu'; - if (!goog.isNull(containerSrs)) { - var proj = ol.proj.get(containerSrs); - axisOrientation = proj.getAxisOrientation(); - } - var coords = s.split(/[\s,]+/); - // The "dimension" attribute is from the GML 3.0.1 spec. - var dim = 2; - if (!goog.isNull(node.getAttribute('srsDimension'))) { - dim = ol.format.XSD.readNonNegativeIntegerString( - node.getAttribute('srsDimension')); - } else if (!goog.isNull(node.getAttribute('dimension'))) { - dim = ol.format.XSD.readNonNegativeIntegerString( - node.getAttribute('dimension')); - } else if (!goog.isNull(containerDimension)) { - dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); - } - var x, y, z; - var flatCoordinates = []; - for (var i = 0, ii = coords.length; i < ii; i += dim) { - x = parseFloat(coords[i]); - y = parseFloat(coords[i + 1]); - z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; - if (axisOrientation.substr(0, 2) === 'en') { - flatCoordinates.push(x, y, z); - } else { - flatCoordinates.push(y, x, z); - } +ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) { + var esriJSONObject = /** @type {EsriJSONObject} */ (object); + if (goog.isDefAndNotNull(esriJSONObject.spatialReference) && + goog.isDefAndNotNull(esriJSONObject.spatialReference.wkid)) { + var crs = esriJSONObject.spatialReference.wkid; + return ol.proj.get('EPSG:' + crs); + } else { + return null; } - return flatCoordinates; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private - * @return {ol.Extent|undefined} Envelope. + * @return {EsriJSONGeometry} EsriJSON geometry. */ -ol.format.GML2.prototype.readBox_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Box'); - var flatCoordinates = ol.xml.pushParseAndPop( - /** @type {Array.<number>} */ ([null]), - this.BOX_PARSERS_, node, objectStack, this); - return ol.extent.createOrUpdate(flatCoordinates[1][0], - flatCoordinates[1][1], flatCoordinates[1][3], - flatCoordinates[1][4]); +ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) { + var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()]; + goog.asserts.assert(goog.isDef(geometryWriter), + 'geometryWriter should be defined'); + return geometryWriter(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode a geometry as a EsriJSON string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api */ -ol.format.GML2.prototype.innerBoundaryIsParser_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'innerBoundaryIs'); - var flatLinearRing = ol.xml.pushParseAndPop( - /** @type {Array.<number>|undefined} */ (undefined), - this.RING_PARSERS, node, objectStack, this); - if (goog.isDef(flatLinearRing)) { - var flatLinearRings = /** @type {Array.<Array.<number>>} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(flatLinearRings)); - goog.asserts.assert(flatLinearRings.length > 0); - flatLinearRings.push(flatLinearRing); - } -}; +ol.format.EsriJSON.prototype.writeGeometry; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode a geometry as a EsriJSON object. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {EsriJSONGeometry} Object. + * @api */ -ol.format.GML2.prototype.outerBoundaryIsParser_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'outerBoundaryIs'); - var flatLinearRing = ol.xml.pushParseAndPop( - /** @type {Array.<number>|undefined} */ (undefined), - this.RING_PARSERS, node, objectStack, this); - if (goog.isDef(flatLinearRing)) { - var flatLinearRings = /** @type {Array.<Array.<number>>} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(flatLinearRings)); - goog.asserts.assert(flatLinearRings.length > 0); - flatLinearRings[0] = flatLinearRing; - } +ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry, + opt_options) { + return ol.format.EsriJSON.writeGeometry_(geometry, + this.adaptOptions(opt_options)); }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Encode a feature as a EsriJSON Feature string. + * + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api */ -ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'coordinates': ol.xml.makeReplacer( - ol.format.GML2.prototype.readFlatCoordinates_) - } -}); +ol.format.EsriJSON.prototype.writeFeature; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Encode a feature as a esriJSON Feature object. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + * @api */ -ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_, - 'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_ +ol.format.EsriJSON.prototype.writeFeatureObject = function( + feature, opt_options) { + opt_options = this.adaptOptions(opt_options); + var object = {}; + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + object['geometry'] = + ol.format.EsriJSON.writeGeometry_(geometry, opt_options); } -}); + var properties = feature.getProperties(); + goog.object.remove(properties, feature.getGeometryName()); + if (!goog.object.isEmpty(properties)) { + object['attributes'] = properties; + } else { + object['attributes'] = {}; + } + if (goog.isDef(opt_options) && goog.isDef(opt_options.featureProjection)) { + object['spatialReference'] = /** @type {EsriJSONCRS} */({ + wkid: ol.proj.get( + opt_options.featureProjection).getCode().split(':').pop() + }); + } + return object; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Encode an array of features as EsriJSON. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} EsriJSON. + * @api */ -ol.format.GML2.prototype.BOX_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'coordinates': ol.xml.makeArrayPusher( - ol.format.GML2.prototype.readFlatCoordinates_) - } -}); +ol.format.EsriJSON.prototype.writeFeatures; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Encode an array of features as a EsriJSON object. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} EsriJSON Object. + * @api */ -ol.format.GML2.prototype.GEOMETRY_PARSERS_ = Object({ - 'http://www.opengis.net/gml' : { - 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), - 'MultiPoint': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readMultiPoint), - 'LineString': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readLineString), - 'MultiLineString': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readMultiLineString), - 'LinearRing' : ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readLinearRing), - 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), - 'MultiPolygon': ol.xml.makeReplacer( - ol.format.GMLBase.prototype.readMultiPolygon), - 'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_) +ol.format.EsriJSON.prototype.writeFeaturesObject = + function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var objects = []; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + objects.push(this.writeFeatureObject(features[i], opt_options)); } -}); + return /** @type {EsriJSONFeatureCollection} */ ({ + 'features': objects + }); +}; -goog.provide('ol.format.GPX'); +// TODO: serialize dataProjection as crs member when writing +// see https://github.com/openlayers/ol3/issues/2078 + +goog.provide('ol.format.GeoJSON'); goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); goog.require('goog.object'); goog.require('ol.Feature'); goog.require('ol.format.Feature'); -goog.require('ol.format.XMLFeature'); -goog.require('ol.format.XSD'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.GeometryCollection'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); goog.require('ol.proj'); -goog.require('ol.xml'); /** * @classdesc - * Feature format for reading and writing data in the GPX format. + * Feature format for reading and writing data in the GeoJSON format. * * @constructor - * @extends {ol.format.XMLFeature} - * @param {olx.format.GPXOptions=} opt_options Options. + * @extends {ol.format.JSONFeature} + * @param {olx.format.GeoJSONOptions=} opt_options Options. * @api stable */ -ol.format.GPX = function(opt_options) { +ol.format.GeoJSON = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; @@ -86244,2761 +88532,3138 @@ ol.format.GPX = function(opt_options) { /** * @inheritDoc */ - this.defaultDataProjection = ol.proj.get('EPSG:4326'); + this.defaultDataProjection = ol.proj.get( + goog.isDefAndNotNull(options.defaultDataProjection) ? + options.defaultDataProjection : 'EPSG:4326'); + /** - * @type {function(ol.Feature, Node)|undefined} + * Name of the geometry attribute for features. + * @type {string|undefined} * @private */ - this.readExtensions_ = options.readExtensions; + this.geometryName_ = options.geometryName; + }; -goog.inherits(ol.format.GPX, ol.format.XMLFeature); +goog.inherits(ol.format.GeoJSON, ol.format.JSONFeature); /** * @const - * @private * @type {Array.<string>} + * @private */ -ol.format.GPX.NAMESPACE_URIS_ = [ - null, - 'http://www.topografix.com/GPX/1/0', - 'http://www.topografix.com/GPX/1/1' -]; +ol.format.GeoJSON.EXTENSIONS_ = ['.geojson']; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {Node} node Node. - * @param {Object} values Values. + * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. * @private - * @return {Array.<number>} Flat coordinates. + * @return {ol.geom.Geometry} Geometry. */ -ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - flatCoordinates.push( - parseFloat(node.getAttribute('lon')), - parseFloat(node.getAttribute('lat'))); - if (goog.object.containsKey(values, 'ele')) { - flatCoordinates.push( - /** @type {number} */ (goog.object.get(values, 'ele'))); - goog.object.remove(values, 'ele'); - } else { - flatCoordinates.push(0); - } - if (goog.object.containsKey(values, 'time')) { - flatCoordinates.push( - /** @type {number} */ (goog.object.get(values, 'time'))); - goog.object.remove(values, 'time'); - } else { - flatCoordinates.push(0); +ol.format.GeoJSON.readGeometry_ = function(object, opt_options) { + if (goog.isNull(object)) { + return null; } - return flatCoordinates; + var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type]; + goog.asserts.assert(goog.isDef(geometryReader), + 'geometryReader should be defined'); + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + geometryReader(object), false, opt_options)); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometryCollection} object Object. + * @param {olx.format.ReadOptions=} opt_options Read options. * @private + * @return {ol.geom.GeometryCollection} Geometry collection. */ -ol.format.GPX.parseLink_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'link'); - var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); - var href = node.getAttribute('href'); - if (!goog.isNull(href)) { - goog.object.set(values, 'link', href); - } - ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack); +ol.format.GeoJSON.readGeometryCollectionGeometry_ = function( + object, opt_options) { + goog.asserts.assert(object.type == 'GeometryCollection', + 'object.type should be GeometryCollection'); + var geometries = goog.array.map(object.geometries, + /** + * @param {GeoJSONGeometry} geometry Geometry. + * @return {ol.geom.Geometry} geometry Geometry. + */ + function(geometry) { + return ol.format.GeoJSON.readGeometry_(geometry, opt_options); + }); + return new ol.geom.GeometryCollection(geometries); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometry} object Object. * @private + * @return {ol.geom.Point} Point. */ -ol.format.GPX.parseExtensions_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'extensions'); - var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); - goog.object.set(values, 'extensionsNode_', node); +ol.format.GeoJSON.readPointGeometry_ = function(object) { + goog.asserts.assert(object.type == 'Point', + 'object.type should be Point'); + return new ol.geom.Point(object.coordinates); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometry} object Object. * @private + * @return {ol.geom.LineString} LineString. */ -ol.format.GPX.parseRtePt_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'rtept'); - var values = ol.xml.pushParseAndPop( - {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack); - if (goog.isDef(values)) { - var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); - var flatCoordinates = /** @type {Array.<number>} */ - (goog.object.get(rteValues, 'flatCoordinates')); - ol.format.GPX.appendCoordinate_(flatCoordinates, node, values); - } +ol.format.GeoJSON.readLineStringGeometry_ = function(object) { + goog.asserts.assert(object.type == 'LineString', + 'object.type should be LineString'); + return new ol.geom.LineString(object.coordinates); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometry} object Object. * @private + * @return {ol.geom.MultiLineString} MultiLineString. */ -ol.format.GPX.parseTrkPt_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'trkpt'); - var values = ol.xml.pushParseAndPop( - {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack); - if (goog.isDef(values)) { - var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); - var flatCoordinates = /** @type {Array.<number>} */ - (goog.object.get(trkValues, 'flatCoordinates')); - ol.format.GPX.appendCoordinate_(flatCoordinates, node, values); - } +ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) { + goog.asserts.assert(object.type == 'MultiLineString', + 'object.type should be MultiLineString'); + return new ol.geom.MultiLineString(object.coordinates); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometry} object Object. * @private + * @return {ol.geom.MultiPoint} MultiPoint. */ -ol.format.GPX.parseTrkSeg_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'trkseg'); - var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); - ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack); - var flatCoordinates = /** @type {Array.<number>} */ - (goog.object.get(values, 'flatCoordinates')); - var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends')); - ends.push(flatCoordinates.length); +ol.format.GeoJSON.readMultiPointGeometry_ = function(object) { + goog.asserts.assert(object.type == 'MultiPoint', + 'object.type should be MultiPoint'); + return new ol.geom.MultiPoint(object.coordinates); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometry} object Object. * @private - * @return {ol.Feature|undefined} Track. + * @return {ol.geom.MultiPolygon} MultiPolygon. */ -ol.format.GPX.readRte_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'rte'); - var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); - var values = ol.xml.pushParseAndPop({ - 'flatCoordinates': [] - }, ol.format.GPX.RTE_PARSERS_, node, objectStack); - if (!goog.isDef(values)) { - return undefined; - } - var flatCoordinates = /** @type {Array.<number>} */ - (goog.object.get(values, 'flatCoordinates')); - goog.object.remove(values, 'flatCoordinates'); - var geometry = new ol.geom.LineString(null); - geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); - ol.format.Feature.transformWithOptions(geometry, false, options); - var feature = new ol.Feature(geometry); - feature.setProperties(values); - return feature; +ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) { + goog.asserts.assert(object.type == 'MultiPolygon', + 'object.type should be MultiPolygon'); + return new ol.geom.MultiPolygon(object.coordinates); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {GeoJSONGeometry} object Object. * @private - * @return {ol.Feature|undefined} Track. + * @return {ol.geom.Polygon} Polygon. */ -ol.format.GPX.readTrk_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'trk'); - var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); - var values = ol.xml.pushParseAndPop({ - 'flatCoordinates': [], - 'ends': [] - }, ol.format.GPX.TRK_PARSERS_, node, objectStack); - if (!goog.isDef(values)) { - return undefined; - } - var flatCoordinates = /** @type {Array.<number>} */ - (goog.object.get(values, 'flatCoordinates')); - goog.object.remove(values, 'flatCoordinates'); - var ends = /** @type {Array.<number>} */ (goog.object.get(values, 'ends')); - goog.object.remove(values, 'ends'); - var geometry = new ol.geom.MultiLineString(null); - geometry.setFlatCoordinates( - ol.geom.GeometryLayout.XYZM, flatCoordinates, ends); - ol.format.Feature.transformWithOptions(geometry, false, options); - var feature = new ol.Feature(geometry); - feature.setProperties(values); - return feature; +ol.format.GeoJSON.readPolygonGeometry_ = function(object) { + goog.asserts.assert(object.type == 'Polygon', + 'object.type should be Polygon'); + return new ol.geom.Polygon(object.coordinates); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private - * @return {ol.Feature|undefined} Waypoint. + * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry. */ -ol.format.GPX.readWpt_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'wpt'); - var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); - var values = ol.xml.pushParseAndPop( - {}, ol.format.GPX.WPT_PARSERS_, node, objectStack); - if (!goog.isDef(values)) { - return undefined; - } - var coordinates = ol.format.GPX.appendCoordinate_([], node, values); - var geometry = new ol.geom.Point( - coordinates, ol.geom.GeometryLayout.XYZM); - ol.format.Feature.transformWithOptions(geometry, false, options); - var feature = new ol.Feature(geometry); - feature.setProperties(values); - return feature; +ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) { + var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()]; + goog.asserts.assert(goog.isDef(geometryWriter), + 'geometryWriter should be defined'); + return geometryWriter(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options)), + opt_options); }; /** - * @const - * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>} + * @param {ol.geom.Geometry} geometry Geometry. * @private + * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection. */ -ol.format.GPX.FEATURE_READER_ = { - 'rte': ol.format.GPX.readRte_, - 'trk': ol.format.GPX.readTrk_, - 'wpt': ol.format.GPX.readWpt_ +ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) { + return /** @type {GeoJSONGeometryCollection} */ ({ + type: 'GeometryCollection', + geometries: [] + }); }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {GeoJSONGeometryCollection} GeoJSON geometry collection. */ -ol.format.GPX.GPX_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_), - 'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_), - 'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_) - }); +ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function( + geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection, + 'geometry should be an ol.geom.GeometryCollection'); + var geometries = goog.array.map( + geometry.getGeometriesArray(), function(geometry) { + return ol.format.GeoJSON.writeGeometry_(geometry, opt_options); + }); + return /** @type {GeoJSONGeometryCollection} */ ({ + type: 'GeometryCollection', + geometries: geometries + }); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GPX.LINK_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'text': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'), - 'type': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType') - }); +ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + return /** @type {GeoJSONGeometry} */ ({ + type: 'LineString', + coordinates: geometry.getCoordinates() + }); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GPX.RTE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'link': ol.format.GPX.parseLink_, - 'number': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), - 'extensions': ol.format.GPX.parseExtensions_, - 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'rtept': ol.format.GPX.parseRtePt_ - }); +ol.format.GeoJSON.writeMultiLineStringGeometry_ = + function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, + 'geometry should be an ol.geom.MultiLineString'); + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiLineString', + coordinates: geometry.getCoordinates() + }); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) - }); +ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint, + 'geometry should be an ol.geom.MultiPoint'); + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiPoint', + coordinates: geometry.getCoordinates() + }); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GPX.TRK_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'link': ol.format.GPX.parseLink_, - 'number': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), - 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'extensions': ol.format.GPX.parseExtensions_, - 'trkseg': ol.format.GPX.parseTrkSeg_ - }); +ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon, + 'geometry should be an ol.geom.MultiPolygon'); + var right; + if (goog.isDef(opt_options)) { + right = opt_options.rightHanded; + } + return /** @type {GeoJSONGeometry} */ ({ + type: 'MultiPolygon', + coordinates: geometry.getCoordinates(right) + }); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. * @private + * @return {GeoJSONGeometry} GeoJSON geometry. */ -ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'trkpt': ol.format.GPX.parseTrkPt_ - }); +ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + return /** @type {GeoJSONGeometry} */ ({ + type: 'Point', + coordinates: geometry.getCoordinates() + }); +}; + + +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @private + * @return {GeoJSONGeometry} GeoJSON geometry. + */ +ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, + 'geometry should be an ol.geom.Polygon'); + var right; + if (goog.isDef(opt_options)) { + right = opt_options.rightHanded; + } + return /** @type {GeoJSONGeometry} */ ({ + type: 'Polygon', + coordinates: geometry.getCoordinates(right) + }); +}; /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private + * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>} */ -ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) - }); +ol.format.GeoJSON.GEOMETRY_READERS_ = { + 'Point': ol.format.GeoJSON.readPointGeometry_, + 'LineString': ol.format.GeoJSON.readLineStringGeometry_, + 'Polygon': ol.format.GeoJSON.readPolygonGeometry_, + 'MultiPoint': ol.format.GeoJSON.readMultiPointGeometry_, + 'MultiLineString': ol.format.GeoJSON.readMultiLineStringGeometry_, + 'MultiPolygon': ol.format.GeoJSON.readMultiPolygonGeometry_, + 'GeometryCollection': ol.format.GeoJSON.readGeometryCollectionGeometry_ +}; /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private + * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>} */ -ol.format.GPX.WPT_PARSERS_ = ol.xml.makeParsersNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime), - 'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'link': ol.format.GPX.parseLink_, - 'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'sat': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger), - 'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'ageofdgpsdata': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'dgpsid': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), - 'extensions': ol.format.GPX.parseExtensions_ - }); +ol.format.GeoJSON.GEOMETRY_WRITERS_ = { + 'Point': ol.format.GeoJSON.writePointGeometry_, + 'LineString': ol.format.GeoJSON.writeLineStringGeometry_, + 'Polygon': ol.format.GeoJSON.writePolygonGeometry_, + 'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_, + 'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_, + 'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_, + 'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_, + 'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ +}; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.getExtensions = function() { + return ol.format.GeoJSON.EXTENSIONS_; +}; + + +/** + * Read a feature from a GeoJSON Feature source. Only works for Feature, + * use `readFeatures` to read FeatureCollection source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable + */ +ol.format.GeoJSON.prototype.readFeature; + + +/** + * Read all features from a GeoJSON source. Works with both Feature and + * FeatureCollection sources. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.GeoJSON.prototype.readFeatures; + + +/** + * @inheritDoc + */ +ol.format.GeoJSON.prototype.readFeatureFromObject = function( + object, opt_options) { + var geoJSONFeature = /** @type {GeoJSONFeature} */ (object); + goog.asserts.assert(geoJSONFeature.type == 'Feature', + 'geoJSONFeature.type should be Feature'); + var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry, + opt_options); + var feature = new ol.Feature(); + if (goog.isDef(this.geometryName_)) { + feature.setGeometryName(this.geometryName_); + } + feature.setGeometry(geometry); + if (goog.isDef(geoJSONFeature.id)) { + feature.setId(geoJSONFeature.id); + } + if (goog.isDef(geoJSONFeature.properties)) { + feature.setProperties(geoJSONFeature.properties); + } + return feature; +}; /** - * @param {Array.<ol.Feature>} features - * @private + * @inheritDoc */ -ol.format.GPX.prototype.handleReadExtensions_ = function(features) { - if (goog.isNull(features)) { - features = []; - } - for (var i = 0, ii = features.length; i < ii; ++i) { - var feature = features[i]; - if (goog.isDef(this.readExtensions_)) { - var extensionsNode = feature.get('extensionsNode_') || null; - this.readExtensions_(feature, extensionsNode); +ol.format.GeoJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + var geoJSONObject = /** @type {GeoJSONObject} */ (object); + if (geoJSONObject.type == 'Feature') { + return [this.readFeatureFromObject(object, opt_options)]; + } else if (geoJSONObject.type == 'FeatureCollection') { + var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */ + (object); + /** @type {Array.<ol.Feature>} */ + var features = []; + var geoJSONFeatures = geoJSONFeatureCollection.features; + var i, ii; + for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) { + features.push(this.readFeatureFromObject(geoJSONFeatures[i], + opt_options)); } - feature.set('extensionsNode_', undefined); + return features; + } else { + goog.asserts.fail('Unknown geoJSONObject.type: ' + geoJSONObject.type); + return []; } }; /** - * Read the first feature from a GPX source. + * Read a geometry from a GeoJSON source. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {Document|Node|Object|string} source Source. * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. + * @return {ol.geom.Geometry} Geometry. * @api stable */ -ol.format.GPX.prototype.readFeature; +ol.format.GeoJSON.prototype.readGeometry; /** * @inheritDoc */ -ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { - return null; - } - var featureReader = ol.format.GPX.FEATURE_READER_[node.localName]; - if (!goog.isDef(featureReader)) { - return null; - } - var feature = featureReader(node, [this.getReadOptions(node, opt_options)]); - if (!goog.isDef(feature)) { - return null; - } - this.handleReadExtensions_([feature]); - return feature; +ol.format.GeoJSON.prototype.readGeometryFromObject = function( + object, opt_options) { + return ol.format.GeoJSON.readGeometry_( + /** @type {GeoJSONGeometry} */ (object), opt_options); }; /** - * Read all features from a GPX source. + * Read the projection from a GeoJSON source. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. * @api stable */ -ol.format.GPX.prototype.readFeatures; +ol.format.GeoJSON.prototype.readProjection; /** * @inheritDoc */ -ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { - return []; - } - if (node.localName == 'gpx') { - var features = ol.xml.pushParseAndPop( - /** @type {Array.<ol.Feature>} */ ([]), ol.format.GPX.GPX_PARSERS_, - node, [this.getReadOptions(node, opt_options)]); - if (goog.isDef(features)) { - this.handleReadExtensions_(features); - return features; +ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) { + var geoJSONObject = /** @type {GeoJSONObject} */ (object); + var crs = geoJSONObject.crs; + if (goog.isDefAndNotNull(crs)) { + if (crs.type == 'name') { + return ol.proj.get(crs.properties.name); + } else if (crs.type == 'EPSG') { + // 'EPSG' is not part of the GeoJSON specification, but is generated by + // GeoServer. + // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996 + // is fixed and widely deployed. + return ol.proj.get('EPSG:' + crs.properties.code); } else { - return []; + goog.asserts.fail('Unknown crs.type: ' + crs.type); + return null; } + } else { + return this.defaultDataProjection; } - return []; }; /** - * Read the projection from a GPX source. + * Encode a feature as a GeoJSON Feature string. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.proj.Projection} Projection. + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. * @api stable */ -ol.format.GPX.prototype.readProjection; - - -/** - * @inheritDoc - */ -ol.format.GPX.prototype.readProjectionFromDocument = function(doc) { - return this.defaultDataProjection; -}; +ol.format.GeoJSON.prototype.writeFeature; /** - * @inheritDoc + * Encode a feature as a GeoJSON Feature object. + * + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} Object. + * @api stable */ -ol.format.GPX.prototype.readProjectionFromNode = function(node) { - return this.defaultDataProjection; +ol.format.GeoJSON.prototype.writeFeatureObject = function( + feature, opt_options) { + opt_options = this.adaptOptions(opt_options); + var object = { + 'type': 'Feature' + }; + var id = feature.getId(); + if (goog.isDefAndNotNull(id)) { + object['id'] = id; + } + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + object['geometry'] = + ol.format.GeoJSON.writeGeometry_(geometry, opt_options); + } else { + object['geometry'] = null; + } + var properties = feature.getProperties(); + goog.object.remove(properties, feature.getGeometryName()); + if (!goog.object.isEmpty(properties)) { + object['properties'] = properties; + } else { + object['properties'] = null; + } + return object; }; /** - * @param {Node} node Node. - * @param {string} value Value for the link's `href` attribute. - * @param {Array.<*>} objectStack Node stack. - * @private + * Encode an array of features as GeoJSON. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable */ -ol.format.GPX.writeLink_ = function(node, value, objectStack) { - node.setAttribute('href', value); - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var properties = goog.object.get(context, 'properties'); - var link = [ - goog.object.get(properties, 'linkText'), - goog.object.get(properties, 'linkType') - ]; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}), - ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, - link, objectStack, ol.format.GPX.LINK_SEQUENCE_); -}; +ol.format.GeoJSON.prototype.writeFeatures; /** - * @param {Node} node Node. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode an array of features as a GeoJSON object. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {Object} GeoJSON Object. + * @api stable */ -ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var parentNode = context.node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - var namespaceURI = parentNode.namespaceURI; - var properties = goog.object.get(context, 'properties'); - //FIXME Projection handling - ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]); - ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]); - var geometryLayout = goog.object.get(context, 'geometryLayout'); - /* jshint -W086 */ - switch (geometryLayout) { - case ol.geom.GeometryLayout.XYZM: - if (coordinate[3] !== 0) { - goog.object.set(properties, 'time', coordinate[3]); - } - case ol.geom.GeometryLayout.XYZ: - if (coordinate[2] !== 0) { - goog.object.set(properties, 'ele', coordinate[2]); - } - break; - case ol.geom.GeometryLayout.XYM: - if (coordinate[2] !== 0) { - goog.object.set(properties, 'time', coordinate[2]); - } +ol.format.GeoJSON.prototype.writeFeaturesObject = + function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var objects = []; + var i, ii; + for (i = 0, ii = features.length; i < ii; ++i) { + objects.push(this.writeFeatureObject(features[i], opt_options)); } - /* jshint +W086 */ - var orderedKeys = ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - ({node: node, 'properties': properties}), - ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, - values, objectStack, orderedKeys); + return /** @type {GeoJSONFeatureCollection} */ ({ + type: 'FeatureCollection', + features: objects + }); }; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode a geometry as a GeoJSON string. + * + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} GeoJSON. + * @api stable */ -ol.format.GPX.writeRte_ = function(node, feature, objectStack) { - var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); - var properties = feature.getProperties(); - var context = {node: node, 'properties': properties}; - var geometry = feature.getGeometry(); - if (goog.isDef(geometry)) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - geometry = /** @type {ol.geom.LineString} */ - (ol.format.Feature.transformWithOptions(geometry, true, options)); - goog.object.set(context, 'geometryLayout', geometry.getLayout()); - goog.object.set(properties, 'rtept', geometry.getCoordinates()); - } - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), - ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, - values, objectStack, orderedKeys); -}; +ol.format.GeoJSON.prototype.writeGeometry; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode a geometry as a GeoJSON object. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {GeoJSONGeometry|GeoJSONGeometryCollection} Object. + * @api stable */ -ol.format.GPX.writeTrk_ = function(node, feature, objectStack) { - var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); - var properties = feature.getProperties(); - var context = {node: node, 'properties': properties}; - var geometry = feature.getGeometry(); - if (goog.isDef(geometry)) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString); - geometry = /** @type {ol.geom.MultiLineString} */ - (ol.format.Feature.transformWithOptions(geometry, true, options)); - goog.object.set(properties, 'trkseg', geometry.getLineStrings()); - } - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), - ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, - values, objectStack, orderedKeys); +ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry, + opt_options) { + return ol.format.GeoJSON.writeGeometry_(geometry, + this.adaptOptions(opt_options)); }; +goog.provide('ol.format.XMLFeature'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.xml'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); +goog.require('ol.proj'); +goog.require('ol.xml'); -/** - * @param {Node} node Node. - * @param {ol.geom.LineString} lineString LineString. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) { - var context = {node: node, 'geometryLayout': lineString.getLayout(), - 'properties': {}}; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), - ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_, - lineString.getCoordinates(), objectStack); -}; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Object stack. - * @private + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for XML feature formats. + * + * @constructor + * @extends {ol.format.Feature} */ -ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { - var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - goog.object.set(context, 'properties', feature.getProperties()); - var geometry = feature.getGeometry(); - if (goog.isDef(geometry)) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - geometry = /** @type {ol.geom.Point} */ - (ol.format.Feature.transformWithOptions(geometry, true, options)); - goog.object.set(context, 'geometryLayout', geometry.getLayout()); - ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack); - } +ol.format.XMLFeature = function() { + goog.base(this); }; +goog.inherits(ol.format.XMLFeature, ol.format.Feature); /** - * @const - * @type {Array.<string>} - * @private + * @inheritDoc */ -ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type']; +ol.format.XMLFeature.prototype.getType = function() { + return ol.format.FormatType.XML; +}; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @inheritDoc */ -ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) - }); +ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readFeatureFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readFeatureFromDocument(doc, opt_options); + } else { + goog.asserts.fail('Unknown source type'); + return null; + } +}; /** - * @const - * @type {Object.<string, Array.<string>>} - * @private + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {ol.Feature} Feature. */ -ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, [ - 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept' - ]); +ol.format.XMLFeature.prototype.readFeatureFromDocument = function( + doc, opt_options) { + var features = this.readFeaturesFromDocument(doc, opt_options); + if (features.length > 0) { + return features[0]; + } else { + return null; + } +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {ol.Feature} Feature. */ -ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), - 'number': ol.xml.makeChildAppender( - ol.format.XSD.writeNonNegativeIntegerTextNode), - 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( - ol.format.GPX.writeWptType_)) - }); +ol.format.XMLFeature.prototype.readFeatureFromNode = goog.abstractMethod; /** - * @const - * @type {Object.<string, Array.<string>>} - * @private + * @inheritDoc */ -ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, [ - 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg' - ]); +ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readFeaturesFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readFeaturesFromDocument(doc, opt_options); + } else { + goog.asserts.fail('Unknown source type'); + return []; + } +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {Array.<ol.Feature>} Features. */ -ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), - 'number': ol.xml.makeChildAppender( - ol.format.XSD.writeNonNegativeIntegerTextNode), - 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( - ol.format.GPX.writeTrkSeg_)) - }); +ol.format.XMLFeature.prototype.readFeaturesFromDocument = function( + doc, opt_options) { + /** @type {Array.<ol.Feature>} */ + var features = []; + var n; + for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + goog.array.extend(features, this.readFeaturesFromNode(n, opt_options)); + } + } + return features; +}; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. - * @private + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {Array.<ol.Feature>} Features. */ -ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt'); +ol.format.XMLFeature.prototype.readFeaturesFromNode = goog.abstractMethod; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @inheritDoc */ -ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_) - }); +ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) { + if (ol.xml.isDocument(source)) { + return this.readGeometryFromDocument( + /** @type {Document} */ (source), opt_options); + } else if (ol.xml.isNode(source)) { + return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readGeometryFromDocument(doc, opt_options); + } else { + goog.asserts.fail('Unknown source type'); + return null; + } +}; /** - * @const - * @type {Object.<string, Array.<string>>} - * @private + * @param {Document} doc Document. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {ol.geom.Geometry} Geometry. */ -ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, [ - 'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src', - 'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop', - 'ageofdgpsdata', 'dgpsid' - ]); +ol.format.XMLFeature.prototype.readGeometryFromDocument = goog.abstractMethod; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @param {Node} node Node. + * @param {olx.format.ReadOptions=} opt_options Options. + * @protected + * @return {ol.geom.Geometry} Geometry. */ -ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode), - 'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'geoidheight': ol.xml.makeChildAppender( - ol.format.XSD.writeDecimalTextNode), - 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), - 'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'sat': ol.xml.makeChildAppender( - ol.format.XSD.writeNonNegativeIntegerTextNode), - 'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'ageofdgpsdata': ol.xml.makeChildAppender( - ol.format.XSD.writeDecimalTextNode), - 'dgpsid': ol.xml.makeChildAppender( - ol.format.XSD.writeNonNegativeIntegerTextNode) - }); +ol.format.XMLFeature.prototype.readGeometryFromNode = goog.abstractMethod; -/** - * @const - * @type {Object.<string, string>} - * @private +/** + * @inheritDoc */ -ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { - 'Point': 'wpt', - 'LineString': 'rte', - 'MultiLineString': 'trk' +ol.format.XMLFeature.prototype.readProjection = function(source) { + if (ol.xml.isDocument(source)) { + return this.readProjectionFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readProjectionFromNode(/** @type {Node} */ (source)); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readProjectionFromDocument(doc); + } else { + goog.asserts.fail('Unknown source type'); + return null; + } }; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. - * @private + * @param {Document} doc Document. + * @protected + * @return {ol.proj.Projection} Projection. */ -ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { - goog.asserts.assertInstanceof(value, ol.Feature); - var geometry = value.getGeometry(); - if (goog.isDef(geometry)) { - var parentNode = objectStack[objectStack.length - 1].node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - return ol.xml.createElementNS(parentNode.namespaceURI, - ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]); - } +ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) { + return this.defaultDataProjection; }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @param {Node} node Node. + * @protected + * @return {ol.proj.Projection} Projection. */ -ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.GPX.NAMESPACE_URIS_, { - 'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), - 'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), - 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) - }); +ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) { + return this.defaultDataProjection; +}; /** - * Encode an array of features in the GPX format. - * - * @function - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} Result. - * @api stable + * @inheritDoc */ -ol.format.GPX.prototype.writeFeatures; +ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) { + var node = this.writeFeatureNode(feature, opt_options); + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return goog.dom.xml.serialize(/** @type {Element} */(node)); +}; /** - * Encode an array of features in the GPX format as an XML node. - * - * @param {Array.<ol.Feature>} features Features. + * @param {ol.Feature} feature Feature. * @param {olx.format.WriteOptions=} opt_options Options. + * @protected * @return {Node} Node. - * @api */ -ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) { - opt_options = this.adaptOptions(opt_options); - //FIXME Serialize metadata - var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx'); +ol.format.XMLFeature.prototype.writeFeatureNode = goog.abstractMethod; - ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ - ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_, - ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]); - return gpx; + +/** + * @inheritDoc + */ +ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) { + var node = this.writeFeaturesNode(features, opt_options); + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return goog.dom.xml.serialize(/** @type {Element} */(node)); }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Utilities for string newlines. - * @author nnaze@google.com (Nathan Naze) + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. */ +ol.format.XMLFeature.prototype.writeFeaturesNode = goog.abstractMethod; /** - * Namespace for string utilities + * @inheritDoc */ -goog.provide('goog.string.newlines'); -goog.provide('goog.string.newlines.Line'); - -goog.require('goog.array'); +ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) { + var node = this.writeGeometryNode(geometry, opt_options); + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return goog.dom.xml.serialize(/** @type {Element} */(node)); +}; /** - * Splits a string into lines, properly handling universal newlines. - * @param {string} str String to split. - * @param {boolean=} opt_keepNewlines Whether to keep the newlines in the - * resulting strings. Defaults to false. - * @return {!Array<string>} String split into lines. + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. */ -goog.string.newlines.splitLines = function(str, opt_keepNewlines) { - var lines = goog.string.newlines.getLines(str); - return goog.array.map(lines, function(line) { - return opt_keepNewlines ? line.getFullLine() : line.getContent(); - }); -}; +ol.format.XMLFeature.prototype.writeGeometryNode = goog.abstractMethod; + +// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part +// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect +// envelopes/extents, only geometries! +goog.provide('ol.format.GMLBase'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj'); +goog.require('ol.xml'); /** - * Line metadata class that records the start/end indicies of lines - * in a string. Can be used to implement common newline use cases such as - * splitLines() or determining line/column of an index in a string. - * Also implements methods to get line contents. - * - * Indexes are expressed as string indicies into string.substring(), inclusive - * at the start, exclusive at the end. + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Feature base format for reading and writing data in the GML format. + * This class cannot be instantiated, it contains only base content that + * is shared with versioned format classes ol.format.GML2 and + * ol.format.GML3. * - * Create an array of these with goog.string.newlines.getLines(). - * @param {string} string The original string. - * @param {number} startLineIndex The index of the start of the line. - * @param {number} endContentIndex The index of the end of the line, excluding - * newlines. - * @param {number} endLineIndex The index of the end of the line, index - * newlines. * @constructor - * @struct - * @final + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.XMLFeature} */ -goog.string.newlines.Line = function(string, startLineIndex, - endContentIndex, endLineIndex) { +ol.format.GMLBase = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (goog.isDef(opt_options) ? opt_options : {}); + /** - * The original string. - * @type {string} + * @protected + * @type {Array.<string>|string|undefined} */ - this.string = string; + this.featureType = options.featureType; /** - * Index of the start of the line. - * @type {number} + * @protected + * @type {Object.<string, string>|string|undefined} */ - this.startLineIndex = startLineIndex; + this.featureNS = options.featureNS; /** - * Index of the end of the line, excluding any newline characters. - * Index is the first character after the line, suitable for - * String.substring(). - * @type {number} + * @protected + * @type {string} */ - this.endContentIndex = endContentIndex; + this.srsName = options.srsName; /** - * Index of the end of the line, excluding any newline characters. - * Index is the first character after the line, suitable for - * String.substring(). - * @type {number} + * @protected + * @type {string} */ + this.schemaLocation = ''; - this.endLineIndex = endLineIndex; + /** + * @type {Object.<string, Object.<string, Object>>} + */ + this.FEATURE_COLLECTION_PARSERS = {}; + this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS] = { + 'featureMember': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFeaturesInternal), + 'featureMembers': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFeaturesInternal) + }; + + goog.base(this); }; +goog.inherits(ol.format.GMLBase, ol.format.XMLFeature); /** - * @return {string} The content of the line, excluding any newline characters. + * @const + * @type {string} */ -goog.string.newlines.Line.prototype.getContent = function() { - return this.string.substring(this.startLineIndex, this.endContentIndex); -}; +ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml'; /** - * @return {string} The full line, including any newline characters. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<ol.Feature>} Features. */ -goog.string.newlines.Line.prototype.getFullLine = function() { - return this.string.substring(this.startLineIndex, this.endLineIndex); +ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + var localName = ol.xml.getLocalName(node); + var features; + if (localName == 'FeatureCollection') { + if (node.namespaceURI === 'http://www.opengis.net/wfs') { + features = ol.xml.pushParseAndPop([], + this.FEATURE_COLLECTION_PARSERS, node, + objectStack, this); + } else { + features = ol.xml.pushParseAndPop(null, + this.FEATURE_COLLECTION_PARSERS, node, + objectStack, this); + } + } else if (localName == 'featureMembers' || localName == 'featureMember') { + var context = objectStack[0]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var i, ii, prefix = 'p', defaultPrefix = 'p0'; + if (!goog.isDef(featureType) && goog.isDefAndNotNull(node.childNodes)) { + featureType = [], featureNS = {}; + for (i = 0, ii = node.childNodes.length; i < ii; ++i) { + var child = node.childNodes[i]; + if (child.nodeType === 1) { + var ft = child.nodeName.split(':').pop(); + if (goog.array.indexOf(featureType, ft) === -1) { + var key; + if (!goog.object.contains(featureNS, child.namespaceURI)) { + key = prefix + goog.object.getCount(featureNS); + featureNS[key] = child.namespaceURI; + } else { + key = goog.object.findKey(featureNS, function(value) { + return value === child.namespaceURI; + }); + } + featureType.push(key + ':' + ft); + } + } + } + context['featureType'] = featureType; + context['featureNS'] = featureNS; + } + if (goog.isString(featureNS)) { + var ns = featureNS; + featureNS = {}; + featureNS[defaultPrefix] = ns; + } + var parsersNS = {}; + var featureTypes = goog.isArray(featureType) ? featureType : [featureType]; + for (var p in featureNS) { + var parsers = {}; + for (i = 0, ii = featureTypes.length; i < ii; ++i) { + var featurePrefix = featureTypes[i].indexOf(':') === -1 ? + defaultPrefix : featureTypes[i].split(':')[0]; + if (featurePrefix === p) { + parsers[featureTypes[i].split(':').pop()] = + (localName == 'featureMembers') ? + ol.xml.makeArrayPusher(this.readFeatureElement, this) : + ol.xml.makeReplacer(this.readFeatureElement, this); + } + } + parsersNS[featureNS[p]] = parsers; + } + features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack); + } + if (!goog.isDef(features)) { + features = []; + } + return features; }; /** - * @return {string} The newline characters, if any ('\n', \r', '\r\n', '', etc). + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Geometry|undefined} Geometry. */ -goog.string.newlines.Line.prototype.getNewline = function() { - return this.string.substring(this.endContentIndex, this.endLineIndex); +ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) { + var context = objectStack[0]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + context['srsName'] = node.firstElementChild.getAttribute('srsName'); + var geometry = ol.xml.pushParseAndPop(/** @type {ol.geom.Geometry} */(null), + this.GEOMETRY_PARSERS_, node, objectStack, this); + if (goog.isDefAndNotNull(geometry)) { + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, context)); + } else { + return undefined; + } }; /** - * Splits a string into an array of line metadata. - * @param {string} str String to split. - * @return {!Array<!goog.string.newlines.Line>} Array of line metadata. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.Feature} Feature. */ -goog.string.newlines.getLines = function(str) { - // We use the constructor because literals are evaluated only once in - // < ES 3.1. - // See http://www.mail-archive.com/es-discuss@mozilla.org/msg01796.html - var re = RegExp('\r\n|\r|\n', 'g'); - var sliceIndex = 0; - var result; - var lines = []; +ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) { + var n; + var fid = node.getAttribute('fid') || + ol.xml.getAttributeNS(node, ol.format.GMLBase.GMLNS, 'id'); + var values = {}, geometryName; + for (n = node.firstElementChild; !goog.isNull(n); + n = n.nextElementSibling) { + var localName = ol.xml.getLocalName(n); + // Assume attribute elements have one child node and that the child + // is a text or CDATA node (to be treated as text). + // Otherwise assume it is a geometry node. + if (n.childNodes.length === 0 || + (n.childNodes.length === 1 && + (n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4))) { + var value = ol.xml.getAllTextContent(n, false); + if (goog.string.isEmpty(value)) { + value = undefined; + } + values[localName] = value; + } else { + // boundedBy is an extent and must not be considered as a geometry + if (localName !== 'boundedBy') { + geometryName = localName; + } + values[localName] = this.readGeometryElement(n, objectStack); + } + } + var feature = new ol.Feature(values); + if (goog.isDef(geometryName)) { + feature.setGeometryName(geometryName); + } + if (fid) { + feature.setId(fid); + } + return feature; +}; - while (result = re.exec(str)) { - var line = new goog.string.newlines.Line( - str, sliceIndex, result.index, result.index + result[0].length); - lines.push(line); - // remember where to start the slice from - sliceIndex = re.lastIndex; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Point|undefined} Point. + */ +ol.format.GMLBase.prototype.readPoint = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Point', 'localName should be Point'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (goog.isDefAndNotNull(flatCoordinates)) { + var point = new ol.geom.Point(null); + goog.asserts.assert(flatCoordinates.length == 3, + 'flatCoordinates should have a length of 3'); + point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return point; } +}; - // If the string does not end with a newline, add the last line. - if (sliceIndex < str.length) { - var line = new goog.string.newlines.Line( - str, sliceIndex, str.length, str.length); - lines.push(line); + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiPoint|undefined} MultiPoint. + */ +ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MultiPoint', + 'localName should be MultiPoint'); + var coordinates = ol.xml.pushParseAndPop( + /** @type {Array.<Array.<number>>} */ ([]), + this.MULTIPOINT_PARSERS_, node, objectStack, this); + if (goog.isDef(coordinates)) { + return new ol.geom.MultiPoint(coordinates); + } else { + return undefined; } +}; - return lines; + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MultiLineString', + 'localName should be MultiLineString'); + var lineStrings = ol.xml.pushParseAndPop( + /** @type {Array.<ol.geom.LineString>} */ ([]), + this.MULTILINESTRING_PARSERS_, node, objectStack, this); + if (goog.isDef(lineStrings)) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; + } else { + return undefined; + } }; -goog.provide('ol.format.TextFeature'); -goog.require('goog.asserts'); -goog.require('ol.format.Feature'); -goog.require('ol.format.FormatType'); +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. + */ +ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MultiPolygon', + 'localName should be MultiPolygon'); + var polygons = ol.xml.pushParseAndPop( + /** @type {Array.<ol.geom.Polygon>} */ ([]), + this.MULTIPOLYGON_PARSERS_, node, objectStack, this); + if (goog.isDef(polygons)) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(polygons); + return multiPolygon; + } else { + return undefined; + } +}; + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'pointMember' || + node.localName == 'pointMembers', + 'localName should be pointMember or pointMembers'); + ol.xml.parseNode(this.POINTMEMBER_PARSERS_, + node, objectStack, this); +}; /** - * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for text feature formats. - * - * @constructor - * @extends {ol.format.Feature} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.TextFeature = function() { - goog.base(this); +ol.format.GMLBase.prototype.lineStringMemberParser_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'lineStringMember' || + node.localName == 'lineStringMembers', + 'localName should be LineStringMember or LineStringMembers'); + ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_, + node, objectStack, this); }; -goog.inherits(ol.format.TextFeature, ol.format.Feature); /** - * @param {Document|Node|Object|string} source Source. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private - * @return {string} Text. */ -ol.format.TextFeature.prototype.getText_ = function(source) { - if (goog.isString(source)) { - return source; +ol.format.GMLBase.prototype.polygonMemberParser_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'polygonMember' || + node.localName == 'polygonMembers', + 'localName should be polygonMember or polygonMembers'); + ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node, + objectStack, this); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.GMLBase.prototype.readLineString = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LineString', + 'localName should be LineString'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (goog.isDefAndNotNull(flatCoordinates)) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return lineString; } else { - goog.asserts.fail(); - return ''; + return undefined; } }; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} LinearRing flat coordinates. */ -ol.format.TextFeature.prototype.getType = function() { - return ol.format.FormatType.TEXT; +ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var ring = ol.xml.pushParseAndPop(/** @type {Array.<number>} */(null), + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, + objectStack, this); + if (goog.isDefAndNotNull(ring)) { + return ring; + } else { + return undefined; + } }; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.LinearRing|undefined} LinearRing. */ -ol.format.TextFeature.prototype.readFeature = function(source, opt_options) { - return this.readFeatureFromText( - this.getText_(source), this.adaptOptions(opt_options)); +ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var flatCoordinates = + this.readFlatCoordinatesFromNode_(node, objectStack); + if (goog.isDef(flatCoordinates)) { + var ring = new ol.geom.LinearRing(null); + ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return ring; + } else { + return undefined; + } }; /** - * @param {string} text Text. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @protected - * @return {ol.Feature} Feature. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.geom.Polygon|undefined} Polygon. */ -ol.format.TextFeature.prototype.readFeatureFromText = goog.abstractMethod; +ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Polygon', + 'localName should be Polygon'); + var flatLinearRings = ol.xml.pushParseAndPop( + /** @type {Array.<Array.<number>>} */ ([null]), + this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); + if (goog.isDef(flatLinearRings) && + !goog.isNull(flatLinearRings[0])) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + goog.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + return polygon; + } else { + return undefined; + } +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} Flat coordinates. */ -ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) { - return this.readFeaturesFromText( - this.getText_(source), this.adaptOptions(opt_options)); +ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop( + null, + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, + objectStack, this)); }; /** - * @param {string} text Text. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @protected - * @return {Array.<ol.Feature>} Features. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.TextFeature.prototype.readFeaturesFromText = goog.abstractMethod; +ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'pointMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.pointMemberParser_), + 'pointMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.pointMemberParser_) + } +}); /** - * @inheritDoc + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) { - return this.readGeometryFromText( - this.getText_(source), this.adaptOptions(opt_options)); -}; +ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'lineStringMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.lineStringMemberParser_), + 'lineStringMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.lineStringMemberParser_) + } +}); /** - * @param {string} text Text. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @protected - * @return {ol.geom.Geometry} Geometry. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.TextFeature.prototype.readGeometryFromText = goog.abstractMethod; +ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'polygonMember': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.polygonMemberParser_), + 'polygonMembers': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.polygonMemberParser_) + } +}); /** - * @inheritDoc + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.TextFeature.prototype.readProjection = function(source) { - return this.readProjectionFromText(this.getText_(source)); -}; +ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_) + } +}); /** - * @param {string} text Text. - * @protected - * @return {ol.proj.Projection} Projection. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.TextFeature.prototype.readProjectionFromText = goog.abstractMethod; +ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'LineString': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readLineString) + } +}); /** - * @inheritDoc + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) { - return this.writeFeatureText(feature, this.adaptOptions(opt_options)); -}; +ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'Polygon': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readPolygon) + } +}); /** - * @param {ol.Feature} feature Features. - * @param {olx.format.WriteOptions=} opt_options Write options. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @protected - * @return {string} Text. */ -ol.format.TextFeature.prototype.writeFeatureText = goog.abstractMethod; +ol.format.GMLBase.prototype.RING_PARSERS = Object({ + 'http://www.opengis.net/gml' : { + 'LinearRing': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readFlatLinearRing_) + } +}); /** * @inheritDoc */ -ol.format.TextFeature.prototype.writeFeatures = function( - features, opt_options) { - return this.writeFeaturesText(features, this.adaptOptions(opt_options)); +ol.format.GMLBase.prototype.readGeometryFromNode = + function(node, opt_options) { + var geometry = this.readGeometryElement(node, + [this.getReadOptions(node, goog.isDef(opt_options) ? opt_options : {})]); + return goog.isDef(geometry) ? geometry : null; }; /** - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @protected - * @return {string} Text. + * Read all features from a GML FeatureCollection. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. + * @return {Array.<ol.Feature>} Features. + * @api stable */ -ol.format.TextFeature.prototype.writeFeaturesText = goog.abstractMethod; +ol.format.GMLBase.prototype.readFeatures; /** * @inheritDoc */ -ol.format.TextFeature.prototype.writeGeometry = function( - geometry, opt_options) { - return this.writeGeometryText(geometry, this.adaptOptions(opt_options)); +ol.format.GMLBase.prototype.readFeaturesFromNode = + function(node, opt_options) { + var options = { + featureType: this.featureType, + featureNS: this.featureNS + }; + if (goog.isDef(opt_options)) { + goog.object.extend(options, this.getReadOptions(node, opt_options)); + } + return this.readFeaturesInternal(node, [options]); }; /** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @protected - * @return {string} Text. + * @inheritDoc */ -ol.format.TextFeature.prototype.writeGeometryText = goog.abstractMethod; +ol.format.GMLBase.prototype.readProjectionFromNode = function(node) { + return ol.proj.get(goog.isDef(this.srsName_) ? this.srsName_ : + node.firstElementChild.getAttribute('srsName')); +}; -goog.provide('ol.format.IGC'); -goog.provide('ol.format.IGCZ'); +goog.provide('ol.format.XSD'); goog.require('goog.asserts'); goog.require('goog.string'); -goog.require('goog.string.newlines'); -goog.require('ol.Feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.TextFeature'); -goog.require('ol.geom.LineString'); -goog.require('ol.proj'); +goog.require('ol.xml'); /** - * IGC altitude/z. One of 'barometric', 'gps', 'none'. - * @enum {string} - * @api + * @const + * @type {string} */ -ol.format.IGCZ = { - BAROMETRIC: 'barometric', - GPS: 'gps', - NONE: 'none' -}; - +ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema'; /** - * @classdesc - * Feature format for `*.igc` flight recording files. - * - * @constructor - * @extends {ol.format.TextFeature} - * @param {olx.format.IGCOptions=} opt_options Options. - * @api + * @param {Node} node Node. + * @return {boolean|undefined} Boolean. */ -ol.format.IGC = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this); - - /** - * @inheritDoc - */ - this.defaultDataProjection = ol.proj.get('EPSG:4326'); +ol.format.XSD.readBoolean = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readBooleanString(s); +}; - /** - * @private - * @type {ol.format.IGCZ} - */ - this.altitudeMode_ = goog.isDef(options.altitudeMode) ? - options.altitudeMode : ol.format.IGCZ.NONE; +/** + * @param {string} string String. + * @return {boolean|undefined} Boolean. + */ +ol.format.XSD.readBooleanString = function(string) { + var m = /^\s*(true|1)|(false|0)\s*$/.exec(string); + if (m) { + return goog.isDef(m[1]) || false; + } else { + return undefined; + } }; -goog.inherits(ol.format.IGC, ol.format.TextFeature); /** - * @const - * @type {Array.<string>} - * @private + * @param {Node} node Node. + * @return {number|undefined} DateTime in seconds. */ -ol.format.IGC.EXTENSIONS_ = ['.igc']; +ol.format.XSD.readDateTime = function(node) { + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/; + var m = re.exec(s); + if (m) { + var year = parseInt(m[1], 10); + var month = parseInt(m[2], 10) - 1; + var day = parseInt(m[3], 10); + var hour = parseInt(m[4], 10); + var minute = parseInt(m[5], 10); + var second = parseInt(m[6], 10); + var dateTime = Date.UTC(year, month, day, hour, minute, second) / 1000; + if (m[7] != 'Z') { + var sign = m[8] == '-' ? -1 : 1; + dateTime += sign * 60 * parseInt(m[9], 10); + if (goog.isDef(m[10])) { + dateTime += sign * 60 * 60 * parseInt(m[10], 10); + } + } + return dateTime; + } else { + return undefined; + } +}; /** - * @const - * @type {RegExp} - * @private + * @param {Node} node Node. + * @return {number|undefined} Decimal. */ -ol.format.IGC.B_RECORD_RE_ = - /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/; +ol.format.XSD.readDecimal = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readDecimalString(s); +}; /** - * @const - * @type {RegExp} - * @private + * @param {string} string String. + * @return {number|undefined} Decimal. */ -ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/; +ol.format.XSD.readDecimalString = function(string) { + // FIXME check spec + var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string); + if (m) { + return parseFloat(m[1]); + } else { + return undefined; + } +}; /** - * @const - * @type {RegExp} - * @private + * @param {Node} node Node. + * @return {number|undefined} Non negative integer. */ -ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/; +ol.format.XSD.readNonNegativeInteger = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return ol.format.XSD.readNonNegativeIntegerString(s); +}; /** - * @inheritDoc + * @param {string} string String. + * @return {number|undefined} Non negative integer. */ -ol.format.IGC.prototype.getExtensions = function() { - return ol.format.IGC.EXTENSIONS_; +ol.format.XSD.readNonNegativeIntegerString = function(string) { + var m = /^\s*(\d+)\s*$/.exec(string); + if (m) { + return parseInt(m[1], 10); + } else { + return undefined; + } }; /** - * Read the feature from the IGC source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. - * @api + * @param {Node} node Node. + * @return {string|undefined} String. */ -ol.format.IGC.prototype.readFeature; +ol.format.XSD.readString = function(node) { + var s = ol.xml.getAllTextContent(node, false); + return goog.string.trim(s); +}; /** - * @inheritDoc + * @param {Node} node Node to append a TextNode with the boolean to. + * @param {boolean} bool Boolean. */ -ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) { - var altitudeMode = this.altitudeMode_; - var lines = goog.string.newlines.splitLines(text); - /** @type {Object.<string, string>} */ - var properties = {}; - var flatCoordinates = []; - var year = 2000; - var month = 0; - var day = 1; - var i, ii; - for (i = 0, ii = lines.length; i < ii; ++i) { - var line = lines[i]; - var m; - if (line.charAt(0) == 'B') { - m = ol.format.IGC.B_RECORD_RE_.exec(line); - if (m) { - var hour = parseInt(m[1], 10); - var minute = parseInt(m[2], 10); - var second = parseInt(m[3], 10); - var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000; - if (m[6] == 'S') { - y = -y; - } - var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000; - if (m[9] == 'W') { - x = -x; - } - flatCoordinates.push(x, y); - if (altitudeMode != ol.format.IGCZ.NONE) { - var z; - if (altitudeMode == ol.format.IGCZ.GPS) { - z = parseInt(m[11], 10); - } else if (altitudeMode == ol.format.IGCZ.BAROMETRIC) { - z = parseInt(m[12], 10); - } else { - goog.asserts.fail(); - z = 0; - } - flatCoordinates.push(z); - } - var dateTime = Date.UTC(year, month, day, hour, minute, second); - flatCoordinates.push(dateTime / 1000); - } - } else if (line.charAt(0) == 'H') { - m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line); - if (m) { - day = parseInt(m[1], 10); - month = parseInt(m[2], 10) - 1; - year = 2000 + parseInt(m[3], 10); - } else { - m = ol.format.IGC.H_RECORD_RE_.exec(line); - if (m) { - properties[m[1]] = goog.string.trim(m[2]); - m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line); - } - } - } - } - if (flatCoordinates.length === 0) { - return null; - } - var lineString = new ol.geom.LineString(null); - var layout = altitudeMode == ol.format.IGCZ.NONE ? - ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM; - lineString.setFlatCoordinates(layout, flatCoordinates); - var feature = new ol.Feature(ol.format.Feature.transformWithOptions( - lineString, false, opt_options)); - feature.setProperties(properties); - return feature; +ol.format.XSD.writeBooleanTextNode = function(node, bool) { + ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0'); }; /** - * Read the feature from the source. As IGC sources contain a single - * feature, this will return the feature in an array. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. - * @api + * @param {Node} node Node to append a TextNode with the dateTime to. + * @param {number} dateTime DateTime in seconds. */ -ol.format.IGC.prototype.readFeatures; +ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) { + var date = new Date(dateTime * 1000); + var string = date.getUTCFullYear() + '-' + + goog.string.padNumber(date.getUTCMonth() + 1, 2) + '-' + + goog.string.padNumber(date.getUTCDate(), 2) + 'T' + + goog.string.padNumber(date.getUTCHours(), 2) + ':' + + goog.string.padNumber(date.getUTCMinutes(), 2) + ':' + + goog.string.padNumber(date.getUTCSeconds(), 2) + 'Z'; + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; /** - * @inheritDoc + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} decimal Decimal. */ -ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) { - var feature = this.readFeatureFromText(text, opt_options); - if (!goog.isNull(feature)) { - return [feature]; - } else { - return []; - } +ol.format.XSD.writeDecimalTextNode = function(node, decimal) { + var string = decimal.toPrecision(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); }; /** - * Read the projection from the IGC source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.proj.Projection} Projection. - * @api + * @param {Node} node Node to append a TextNode with the decimal to. + * @param {number} nonNegativeInteger Non negative integer. */ -ol.format.IGC.prototype.readProjection; +ol.format.XSD.writeNonNegativeIntegerTextNode = + function(node, nonNegativeInteger) { + goog.asserts.assert(nonNegativeInteger >= 0, 'value should be more than 0'); + goog.asserts.assert(nonNegativeInteger == (nonNegativeInteger | 0), + 'value should be an integer value'); + var string = nonNegativeInteger.toString(); + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); +}; /** - * @inheritDoc + * @param {Node} node Node to append a TextNode with the string to. + * @param {string} string String. */ -ol.format.IGC.prototype.readProjectionFromText = function(text) { - return this.defaultDataProjection; +ol.format.XSD.writeStringTextNode = function(node, string) { + node.appendChild(ol.xml.DOCUMENT.createTextNode(string)); }; -goog.provide('ol.style.Text'); +goog.provide('ol.format.GML'); +goog.provide('ol.format.GML3'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.extent'); +goog.require('ol.format.Feature'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj'); +goog.require('ol.xml'); /** * @classdesc - * Set text style for vector features. + * Feature format for reading and writing data in the GML format + * version 3.1.1. + * Currently only supports GML 3.1.1 Simple Features profile. * * @constructor - * @param {olx.style.TextOptions=} opt_options Options. + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.GMLBase} * @api */ -ol.style.Text = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @private - * @type {string|undefined} - */ - this.font_ = options.font; - - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = options.rotation; - - /** - * @private - * @type {number|undefined} - */ - this.scale_ = options.scale; - - /** - * @private - * @type {string|undefined} - */ - this.text_ = options.text; +ol.format.GML3 = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (goog.isDef(opt_options) ? opt_options : {}); - /** - * @private - * @type {string|undefined} - */ - this.textAlign_ = options.textAlign; + goog.base(this, options); /** * @private - * @type {string|undefined} + * @type {boolean} */ - this.textBaseline_ = options.textBaseline; + this.surface_ = goog.isDef(options.surface) ? + options.surface : false; /** * @private - * @type {ol.style.Fill} + * @type {boolean} */ - this.fill_ = goog.isDef(options.fill) ? options.fill : null; + this.curve_ = goog.isDef(options.curve) ? + options.curve : false; /** * @private - * @type {ol.style.Stroke} + * @type {boolean} */ - this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; + this.multiCurve_ = goog.isDef(options.multiCurve) ? + options.multiCurve : true; /** * @private - * @type {number} + * @type {boolean} */ - this.offsetX_ = goog.isDef(options.offsetX) ? options.offsetX : 0; + this.multiSurface_ = goog.isDef(options.multiSurface) ? + options.multiSurface : true; /** - * @private - * @type {number} + * @inheritDoc */ - this.offsetY_ = goog.isDef(options.offsetY) ? options.offsetY : 0; -}; - - -/** - * @return {string|undefined} Font. - * @api - */ -ol.style.Text.prototype.getFont = function() { - return this.font_; -}; - - -/** - * @return {number} Horizontal text offset. - * @api - */ -ol.style.Text.prototype.getOffsetX = function() { - return this.offsetX_; -}; - - -/** - * @return {number} Vertical text offset. - * @api - */ -ol.style.Text.prototype.getOffsetY = function() { - return this.offsetY_; -}; - - -/** - * @return {ol.style.Fill} Fill style. - * @api - */ -ol.style.Text.prototype.getFill = function() { - return this.fill_; -}; - + this.schemaLocation = goog.isDef(options.schemaLocation) ? + options.schemaLocation : ol.format.GML3.schemaLocation_; -/** - * @return {number|undefined} Rotation. - * @api - */ -ol.style.Text.prototype.getRotation = function() { - return this.rotation_; }; +goog.inherits(ol.format.GML3, ol.format.GMLBase); /** - * @return {number|undefined} Scale. - * @api + * @const + * @type {string} + * @private */ -ol.style.Text.prototype.getScale = function() { - return this.scale_; -}; +ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS + + ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' + + '1.0.0/gmlsf.xsd'; /** - * @return {ol.style.Stroke} Stroke style. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. */ -ol.style.Text.prototype.getStroke = function() { - return this.stroke_; +ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MultiCurve', + 'localName should be MultiCurve'); + var lineStrings = ol.xml.pushParseAndPop( + /** @type {Array.<ol.geom.LineString>} */ ([]), + this.MULTICURVE_PARSERS_, node, objectStack, this); + if (goog.isDef(lineStrings)) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; + } else { + return undefined; + } }; /** - * @return {string|undefined} Text. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiPolygon|undefined} MultiPolygon. */ -ol.style.Text.prototype.getText = function() { - return this.text_; +ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MultiSurface', + 'localName should be MultiSurface'); + var polygons = ol.xml.pushParseAndPop( + /** @type {Array.<ol.geom.Polygon>} */ ([]), + this.MULTISURFACE_PARSERS_, node, objectStack, this); + if (goog.isDef(polygons)) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(polygons); + return multiPolygon; + } else { + return undefined; + } }; /** - * @return {string|undefined} Text align. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.style.Text.prototype.getTextAlign = function() { - return this.textAlign_; +ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'curveMember' || + node.localName == 'curveMembers', + 'localName should be curveMember or curveMembers'); + ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this); }; /** - * @return {string|undefined} Text baseline. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.style.Text.prototype.getTextBaseline = function() { - return this.textBaseline_; +ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'surfaceMember' || + node.localName == 'surfaceMembers', + 'localName should be surfaceMember or surfaceMembers'); + ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_, + node, objectStack, this); }; /** - * Set the font. - * - * @param {string|undefined} font Font. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<(Array.<number>)>|undefined} flat coordinates. */ -ol.style.Text.prototype.setFont = function(font) { - this.font_ = font; +ol.format.GML3.prototype.readPatch_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'patches', + 'localName should be patches'); + return ol.xml.pushParseAndPop( + /** @type {Array.<Array.<number>>} */ ([null]), + this.PATCHES_PARSERS_, node, objectStack, this); }; /** - * Set the x offset. - * - * @param {number} offsetX Horizontal text offset. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} flat coordinates. */ -ol.style.Text.prototype.setOffsetX = function(offsetX) { - this.offsetX_ = offsetX; +ol.format.GML3.prototype.readSegment_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'segments', + 'localName should be segments'); + return ol.xml.pushParseAndPop( + /** @type {Array.<number>} */ ([null]), + this.SEGMENTS_PARSERS_, node, objectStack, this); }; /** - * Set the y offset. - * - * @param {number} offsetY Vertical text offset. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<(Array.<number>)>|undefined} flat coordinates. */ -ol.style.Text.prototype.setOffsetY = function(offsetY) { - this.offsetY_ = offsetY; +ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'npde.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'PolygonPatch', + 'localName should be PolygonPatch'); + return ol.xml.pushParseAndPop( + /** @type {Array.<Array.<number>>} */ ([null]), + this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this); }; /** - * Set the fill. - * - * @param {ol.style.Fill} fill Fill style. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} flat coordinates. */ -ol.style.Text.prototype.setFill = function(fill) { - this.fill_ = fill; +ol.format.GML3.prototype.readLineStringSegment_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LineStringSegment', + 'localName should be LineStringSegment'); + return ol.xml.pushParseAndPop( + /** @type {Array.<number>} */ ([null]), + this.GEOMETRY_FLAT_COORDINATES_PARSERS_, + node, objectStack, this); }; /** - * Set the rotation. - * - * @param {number|undefined} rotation Rotation. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.style.Text.prototype.setRotation = function(rotation) { - this.rotation_ = rotation; +ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'interior', + 'localName should be interior'); + var flatLinearRing = ol.xml.pushParseAndPop( + /** @type {Array.<number>|undefined} */ (undefined), + this.RING_PARSERS, node, objectStack, this); + if (goog.isDef(flatLinearRing)) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + goog.asserts.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length of 1 or more'); + flatLinearRings.push(flatLinearRing); + } }; /** - * Set the scale. - * - * @param {number|undefined} scale Scale. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.style.Text.prototype.setScale = function(scale) { - this.scale_ = scale; +ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'exterior', + 'localName should be exterior'); + var flatLinearRing = ol.xml.pushParseAndPop( + /** @type {Array.<number>|undefined} */ (undefined), + this.RING_PARSERS, node, objectStack, this); + if (goog.isDef(flatLinearRing)) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + goog.asserts.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length of 1 or more'); + flatLinearRings[0] = flatLinearRing; + } }; /** - * Set the stroke. - * - * @param {ol.style.Stroke} stroke Stroke style. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. */ -ol.style.Text.prototype.setStroke = function(stroke) { - this.stroke_ = stroke; +ol.format.GML3.prototype.readSurface_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Surface', + 'localName should be Surface'); + var flatLinearRings = ol.xml.pushParseAndPop( + /** @type {Array.<Array.<number>>} */ ([null]), + this.SURFACE_PARSERS_, node, objectStack, this); + if (goog.isDef(flatLinearRings) && + !goog.isNull(flatLinearRings[0])) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + goog.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + return polygon; + } else { + return undefined; + } }; /** - * Set the text. - * - * @param {string|undefined} text Text. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. */ -ol.style.Text.prototype.setText = function(text) { - this.text_ = text; +ol.format.GML3.prototype.readCurve_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Curve', 'localName should be Curve'); + var flatCoordinates = ol.xml.pushParseAndPop( + /** @type {Array.<number>} */ ([null]), + this.CURVE_PARSERS_, node, objectStack, this); + if (goog.isDef(flatCoordinates)) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + return lineString; + } else { + return undefined; + } }; /** - * Set the text alignment. - * - * @param {string|undefined} textAlign Text align. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Extent|undefined} Envelope. */ -ol.style.Text.prototype.setTextAlign = function(textAlign) { - this.textAlign_ = textAlign; +ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Envelope', + 'localName should be Envelope'); + var flatCoordinates = ol.xml.pushParseAndPop( + /** @type {Array.<number>} */ ([null]), + this.ENVELOPE_PARSERS_, node, objectStack, this); + return ol.extent.createOrUpdate(flatCoordinates[1][0], + flatCoordinates[1][1], flatCoordinates[2][0], + flatCoordinates[2][1]); }; /** - * Set the text baseline. - * - * @param {string|undefined} textBaseline Text baseline. - * @api + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. */ -ol.style.Text.prototype.setTextBaseline = function(textBaseline) { - this.textBaseline_ = textBaseline; +ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false); + var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/; + /** @type {Array.<number>} */ + var flatCoordinates = []; + var m; + while ((m = re.exec(s))) { + flatCoordinates.push(parseFloat(m[1])); + s = s.substr(m[0].length); + } + if (s !== '') { + return undefined; + } + var context = objectStack[0]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var containerSrs = context['srsName']; + var axisOrientation = 'enu'; + if (!goog.isNull(containerSrs)) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + if (axisOrientation === 'neu') { + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) { + var y = flatCoordinates[i]; + var x = flatCoordinates[i + 1]; + flatCoordinates[i] = x; + flatCoordinates[i + 1] = y; + } + } + var len = flatCoordinates.length; + if (len == 2) { + flatCoordinates.push(0); + } + if (len === 0) { + return undefined; + } + return flatCoordinates; }; -// FIXME http://earth.google.com/kml/1.0 namespace? -// FIXME why does node.getAttribute return an unknown type? -// FIXME text -// FIXME serialize arbitrary feature properties -// FIXME don't parse style if extractStyles is false - -goog.provide('ol.format.KML'); - -goog.require('goog.Uri'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.math'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('ol.Feature'); -goog.require('ol.color'); -goog.require('ol.feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.XMLFeature'); -goog.require('ol.format.XSD'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.GeometryCollection'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.LinearRing'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.proj'); -goog.require('ol.style.Fill'); -goog.require('ol.style.Icon'); -goog.require('ol.style.IconAnchorUnits'); -goog.require('ol.style.IconOrigin'); -goog.require('ol.style.Image'); -goog.require('ol.style.Stroke'); -goog.require('ol.style.Style'); -goog.require('ol.style.Text'); -goog.require('ol.xml'); - - -/** - * @typedef {{x: number, xunits: (ol.style.IconAnchorUnits|undefined), - * y: number, yunits: (ol.style.IconAnchorUnits|undefined)}} - */ -ol.format.KMLVec2_; - - -/** - * @typedef {{flatCoordinates: Array.<number>, - * whens: Array.<number>}} - */ -ol.format.KMLGxTrackObject_; - - /** - * @classdesc - * Feature format for reading and writing data in the KML format. - * - * @constructor - * @extends {ol.format.XMLFeature} - * @param {olx.format.KMLOptions=} opt_options Options. - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>|undefined} Flat coordinates. */ -ol.format.KML = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this); - - /** - * @inheritDoc - */ - this.defaultDataProjection = ol.proj.get('EPSG:4326'); - - var defaultStyle = goog.isDef(options.defaultStyle) ? - options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_; - - /** @type {Object.<string, (Array.<ol.style.Style>|string)>} */ - var sharedStyles = {}; - - var findStyle = - /** - * @param {Array.<ol.style.Style>|string|undefined} styleValue Style - * value. - * @return {Array.<ol.style.Style>} Style. - */ - function(styleValue) { - if (goog.isArray(styleValue)) { - return styleValue; - } else if (goog.isString(styleValue)) { - // KML files in the wild occasionally forget the leading `#` on styleUrls - // defined in the same document. Add a leading `#` if it enables to find - // a style. - if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) { - styleValue = '#' + styleValue; - } - return findStyle(sharedStyles[styleValue]); +ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); + var context = objectStack[0]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var containerSrs = context['srsName']; + var containerDimension = node.parentNode.getAttribute('srsDimension'); + var axisOrientation = 'enu'; + if (!goog.isNull(containerSrs)) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + var coords = s.split(/\s+/); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = 2; + if (!goog.isNull(node.getAttribute('srsDimension'))) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('srsDimension')); + } else if (!goog.isNull(node.getAttribute('dimension'))) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('dimension')); + } else if (!goog.isNull(containerDimension)) { + dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); + } + var x, y, z; + var flatCoordinates = []; + for (var i = 0, ii = coords.length; i < ii; i += dim) { + x = parseFloat(coords[i]); + y = parseFloat(coords[i + 1]); + z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; + if (axisOrientation.substr(0, 2) === 'en') { + flatCoordinates.push(x, y, z); } else { - return defaultStyle; - } - }; - - /** - * @private - * @type {boolean} - */ - this.extractStyles_ = goog.isDef(options.extractStyles) ? - options.extractStyles : true; - - /** - * @private - * @type {Object.<string, (Array.<ol.style.Style>|string)>} - */ - this.sharedStyles_ = sharedStyles; - - /** - * @private - * @type {ol.feature.FeatureStyleFunction} - */ - this.featureStyleFunction_ = - /** - * @param {number} resolution Resolution. - * @return {Array.<ol.style.Style>} Style. - * @this {ol.Feature} - */ - function(resolution) { - var style = /** @type {Array.<ol.style.Style>|undefined} */ - (this.get('Style')); - if (goog.isDef(style)) { - return style; - } - var styleUrl = /** @type {string|undefined} */ (this.get('styleUrl')); - if (goog.isDef(styleUrl)) { - return findStyle(styleUrl); + flatCoordinates.push(y, x, z); } - return defaultStyle; - }; - + } + return flatCoordinates; }; -goog.inherits(ol.format.KML, ol.format.XMLFeature); /** * @const - * @type {Array.<string>} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.EXTENSIONS_ = ['.kml']; +ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_), + 'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_) + } +}); /** * @const - * @type {Array.<string>} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.GX_NAMESPACE_URIS_ = [ - 'http://www.google.com/kml/ext/2.2' -]; +ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'interior': ol.format.GML3.prototype.interiorParser_, + 'exterior': ol.format.GML3.prototype.exteriorParser_ + } +}); /** * @const - * @type {Array.<string>} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.NAMESPACE_URIS_ = [ - null, - 'http://earth.google.com/kml/2.0', - 'http://earth.google.com/kml/2.1', - 'http://earth.google.com/kml/2.2', - 'http://www.opengis.net/kml/2.2' -]; +ol.format.GML3.prototype.GEOMETRY_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), + 'MultiPoint': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPoint), + 'LineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLineString), + 'MultiLineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiLineString), + 'LinearRing' : ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLinearRing), + 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), + 'MultiPolygon': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPolygon), + 'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_), + 'MultiSurface': ol.xml.makeReplacer( + ol.format.GML3.prototype.readMultiSurface_), + 'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_), + 'MultiCurve': ol.xml.makeReplacer( + ol.format.GML3.prototype.readMultiCurve_), + 'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_) + } +}); /** * @const - * @type {string} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' + - 'https://developers.google.com/kml/schema/kml22gx.xsd'; +ol.format.GML3.prototype.MULTICURVE_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'curveMember': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.curveMemberParser_), + 'curveMembers': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.curveMemberParser_) + } +}); /** * @const - * @type {ol.Color} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1]; +ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'surfaceMember': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.surfaceMemberParser_), + 'surfaceMembers': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.surfaceMemberParser_) + } +}); /** * @const - * @type {ol.style.Fill} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({ - color: ol.format.KML.DEFAULT_COLOR_ +ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'LineString': ol.xml.makeArrayPusher( + ol.format.GMLBase.prototype.readLineString), + 'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_) + } }); /** * @const - * @type {ol.Size} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [2, 20]; // FIXME maybe [8, 32] ? +ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon), + 'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_) + } +}); /** * @const - * @type {ol.style.IconAnchorUnits} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ = - ol.style.IconAnchorUnits.PIXELS; +ol.format.GML3.prototype.SURFACE_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_) + } +}); /** * @const - * @type {ol.style.IconAnchorUnits} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ = - ol.style.IconAnchorUnits.PIXELS; +ol.format.GML3.prototype.CURVE_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_) + } +}); /** * @const - * @type {ol.Size} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [32, 32]; +ol.format.GML3.prototype.ENVELOPE_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'lowerCorner': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.readFlatPosList_), + 'upperCorner': ol.xml.makeArrayPusher( + ol.format.GML3.prototype.readFlatPosList_) + } +}); /** * @const - * @type {string} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ = - 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png'; +ol.format.GML3.prototype.PATCHES_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'PolygonPatch': ol.xml.makeReplacer( + ol.format.GML3.prototype.readPolygonPatch_) + } +}); /** * @const - * @type {ol.style.Image} + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({ - anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_, - anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_, - anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_, - crossOrigin: 'anonymous', - rotation: 0, - scale: 1, - size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_, - src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ +ol.format.GML3.prototype.SEGMENTS_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'LineStringSegment': ol.xml.makeReplacer( + ol.format.GML3.prototype.readLineStringSegment_) + } }); /** - * @const - * @type {ol.style.Stroke} + * @param {Node} node Node. + * @param {ol.geom.Point} value Point geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ - color: ol.format.KML.DEFAULT_COLOR_, - width: 1 -}); +ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + var axisOrientation = 'enu'; + if (goog.isDefAndNotNull(srsName)) { + axisOrientation = ol.proj.get(srsName).getAxisOrientation(); + } + var point = value.getCoordinates(); + var coords; + // only 2d for simple features profile + if (axisOrientation.substr(0, 2) === 'en') { + coords = (point[0] + ' ' + point[1]); + } else { + coords = (point[1] + ' ' + point[0]); + } + ol.format.XSD.writeStringTextNode(node, coords); +}; /** - * @const - * @type {ol.style.Text} + * @param {Array.<number>} point Point geometry. + * @param {string=} opt_srsName Optional srsName + * @return {string} * @private */ -ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({ - font: 'normal 16px Helvetica', - fill: ol.format.KML.DEFAULT_FILL_STYLE_, - stroke: ol.format.KML.DEFAULT_STROKE_STYLE_, - scale: 1 -}); +ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) { + var axisOrientation = 'enu'; + if (goog.isDefAndNotNull(opt_srsName)) { + axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation(); + } + return ((axisOrientation.substr(0, 2) === 'en') ? + point[0] + ' ' + point[1] : + point[1] + ' ' + point[0]); +}; /** - * @const - * @type {ol.style.Style} + * @param {Node} node Node. + * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({ - fill: ol.format.KML.DEFAULT_FILL_STYLE_, - image: ol.format.KML.DEFAULT_IMAGE_STYLE_, - text: ol.format.KML.DEFAULT_TEXT_STYLE_, - stroke: ol.format.KML.DEFAULT_STROKE_STYLE_, - zIndex: 0 -}); +ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + // only 2d for simple features profile + var points = value.getCoordinates(); + var len = points.length; + var parts = new Array(len); + var point; + for (var i = 0; i < len; ++i) { + point = points[i]; + parts[i] = this.getCoords_(point, srsName); + } + ol.format.XSD.writeStringTextNode(node, parts.join(' ')); +}; /** - * @const - * @type {Array.<ol.style.Style>} + * @param {Node} node Node. + * @param {ol.geom.Point} geometry Point geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_]; +ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + if (goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); + } + var pos = ol.xml.createElementNS(node.namespaceURI, 'pos'); + node.appendChild(pos); + this.writePos_(pos, geometry, objectStack); +}; /** - * @const - * @type {Object.<string, ol.style.IconAnchorUnits>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = { - 'fraction': ol.style.IconAnchorUnits.FRACTION, - 'pixels': ol.style.IconAnchorUnits.PIXELS +ol.format.GML3.ENVELOPE_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + } }; /** * @param {Node} node Node. - * @private - * @return {ol.Color|undefined} Color. + * @param {ol.Extent} extent Extent. + * @param {Array.<*>} objectStack Node stack. */ -ol.format.KML.readColor_ = function(node) { - var s = ol.xml.getAllTextContent(node, false); - // The KML specification states that colors should not include a leading `#` - // but we tolerate them. - var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s); - if (m) { - var hexColor = m[1]; - return [ - parseInt(hexColor.substr(6, 2), 16), - parseInt(hexColor.substr(4, 2), 16), - parseInt(hexColor.substr(2, 2), 16), - parseInt(hexColor.substr(0, 2), 16) / 255 - ]; - - } else { - return undefined; +ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) { + goog.asserts.assert(extent.length == 4, 'extent should have 4 items'); + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + if (goog.isDef(srsName)) { + node.setAttribute('srsName', srsName); } + var keys = ['lowerCorner', 'upperCorner']; + var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]]; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, + objectStack, keys, this); }; /** * @param {Node} node Node. + * @param {ol.geom.LinearRing} geometry LinearRing geometry. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {Array.<number>|undefined} Flat coordinates. */ -ol.format.KML.readFlatCoordinates_ = function(node) { - var s = ol.xml.getAllTextContent(node, false); - var flatCoordinates = []; - // The KML specification states that coordinate tuples should not include - // spaces, but we tolerate them. - var re = - /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i; - var m; - while ((m = re.exec(s))) { - var x = parseFloat(m[1]); - var y = parseFloat(m[2]); - var z = m[3] ? parseFloat(m[3]) : 0; - flatCoordinates.push(x, y, z); - s = s.substr(m[0].length); - } - if (s !== '') { - return undefined; +ol.format.GML3.prototype.writeLinearRing_ = + function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + if (goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); } - return flatCoordinates; + var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); + node.appendChild(posList); + this.writePosList_(posList, geometry, objectStack); }; /** - * @param {Node} node Node. + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node} Node. * @private - * @return {string|undefined} Style URL. */ -ol.format.KML.readStyleUrl_ = function(node) { - var s = goog.string.trim(ol.xml.getAllTextContent(node, false)); - if (goog.isDefAndNotNull(node.baseURI)) { - return goog.Uri.resolve(node.baseURI, s).toString(); - } else { - return s; +ol.format.GML3.prototype.RING_NODE_FACTORY_ = + function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + var parentNode = context.node; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var exteriorWritten = context['exteriorWritten']; + if (!goog.isDef(exteriorWritten)) { + context['exteriorWritten'] = true; } - + return ol.xml.createElementNS(parentNode.namespaceURI, + goog.isDef(exteriorWritten) ? 'interior' : 'exterior'); }; /** * @param {Node} node Node. + * @param {ol.geom.Polygon} geometry Polygon geometry. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {string} URI. */ -ol.format.KML.readURI_ = function(node) { - var s = ol.xml.getAllTextContent(node, false); - if (goog.isDefAndNotNull(node.baseURI)) { - return goog.Uri.resolve(node.baseURI, goog.string.trim(s)).toString(); - } else { - return goog.string.trim(s); +ol.format.GML3.prototype.writeSurfaceOrPolygon_ = + function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + if (node.nodeName !== 'PolygonPatch' && goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); + } + if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') { + var rings = geometry.getLinearRings(); + ol.xml.pushSerializeAndPop( + {node: node, srsName: srsName}, + ol.format.GML3.RING_SERIALIZERS_, + this.RING_NODE_FACTORY_, + rings, objectStack, undefined, this); + } else if (node.nodeName === 'Surface') { + var patches = ol.xml.createElementNS(node.namespaceURI, 'patches'); + node.appendChild(patches); + this.writeSurfacePatches_( + patches, geometry, objectStack); } }; /** * @param {Node} node Node. + * @param {ol.geom.LineString} geometry LineString geometry. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {ol.format.KMLVec2_} Vec2. */ -ol.format.KML.readVec2_ = function(node) { - var xunits = node.getAttribute('xunits'); - var yunits = node.getAttribute('yunits'); - return { - x: parseFloat(node.getAttribute('x')), - xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits], - y: parseFloat(node.getAttribute('y')), - yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits] - }; +ol.format.GML3.prototype.writeCurveOrLineString_ = + function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + if (node.nodeName !== 'LineStringSegment' && + goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); + } + if (node.nodeName === 'LineString' || + node.nodeName === 'LineStringSegment') { + var posList = ol.xml.createElementNS(node.namespaceURI, 'posList'); + node.appendChild(posList); + this.writePosList_(posList, geometry, objectStack); + } else if (node.nodeName === 'Curve') { + var segments = ol.xml.createElementNS(node.namespaceURI, 'segments'); + node.appendChild(segments); + this.writeCurveSegments_(segments, + geometry, objectStack); + } }; /** * @param {Node} node Node. + * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {number|undefined} Scale. */ -ol.format.KML.readScale_ = function(node) { - var number = ol.format.XSD.readDecimal(node); - if (goog.isDef(number)) { - return Math.sqrt(number); - } else { - return undefined; +ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = + function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + var surface = context['surface']; + if (goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); } + var polygons = geometry.getPolygons(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface}, + ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_, + this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons, + objectStack, undefined, this); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.MultiPoint} geometry MultiPoint geometry. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {Array.<ol.style.Style>|string|undefined} StyleMap. */ -ol.format.KML.readStyleMapValue_ = function(node, objectStack) { - return ol.xml.pushParseAndPop( - /** @type {Array.<ol.style.Style>|string|undefined} */ (undefined), - ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); +ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry, + objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + if (goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); + } + var points = geometry.getPoints(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName}, + ol.format.GML3.POINTMEMBER_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('pointMember'), points, + objectStack, undefined, this); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.MultiLineString} geometry MultiLineString geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.IconStyleParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'IconStyle'); - // FIXME refreshMode - // FIXME refreshInterval - // FIXME viewRefreshTime - // FIXME viewBoundScale - // FIXME viewFormat - // FIXME httpQuery - var object = ol.xml.pushParseAndPop( - {}, ol.format.KML.ICON_STYLE_PARSERS_, node, objectStack); - if (!goog.isDef(object)) { - return; - } - var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isObject(styleObject)); - var IconObject = /** @type {Object} */ (goog.object.get(object, 'Icon', {})); - var src; - var href = /** @type {string|undefined} */ - (goog.object.get(IconObject, 'href')); - if (goog.isDef(href)) { - src = href; - } else { - src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_; - } - var anchor, anchorXUnits, anchorYUnits; - var hotSpot = /** @type {ol.format.KMLVec2_|undefined} */ - (goog.object.get(object, 'hotSpot')); - if (goog.isDef(hotSpot)) { - anchor = [hotSpot.x, hotSpot.y]; - anchorXUnits = hotSpot.xunits; - anchorYUnits = hotSpot.yunits; - } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { - anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_; - anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_; - anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_; - } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) { - anchor = [0.5, 0]; - anchorXUnits = ol.style.IconAnchorUnits.FRACTION; - anchorYUnits = ol.style.IconAnchorUnits.FRACTION; - } - - var offset; - var x = /** @type {number|undefined} */ - (goog.object.get(IconObject, 'x')); - var y = /** @type {number|undefined} */ - (goog.object.get(IconObject, 'y')); - if (goog.isDef(x) && goog.isDef(y)) { - offset = [x, y]; - } - - var size; - var w = /** @type {number|undefined} */ - (goog.object.get(IconObject, 'w')); - var h = /** @type {number|undefined} */ - (goog.object.get(IconObject, 'h')); - if (goog.isDef(w) && goog.isDef(h)) { - size = [w, h]; - } - - var rotation; - var heading = /** @type {number|undefined} */ - (goog.object.get(object, 'heading')); - if (goog.isDef(heading)) { - rotation = goog.math.toRadians(heading); +ol.format.GML3.prototype.writeMultiCurveOrLineString_ = + function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var srsName = context['srsName']; + var curve = context['curve']; + if (goog.isDefAndNotNull(srsName)) { + node.setAttribute('srsName', srsName); } + var lines = geometry.getLineStrings(); + ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve}, + ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_, + this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines, + objectStack, undefined, this); +}; - var scale = /** @type {number|undefined} */ - (goog.object.get(object, 'scale')); - if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { - size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; - } - var imageStyle = new ol.style.Icon({ - anchor: anchor, - anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT, - anchorXUnits: anchorXUnits, - anchorYUnits: anchorYUnits, - crossOrigin: 'anonymous', // FIXME should this be configurable? - offset: offset, - offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT, - rotation: rotation, - scale: scale, - size: size, - src: src - }); - goog.object.set(styleObject, 'imageStyle', imageStyle); +/** + * @param {Node} node Node. + * @param {ol.geom.LinearRing} ring LinearRing geometry. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) { + var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing'); + node.appendChild(linearRing); + this.writeLinearRing_(linearRing, ring, objectStack); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.LabelStyleParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LabelStyle'); - // FIXME colorMode - var object = ol.xml.pushParseAndPop( - {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack); - if (!goog.isDef(object)) { - return; +ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = + function(node, polygon, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var child = this.GEOMETRY_NODE_FACTORY_( + polygon, objectStack); + if (goog.isDef(child)) { + node.appendChild(child); + this.writeSurfaceOrPolygon_(child, polygon, objectStack); } - var styleObject = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(styleObject)); - var textStyle = new ol.style.Text({ - fill: new ol.style.Fill({ - color: /** @type {ol.Color} */ - (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)) - }), - scale: /** @type {number|undefined} */ - (goog.object.get(object, 'scale')) - }); - goog.object.set(styleObject, 'textStyle', textStyle); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.Point} point Point geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.LineStyleParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LineStyle'); - // FIXME colorMode - // FIXME gx:outerColor - // FIXME gx:outerWidth - // FIXME gx:physicalWidth - // FIXME gx:labelVisibility - var object = ol.xml.pushParseAndPop( - {}, ol.format.KML.LINE_STYLE_PARSERS_, node, objectStack); - if (!goog.isDef(object)) { - return; - } - var styleObject = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(styleObject)); - var strokeStyle = new ol.style.Stroke({ - color: /** @type {ol.Color} */ - (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)), - width: /** @type {number} */ (goog.object.get(object, 'width', 1)) - }); - goog.object.set(styleObject, 'strokeStyle', strokeStyle); +ol.format.GML3.prototype.writePointMember_ = + function(node, point, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, 'Point'); + node.appendChild(child); + this.writePoint_(child, point, objectStack); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.LineString} line LineString geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.PolyStyleParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'PolyStyle'); - // FIXME colorMode - var object = ol.xml.pushParseAndPop( - {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack); - if (!goog.isDef(object)) { - return; - } - var styleObject = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(styleObject)); - var fillStyle = new ol.style.Fill({ - color: /** @type {ol.Color} */ - (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)) - }); - goog.object.set(styleObject, 'fillStyle', fillStyle); - var fill = /** @type {boolean|undefined} */ (goog.object.get(object, 'fill')); - if (goog.isDef(fill)) { - goog.object.set(styleObject, 'fill', fill); - } - var outline = - /** @type {boolean|undefined} */ (goog.object.get(object, 'outline')); - if (goog.isDef(outline)) { - goog.object.set(styleObject, 'outline', outline); +ol.format.GML3.prototype.writeLineStringOrCurveMember_ = + function(node, line, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack); + if (goog.isDef(child)) { + node.appendChild(child); + this.writeCurveOrLineString_(child, line, objectStack); } }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.Polygon} polygon Polygon geometry. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {Array.<number>} LinearRing flat coordinates. */ -ol.format.KML.readFlatLinearRing_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LinearRing'); - return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop( - null, ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack)); +ol.format.GML3.prototype.writeSurfacePatches_ = + function(node, polygon, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch'); + node.appendChild(child); + this.writeSurfaceOrPolygon_(child, polygon, objectStack); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {ol.geom.LineString} line LineString geometry. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.gxCoordParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(goog.array.contains( - ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI)); - goog.asserts.assert(node.localName == 'coord'); - var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isObject(gxTrackObject)); - var flatCoordinates = gxTrackObject.flatCoordinates; - var s = ol.xml.getAllTextContent(node, false); - var re = - /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i; - var m = re.exec(s); - if (m) { - var x = parseFloat(m[1]); - var y = parseFloat(m[2]); - var z = parseFloat(m[3]); - flatCoordinates.push(x, y, z, 0); - } else { - flatCoordinates.push(0, 0, 0, 0); - } +ol.format.GML3.prototype.writeCurveSegments_ = + function(node, line, objectStack) { + var child = ol.xml.createElementNS(node.namespaceURI, + 'LineStringSegment'); + node.appendChild(child); + this.writeCurveOrLineString_(child, line, objectStack); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {ol.geom.MultiLineString|undefined} MultiLineString. + * @param {ol.geom.Geometry|ol.Extent} geometry Geometry. + * @param {Array.<*>} objectStack Node stack. */ -ol.format.KML.readGxMultiTrack_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(goog.array.contains( - ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI)); - goog.asserts.assert(node.localName == 'MultiTrack'); - var lineStrings = ol.xml.pushParseAndPop( - /** @type {Array.<ol.geom.LineString>} */ ([]), - ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack); - if (!goog.isDef(lineStrings)) { - return undefined; +ol.format.GML3.prototype.writeGeometryElement = + function(node, geometry, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var item = goog.object.clone(context); + item.node = node; + var value; + if (goog.isArray(geometry)) { + if (goog.isDef(context.dataProjection)) { + value = ol.proj.transformExtent( + geometry, context.featureProjection, context.dataProjection); + } else { + value = geometry; + } + } else { + goog.asserts.assertInstanceof(geometry, ol.geom.Geometry, + 'geometry should be an ol.geom.Geometry'); + value = + ol.format.Feature.transformWithOptions(geometry, true, context); } - var multiLineString = new ol.geom.MultiLineString(null); - multiLineString.setLineStrings(lineStrings); - return multiLineString; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + (item), ol.format.GML3.GEOMETRY_SERIALIZERS_, + this.GEOMETRY_NODE_FACTORY_, [value], + objectStack, undefined, this); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {ol.geom.LineString|undefined} LineString. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. */ -ol.format.KML.readGxTrack_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(goog.array.contains( - ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI)); - goog.asserts.assert(node.localName == 'Track'); - var gxTrackObject = ol.xml.pushParseAndPop( - /** @type {ol.format.KMLGxTrackObject_} */ ({ - flatCoordinates: [], - whens: [] - }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack); - if (!goog.isDef(gxTrackObject)) { - return undefined; +ol.format.GML3.prototype.writeFeatureElement = + function(node, feature, objectStack) { + var fid = feature.getId(); + if (goog.isDef(fid)) { + node.setAttribute('fid', fid); } - var flatCoordinates = gxTrackObject.flatCoordinates; - var whens = gxTrackObject.whens; - goog.asserts.assert(flatCoordinates.length / 4 == whens.length); - var i, ii; - for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii; - ++i) { - flatCoordinates[4 * i + 3] = whens[i]; + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featureNS = context['featureNS']; + var geometryName = feature.getGeometryName(); + if (!goog.isDef(context.serializers)) { + context.serializers = {}; + context.serializers[featureNS] = {}; } - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); - return lineString; + var properties = feature.getProperties(); + var keys = [], values = []; + for (var key in properties) { + var value = properties[key]; + if (!goog.isNull(value)) { + keys.push(key); + values.push(value); + if (key == geometryName) { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + this.writeGeometryElement, this); + } + } else { + if (!(key in context.serializers[featureNS])) { + context.serializers[featureNS][key] = ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode); + } + } + } + } + var item = goog.object.clone(context); + item.node = node; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + (item), context.serializers, + ol.xml.makeSimpleNodeFactory(undefined, featureNS), + values, + objectStack, keys); }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {Array.<ol.Feature>} features Features. + * @param {Array.<*>} objectStack Node stack. * @private - * @return {Object} Icon object. */ -ol.format.KML.readIcon_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Icon'); - var iconObject = ol.xml.pushParseAndPop( - {}, ol.format.KML.ICON_PARSERS_, node, objectStack); - if (goog.isDef(iconObject)) { - return iconObject; - } else { - return null; - } +ol.format.GML3.prototype.writeFeatureMembers_ = + function(node, features, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var serializers = {}; + serializers[featureNS] = {}; + serializers[featureNS][featureType] = ol.xml.makeChildAppender( + this.writeFeatureElement, this); + var item = goog.object.clone(context); + item.node = node; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + (item), + serializers, + ol.xml.makeSimpleNodeFactory(featureType, featureNS), features, + objectStack); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private - * @return {Array.<number>} Flat coordinates. */ -ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(null, - ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack)); +ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'surfaceMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygonMember_), + 'polygonMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygonMember_) + } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private - * @return {ol.geom.LineString|undefined} LineString. */ -ol.format.KML.readLineString_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LineString'); - var flatCoordinates = - ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); - if (goog.isDef(flatCoordinates)) { - var lineString = new ol.geom.LineString(null); - lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); - return lineString; - } else { - return undefined; +ol.format.GML3.POINTMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'pointMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writePointMember_) } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private - * @return {ol.geom.Polygon|undefined} Polygon. */ -ol.format.KML.readLinearRing_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'LinearRing'); - var flatCoordinates = - ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); - if (goog.isDef(flatCoordinates)) { - var polygon = new ol.geom.Polygon(null); - polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates, - [flatCoordinates.length]); - return polygon; - } else { - return undefined; +ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'lineStringMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLineStringOrCurveMember_), + 'curveMember': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLineStringOrCurveMember_) } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private - * @return {ol.geom.Geometry} Geometry. */ -ol.format.KML.readMultiGeometry_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MultiGeometry'); - var geometries = ol.xml.pushParseAndPop( - /** @type {Array.<ol.geom.Geometry>} */ ([]), - ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack); - if (!goog.isDef(geometries)) { - return null; - } - if (geometries.length === 0) { - return new ol.geom.GeometryCollection(geometries); - } - var homogeneous = true; - var type = geometries[0].getType(); - var geometry, i, ii; - for (i = 1, ii = geometries.length; i < ii; ++i) { - geometry = geometries[i]; - if (geometry.getType() != type) { - homogeneous = false; - break; - } +ol.format.GML3.RING_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_), + 'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_) } - if (homogeneous) { - /** @type {ol.geom.GeometryLayout} */ - var layout; - /** @type {Array.<number>} */ - var flatCoordinates; - if (type == ol.geom.GeometryType.POINT) { - var point = geometries[0]; - goog.asserts.assertInstanceof(point, ol.geom.Point); - layout = point.getLayout(); - flatCoordinates = point.getFlatCoordinates(); - for (i = 1, ii = geometries.length; i < ii; ++i) { - geometry = geometries[i]; - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - goog.asserts.assert(geometry.getLayout() == layout); - goog.array.extend(flatCoordinates, geometry.getFlatCoordinates()); - } - var multiPoint = new ol.geom.MultiPoint(null); - multiPoint.setFlatCoordinates(layout, flatCoordinates); - return multiPoint; - } else if (type == ol.geom.GeometryType.LINE_STRING) { - var multiLineString = new ol.geom.MultiLineString(null); - multiLineString.setLineStrings(geometries); - return multiLineString; - } else if (type == ol.geom.GeometryType.POLYGON) { - var multiPolygon = new ol.geom.MultiPolygon(null); - multiPolygon.setPolygons(geometries); - return multiPolygon; - } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) { - return new ol.geom.GeometryCollection(geometries); - } else { - goog.asserts.fail(); - return null; - } - } else { - return new ol.geom.GeometryCollection(geometries); +}; + + +/** + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @private + */ +ol.format.GML3.GEOMETRY_SERIALIZERS_ = { + 'http://www.opengis.net/gml': { + 'Curve': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeCurveOrLineString_), + 'MultiCurve': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiCurveOrLineString_), + 'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_), + 'MultiPoint': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiPoint_), + 'LineString': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeCurveOrLineString_), + 'MultiLineString': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiCurveOrLineString_), + 'LinearRing': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeLinearRing_), + 'Polygon': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygon_), + 'MultiPolygon': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), + 'Surface': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeSurfaceOrPolygon_), + 'MultiSurface': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_), + 'Envelope': ol.xml.makeChildAppender( + ol.format.GML3.prototype.writeEnvelope) } }; /** - * @param {Node} node Node. + * @const + * @type {Object.<string, string>} + * @private + */ +ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = { + 'MultiLineString': 'lineStringMember', + 'MultiCurve': 'curveMember', + 'MultiPolygon': 'polygonMember', + 'MultiSurface': 'surfaceMember' +}; + + +/** + * @const + * @param {*} value Value. * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. * @private - * @return {ol.geom.Point|undefined} Point. */ -ol.format.KML.readPoint_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Point'); - var flatCoordinates = - ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); - if (goog.isDefAndNotNull(flatCoordinates)) { - var point = new ol.geom.Point(null); - goog.asserts.assert(flatCoordinates.length == 3); - point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); - return point; - } else { - return undefined; - } +ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = + function(value, objectStack, opt_nodeName) { + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode), + 'parentNode should be a node'); + return ol.xml.createElementNS('http://www.opengis.net/gml', + ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]); }; /** - * @param {Node} node Node. + * @const + * @param {*} value Value. * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. * @private - * @return {ol.geom.Polygon|undefined} Polygon. */ -ol.format.KML.readPolygon_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Polygon'); - var flatLinearRings = ol.xml.pushParseAndPop( - /** @type {Array.<Array.<number>>} */ ([null]), - ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack); - if (goog.isDefAndNotNull(flatLinearRings) && - !goog.isNull(flatLinearRings[0])) { - var polygon = new ol.geom.Polygon(null); - var flatCoordinates = flatLinearRings[0]; - var ends = [flatCoordinates.length]; - var i, ii; - for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { - goog.array.extend(flatCoordinates, flatLinearRings[i]); - ends.push(flatCoordinates.length); +ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = + function(value, objectStack, opt_nodeName) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var multiSurface = context['multiSurface']; + var surface = context['surface']; + var curve = context['curve']; + var multiCurve = context['multiCurve']; + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode), + 'parentNode should be a node'); + var nodeName; + if (!goog.isArray(value)) { + goog.asserts.assertInstanceof(value, ol.geom.Geometry, + 'value should be an ol.geom.Geometry'); + nodeName = value.getType(); + if (nodeName === 'MultiPolygon' && multiSurface === true) { + nodeName = 'MultiSurface'; + } else if (nodeName === 'Polygon' && surface === true) { + nodeName = 'Surface'; + } else if (nodeName === 'LineString' && curve === true) { + nodeName = 'Curve'; + } else if (nodeName === 'MultiLineString' && multiCurve === true) { + nodeName = 'MultiCurve'; } - polygon.setFlatCoordinates( - ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); - return polygon; } else { - return undefined; + nodeName = 'Envelope'; } + return ol.xml.createElementNS('http://www.opengis.net/gml', + nodeName); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<ol.style.Style>} Style. + * Encode a geometry in GML 3.1.1 Simple Features. + * + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api */ -ol.format.KML.readStyle_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Style'); - var styleObject = ol.xml.pushParseAndPop( - {}, ol.format.KML.STYLE_PARSERS_, node, objectStack); - if (!goog.isDef(styleObject)) { - return null; - } - var fillStyle = /** @type {ol.style.Fill} */ (goog.object.get( - styleObject, 'fillStyle', ol.format.KML.DEFAULT_FILL_STYLE_)); - var fill = /** @type {boolean|undefined} */ - (goog.object.get(styleObject, 'fill')); - if (goog.isDef(fill) && !fill) { - fillStyle = null; - } - var imageStyle = /** @type {ol.style.Image} */ (goog.object.get( - styleObject, 'imageStyle', ol.format.KML.DEFAULT_IMAGE_STYLE_)); - var textStyle = /** @type {ol.style.Text} */ (goog.object.get( - styleObject, 'textStyle', ol.format.KML.DEFAULT_TEXT_STYLE_)); - var strokeStyle = /** @type {ol.style.Stroke} */ (goog.object.get( - styleObject, 'strokeStyle', ol.format.KML.DEFAULT_STROKE_STYLE_)); - var outline = /** @type {boolean|undefined} */ - (goog.object.get(styleObject, 'outline')); - if (goog.isDef(outline) && !outline) { - strokeStyle = null; +ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) { + opt_options = this.adaptOptions(opt_options); + var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom'); + var context = {node: geom, srsName: this.srsName, + curve: this.curve_, surface: this.surface_, + multiSurface: this.multiSurface_, multiCurve: this.multiCurve_}; + if (goog.isDef(opt_options)) { + goog.object.extend(context, opt_options); } - return [new ol.style.Style({ - fill: fillStyle, - image: imageStyle, - stroke: strokeStyle, - text: textStyle, - zIndex: undefined // FIXME - })]; + this.writeGeometryElement(geom, geometry, [context]); + return geom; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode an array of features in GML 3.1.1 Simple Features. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable + */ +ol.format.GML3.prototype.writeFeatures; + + +/** + * Encode an array of features in the GML 3.1.1 format as an XML node. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api */ -ol.format.KML.DataParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Data'); - var name = node.getAttribute('name'); - if (!goog.isNull(name)) { - var data = ol.xml.pushParseAndPop( - undefined, ol.format.KML.DATA_PARSERS_, node, objectStack); - if (goog.isDef(data)) { - var featureObject = - /** @type {Object} */ (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isObject(featureObject)); - goog.object.set(featureObject, name, data); - } +ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var node = ol.xml.createElementNS('http://www.opengis.net/gml', + 'featureMembers'); + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation); + var context = { + srsName: this.srsName, + curve: this.curve_, + surface: this.surface_, + multiSurface: this.multiSurface_, + multiCurve: this.multiCurve_, + featureNS: this.featureNS, + featureType: this.featureType + }; + if (goog.isDef(opt_options)) { + goog.object.extend(context, opt_options); } + this.writeFeatureMembers_(node, features, [context]); + return node; }; + /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * @classdesc + * Feature format for reading and writing data in the GML format + * version 3.1.1. + * Currently only supports GML 3.1.1 Simple Features profile. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.GMLBase} + * @api stable */ -ol.format.KML.ExtendedDataParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ExtendedData'); - ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack); -}; +ol.format.GML = ol.format.GML3; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Encode an array of features in GML 3.1.1 Simple Features. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. + * @api stable */ -ol.format.KML.PairDataParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Pair'); - var pairObject = ol.xml.pushParseAndPop( - {}, ol.format.KML.PAIR_PARSERS_, node, objectStack); - if (!goog.isDef(pairObject)) { - return; - } - var key = /** @type {string|undefined} */ - (goog.object.get(pairObject, 'key')); - if (goog.isDef(key) && key == 'normal') { - var styleUrl = /** @type {string|undefined} */ - (goog.object.get(pairObject, 'styleUrl')); - if (goog.isDef(styleUrl)) { - objectStack[objectStack.length - 1] = styleUrl; - } - var Style = /** @type {ol.style.Style} */ - (goog.object.get(pairObject, 'Style')); - if (goog.isDef(Style)) { - objectStack[objectStack.length - 1] = Style; - } - } +ol.format.GML.prototype.writeFeatures; + + +/** + * Encode an array of features in the GML 3.1.1 format as an XML node. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GML.prototype.writeFeaturesNode; + +goog.provide('ol.format.GML2'); + +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('ol.extent'); +goog.require('ol.format.GML'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XSD'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + + +/** + * @classdesc + * Feature format for reading and writing data in the GML format, + * version 2.1.2. + * + * @constructor + * @param {olx.format.GMLOptions=} opt_options Optional configuration object. + * @extends {ol.format.GMLBase} + * @api + */ +ol.format.GML2 = function(opt_options) { + var options = /** @type {olx.format.GMLOptions} */ + (goog.isDef(opt_options) ? opt_options : {}); + + goog.base(this, options); + + this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][ + 'featureMember'] = + ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal); + + /** + * @inheritDoc + */ + this.schemaLocation = goog.isDef(options.schemaLocation) ? + options.schemaLocation : ol.format.GML2.schemaLocation_; + }; +goog.inherits(ol.format.GML2, ol.format.GMLBase); /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {string} * @private */ -ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'StyleMap'); - var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); - if (!goog.isDef(styleMapValue)) { - return; - } - var placemarkObject = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(placemarkObject)); - if (goog.isArray(styleMapValue)) { - goog.object.set(placemarkObject, 'Style', styleMapValue); - } else if (goog.isString(styleMapValue)) { - goog.object.set(placemarkObject, 'styleUrl', styleMapValue); - } else { - goog.asserts.fail(); - } -}; +ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS + + ' http://schemas.opengis.net/gml/2.1.2/feature.xsd'; /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. * @private + * @return {Array.<number>|undefined} Flat coordinates. */ -ol.format.KML.SchemaDataParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'SchemaData'); - ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack); +ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) { + var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, ''); + var context = objectStack[0]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var containerSrs = context['srsName']; + var containerDimension = node.parentNode.getAttribute('srsDimension'); + var axisOrientation = 'enu'; + if (!goog.isNull(containerSrs)) { + var proj = ol.proj.get(containerSrs); + axisOrientation = proj.getAxisOrientation(); + } + var coords = s.split(/[\s,]+/); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = 2; + if (!goog.isNull(node.getAttribute('srsDimension'))) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('srsDimension')); + } else if (!goog.isNull(node.getAttribute('dimension'))) { + dim = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('dimension')); + } else if (!goog.isNull(containerDimension)) { + dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension); + } + var x, y, z; + var flatCoordinates = []; + for (var i = 0, ii = coords.length; i < ii; i += dim) { + x = parseFloat(coords[i]); + y = parseFloat(coords[i + 1]); + z = (dim === 3) ? parseFloat(coords[i + 2]) : 0; + if (axisOrientation.substr(0, 2) === 'en') { + flatCoordinates.push(x, y, z); + } else { + flatCoordinates.push(y, x, z); + } + } + return flatCoordinates; }; @@ -89006,17 +91671,18 @@ ol.format.KML.SchemaDataParser_ = function(node, objectStack) { * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. * @private + * @return {ol.Extent|undefined} Envelope. */ -ol.format.KML.SimpleDataParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'SimpleData'); - var name = node.getAttribute('name'); - if (!goog.isNull(name)) { - var data = ol.format.XSD.readString(node); - var featureObject = - /** @type {Object} */ (objectStack[objectStack.length - 1]); - goog.object.set(featureObject, name, data); - } +ol.format.GML2.prototype.readBox_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Box', 'localName should be Box'); + var flatCoordinates = ol.xml.pushParseAndPop( + /** @type {Array.<number>} */ ([null]), + this.BOX_PARSERS_, node, objectStack, this); + return ol.extent.createOrUpdate(flatCoordinates[1][0], + flatCoordinates[1][1], flatCoordinates[1][3], + flatCoordinates[1][4]); }; @@ -89025,17 +91691,22 @@ ol.format.KML.SimpleDataParser_ = function(node, objectStack) { * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'innerBoundaryIs'); +ol.format.GML2.prototype.innerBoundaryIsParser_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'innerBoundaryIs', + 'localName should be innerBoundaryIs'); var flatLinearRing = ol.xml.pushParseAndPop( /** @type {Array.<number>|undefined} */ (undefined), - ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack); + this.RING_PARSERS, node, objectStack, this); if (goog.isDef(flatLinearRing)) { var flatLinearRings = /** @type {Array.<Array.<number>>} */ (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(flatLinearRings)); - goog.asserts.assert(flatLinearRings.length > 0); + goog.asserts.assert(goog.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + goog.asserts.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length larger than 0'); flatLinearRings.push(flatLinearRing); } }; @@ -89046,58 +91717,38 @@ ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) { * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'outerBoundaryIs'); +ol.format.GML2.prototype.outerBoundaryIsParser_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'outerBoundaryIs', + 'localName should be outerBoundaryIs'); var flatLinearRing = ol.xml.pushParseAndPop( /** @type {Array.<number>|undefined} */ (undefined), - ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack); + this.RING_PARSERS, node, objectStack, this); if (goog.isDef(flatLinearRing)) { var flatLinearRings = /** @type {Array.<Array.<number>>} */ (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isArray(flatLinearRings)); - goog.asserts.assert(flatLinearRings.length > 0); + goog.asserts.assert(goog.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + goog.asserts.assert(flatLinearRings.length > 0, + 'flatLinearRings should have an array length larger than 0'); flatLinearRings[0] = flatLinearRing; } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.whenParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'when'); - var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isObject(gxTrackObject)); - var whens = gxTrackObject.whens; - var s = ol.xml.getAllTextContent(node, false); - var re = - /^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/; - var m = re.exec(s); - if (m) { - var year = parseInt(m[1], 10); - var month = goog.isDef(m[3]) ? parseInt(m[3], 10) - 1 : 0; - var day = goog.isDef(m[5]) ? parseInt(m[5], 10) : 1; - var hour = goog.isDef(m[7]) ? parseInt(m[7], 10) : 0; - var minute = goog.isDef(m[8]) ? parseInt(m[8], 10) : 0; - var second = goog.isDef(m[9]) ? parseInt(m[9], 10) : 0; - var when = Date.UTC(year, month, day, hour, minute, second); - if (goog.isDef(m[10]) && m[10] != 'Z') { - var sign = m[11] == '-' ? -1 : 1; - when += sign * 60 * parseInt(m[12], 10); - if (goog.isDef(m[13])) { - when += sign * 60 * 60 * parseInt(m[13], 10); - } - } - whens.push(when); - } else { - whens.push(0); +ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'coordinates': ol.xml.makeReplacer( + ol.format.GML2.prototype.readFlatCoordinates_) } -}; +}); /** @@ -89105,10 +91756,12 @@ ol.format.KML.whenParser_ = function(node, objectStack) { * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.DATA_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'value': ol.xml.makeReplacer(ol.format.XSD.readString) - }); +ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_, + 'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_ + } +}); /** @@ -89116,11 +91769,12 @@ ol.format.KML.DATA_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Data': ol.format.KML.DataParser_, - 'SchemaData': ol.format.KML.SchemaDataParser_ - }); +ol.format.GML2.prototype.BOX_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'coordinates': ol.xml.makeArrayPusher( + ol.format.GML2.prototype.readFlatCoordinates_) + } +}); /** @@ -89128,128 +91782,305 @@ ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) - }); +ol.format.GML2.prototype.GEOMETRY_PARSERS_ = Object({ + 'http://www.opengis.net/gml' : { + 'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint), + 'MultiPoint': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPoint), + 'LineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLineString), + 'MultiLineString': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiLineString), + 'LinearRing' : ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readLinearRing), + 'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon), + 'MultiPolygon': ol.xml.makeReplacer( + ol.format.GMLBase.prototype.readMultiPolygon), + 'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_) + } +}); + +goog.provide('ol.format.GPX'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.Point'); +goog.require('ol.proj'); +goog.require('ol.xml'); + + + +/** + * @classdesc + * Feature format for reading and writing data in the GPX format. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.GPXOptions=} opt_options Options. + * @api stable + */ +ol.format.GPX = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + goog.base(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + /** + * @type {function(ol.Feature, Node)|undefined} + * @private + */ + this.readExtensions_ = options.readExtensions; +}; +goog.inherits(ol.format.GPX, ol.format.XMLFeature); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private + * @type {Array.<string>} */ -ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_, - 'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_ - }); +ol.format.GPX.NAMESPACE_URIS_ = [ + null, + 'http://www.topografix.com/GPX/1/0', + 'http://www.topografix.com/GPX/1/1' +]; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {Node} node Node. + * @param {Object} values Values. * @private + * @return {Array.<number>} Flat coordinates. */ -ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'when': ol.format.KML.whenParser_ - }, ol.xml.makeParsersNS( - ol.format.KML.GX_NAMESPACE_URIS_, { - 'coord': ol.format.KML.gxCoordParser_ - })); +ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + flatCoordinates.push( + parseFloat(node.getAttribute('lon')), + parseFloat(node.getAttribute('lat'))); + if (goog.object.containsKey(values, 'ele')) { + flatCoordinates.push( + /** @type {number} */ (values['ele'])); + goog.object.remove(values, 'ele'); + } else { + flatCoordinates.push(0); + } + if (goog.object.containsKey(values, 'time')) { + flatCoordinates.push( + /** @type {number} */ (values['time'])); + goog.object.remove(values, 'time'); + } else { + flatCoordinates.push(0); + } + return flatCoordinates; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) - }); +ol.format.GPX.parseLink_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'link', 'localName should be link'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var href = node.getAttribute('href'); + if (!goog.isNull(href)) { + values['link'] = href; + } + ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.ICON_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) - }, ol.xml.makeParsersNS( - ol.format.KML.GX_NAMESPACE_URIS_, { - 'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) - })); +ol.format.GPX.parseExtensions_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'extensions', + 'localName should be extensions'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values['extensionsNode_'] = node; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_), - 'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), - 'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_), - 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) - }); +ol.format.GPX.parseRtePt_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'rtept', 'localName should be rtept'); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack); + if (goog.isDef(values)) { + var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ + (rteValues['flatCoordinates']); + ol.format.GPX.appendCoordinate_(flatCoordinates, node, values); + } +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) - }); +ol.format.GPX.parseTrkPt_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'trkpt', 'localName should be trkpt'); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack); + if (goog.isDef(values)) { + var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ + (trkValues['flatCoordinates']); + ol.format.GPX.appendCoordinate_(flatCoordinates, node, values); + } +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), - 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) - }); +ol.format.GPX.parseTrkSeg_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'trkseg', + 'localName should be trkseg'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack); + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + var ends = /** @type {Array.<number>} */ (values['ends']); + ends.push(flatCoordinates.length); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. + */ +ol.format.GPX.readRte_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'rte', 'localName should be rte'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop({ + 'flatCoordinates': [] + }, ol.format.GPX.RTE_PARSERS_, node, objectStack); + if (!goog.isDef(values)) { + return undefined; + } + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + goog.object.remove(values, 'flatCoordinates'); + var geometry = new ol.geom.LineString(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. + */ +ol.format.GPX.readTrk_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'trk', 'localName should be trk'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop({ + 'flatCoordinates': [], + 'ends': [] + }, ol.format.GPX.TRK_PARSERS_, node, objectStack); + if (!goog.isDef(values)) { + return undefined; + } + var flatCoordinates = /** @type {Array.<number>} */ + (values['flatCoordinates']); + goog.object.remove(values, 'flatCoordinates'); + var ends = /** @type {Array.<number>} */ (values['ends']); + goog.object.remove(values, 'ends'); + var geometry = new ol.geom.MultiLineString(null); + geometry.setFlatCoordinates( + ol.geom.GeometryLayout.XYZM, flatCoordinates, ends); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {ol.Feature|undefined} Waypoint. */ -ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), - 'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) - }); +ol.format.GPX.readWpt_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'wpt', 'localName should be wpt'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var values = ol.xml.pushParseAndPop( + {}, ol.format.GPX.WPT_PARSERS_, node, objectStack); + if (!goog.isDef(values)) { + return undefined; + } + var coordinates = ol.format.GPX.appendCoordinate_([], node, values); + var geometry = new ol.geom.Point( + coordinates, ol.geom.GeometryLayout.XYZM); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setProperties(values); + return feature; +}; /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>} * @private */ -ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_), - 'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_), - 'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_), - 'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_), - 'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_) - }); +ol.format.GPX.FEATURE_READER_ = { + 'rte': ol.format.GPX.readRte_, + 'trk': ol.format.GPX.readTrk_, + 'wpt': ol.format.GPX.readWpt_ +}; /** @@ -89257,9 +92088,11 @@ ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.GX_NAMESPACE_URIS_, { - 'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_) +ol.format.GPX.GPX_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_), + 'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_), + 'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_) }); @@ -89268,9 +92101,12 @@ ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) +ol.format.GPX.LINK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'), + 'type': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType') }); @@ -89279,11 +92115,18 @@ ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.PAIR_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), - 'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyleUrl_) +ol.format.GPX.RTE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'number': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_, + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'rtept': ol.format.GPX.parseRtePt_ }); @@ -89292,36 +92135,11 @@ ol.format.KML.PAIR_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'ExtendedData': ol.format.KML.ExtendedDataParser_, - 'MultiGeometry': ol.xml.makeObjectPropertySetter( - ol.format.KML.readMultiGeometry_, 'geometry'), - 'LineString': ol.xml.makeObjectPropertySetter( - ol.format.KML.readLineString_, 'geometry'), - 'LinearRing': ol.xml.makeObjectPropertySetter( - ol.format.KML.readLinearRing_, 'geometry'), - 'Point': ol.xml.makeObjectPropertySetter( - ol.format.KML.readPoint_, 'geometry'), - 'Polygon': ol.xml.makeObjectPropertySetter( - ol.format.KML.readPolygon_, 'geometry'), - 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), - 'StyleMap': ol.format.KML.PlacemarkStyleMapParser_, - 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), - 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_), - 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) - }, ol.xml.makeParsersNS( - ol.format.KML.GX_NAMESPACE_URIS_, { - 'MultiTrack': ol.xml.makeObjectPropertySetter( - ol.format.KML.readGxMultiTrack_, 'geometry'), - 'Track': ol.xml.makeObjectPropertySetter( - ol.format.KML.readGxTrack_, 'geometry') - } - )); +ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) + }); /** @@ -89329,11 +92147,18 @@ ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), - 'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), - 'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) +ol.format.GPX.TRK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'number': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'extensions': ol.format.GPX.parseExtensions_, + 'trkseg': ol.format.GPX.parseTrkSeg_ }); @@ -89342,9 +92167,9 @@ ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'SimpleData': ol.format.KML.SimpleDataParser_ +ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.format.GPX.parseTrkPt_ }); @@ -89353,12 +92178,10 @@ ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.STYLE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'IconStyle': ol.format.KML.IconStyleParser_, - 'LabelStyle': ol.format.KML.LabelStyleParser_, - 'LineStyle': ol.format.KML.LineStyleParser_, - 'PolyStyle': ol.format.KML.PolyStyleParser_ +ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime) }); @@ -89367,741 +92190,378 @@ ol.format.KML.STYLE_PARSERS_ = ol.xml.makeParsersNS( * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Pair': ol.format.KML.PairDataParser_ +ol.format.GPX.WPT_PARSERS_ = ol.xml.makeParsersNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime), + 'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'link': ol.format.GPX.parseLink_, + 'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'sat': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'ageofdgpsdata': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'dgpsid': + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_ }); /** - * @inheritDoc - */ -ol.format.KML.prototype.getExtensions = function() { - return ol.format.KML.EXTENSIONS_; -}; - - -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Array.<ol.Feature>|undefined} Features. - */ -ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - var localName = ol.xml.getLocalName(node); - goog.asserts.assert(localName == 'Document' || localName == 'Folder'); - // FIXME use scope somehow - var parsersNS = ol.xml.makeParsersNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this), - 'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this), - 'Style': goog.bind(this.readSharedStyle_, this), - 'StyleMap': goog.bind(this.readSharedStyleMap_, this) - }); - var features = ol.xml.pushParseAndPop(/** @type {Array.<ol.Feature>} */ ([]), - parsersNS, node, objectStack, this); - if (goog.isDef(features)) { - return features; - } else { - return undefined; - } -}; - - -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {Array.<ol.Feature>} features * @private - * @return {ol.Feature|undefined} Feature. */ -ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Placemark'); - var object = ol.xml.pushParseAndPop({'geometry': null}, - ol.format.KML.PLACEMARK_PARSERS_, node, objectStack); - if (!goog.isDef(object)) { - return undefined; - } - var feature = new ol.Feature(); - var id = node.getAttribute('id'); - if (!goog.isNull(id)) { - feature.setId(id); - } - var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); - if (goog.isDefAndNotNull(object.geometry)) { - ol.format.Feature.transformWithOptions(object.geometry, false, options); - } - feature.setProperties(object); - if (this.extractStyles_) { - feature.setStyle(this.featureStyleFunction_); +ol.format.GPX.prototype.handleReadExtensions_ = function(features) { + if (goog.isNull(features)) { + features = []; } - return feature; -}; - - -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Style'); - var id = node.getAttribute('id'); - if (!goog.isNull(id)) { - var style = ol.format.KML.readStyle_(node, objectStack); - if (goog.isDef(style)) { - var styleUri; - if (goog.isDefAndNotNull(node.baseURI)) { - styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString(); - } else { - styleUri = '#' + id; - } - this.sharedStyles_[styleUri] = style; + for (var i = 0, ii = features.length; i < ii; ++i) { + var feature = features[i]; + if (goog.isDef(this.readExtensions_)) { + var extensionsNode = feature.get('extensionsNode_') || null; + this.readExtensions_(feature, extensionsNode); } + feature.set('extensionsNode_', undefined); } }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'StyleMap'); - var id = node.getAttribute('id'); - if (goog.isNull(id)) { - return; - } - var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); - if (!goog.isDef(styleMapValue)) { - return; - } - var styleUri; - if (goog.isDefAndNotNull(node.baseURI)) { - styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString(); - } else { - styleUri = '#' + id; - } - this.sharedStyles_[styleUri] = styleMapValue; -}; - - -/** - * Read the first feature from a KML source. + * Read the first feature from a GPX source. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {Document|Node|Object|string} source Source. * @param {olx.format.ReadOptions=} opt_options Read options. * @return {ol.Feature} Feature. * @api stable */ -ol.format.KML.prototype.readFeature; +ol.format.GPX.prototype.readFeature; /** * @inheritDoc */ -ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { +ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { return null; } - goog.asserts.assert(node.localName == 'Placemark'); - var feature = this.readPlacemark_( - node, [this.getReadOptions(node, opt_options)]); - if (goog.isDef(feature)) { - return feature; - } else { + var featureReader = ol.format.GPX.FEATURE_READER_[node.localName]; + if (!goog.isDef(featureReader)) { + return null; + } + var feature = featureReader(node, [this.getReadOptions(node, opt_options)]); + if (!goog.isDef(feature)) { return null; } + this.handleReadExtensions_([feature]); + return feature; }; /** - * Read all features from a KML source. + * Read all features from a GPX source. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {Document|Node|Object|string} source Source. * @param {olx.format.ReadOptions=} opt_options Read options. * @return {Array.<ol.Feature>} Features. * @api stable */ -ol.format.KML.prototype.readFeatures; +ol.format.GPX.prototype.readFeatures; /** * @inheritDoc */ -ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { +ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + if (!goog.array.contains(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) { return []; } - var features; - var localName = ol.xml.getLocalName(node); - if (localName == 'Document' || localName == 'Folder') { - features = this.readDocumentOrFolder_( + if (node.localName == 'gpx') { + var features = ol.xml.pushParseAndPop( + /** @type {Array.<ol.Feature>} */ ([]), ol.format.GPX.GPX_PARSERS_, node, [this.getReadOptions(node, opt_options)]); if (goog.isDef(features)) { + this.handleReadExtensions_(features); return features; } else { - return []; - } - } else if (localName == 'Placemark') { - var feature = this.readPlacemark_( - node, [this.getReadOptions(node, opt_options)]); - if (goog.isDef(feature)) { - return [feature]; - } else { - return []; - } - } else if (localName == 'kml') { - features = []; - var n; - for (n = node.firstElementChild; !goog.isNull(n); - n = n.nextElementSibling) { - var fs = this.readFeaturesFromNode(n, opt_options); - if (goog.isDef(fs)) { - goog.array.extend(features, fs); - } - } - return features; - } else { - return []; - } -}; - - -/** - * @param {Document|Node|string} source Souce. - * @return {string|undefined} Name. - * @api stable - */ -ol.format.KML.prototype.readName = function(source) { - if (ol.xml.isDocument(source)) { - return this.readNameFromDocument(/** @type {Document} */ (source)); - } else if (ol.xml.isNode(source)) { - return this.readNameFromNode(/** @type {Node} */ (source)); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readNameFromDocument(doc); - } else { - goog.asserts.fail(); - return undefined; - } -}; - - -/** - * @param {Document} doc Document. - * @return {string|undefined} Name. - */ -ol.format.KML.prototype.readNameFromDocument = function(doc) { - var n; - for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - var name = this.readNameFromNode(n); - if (goog.isDef(name)) { - return name; - } - } - } - return undefined; -}; - - -/** - * @param {Node} node Node. - * @return {string|undefined} Name. - */ -ol.format.KML.prototype.readNameFromNode = function(node) { - var n; - for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { - if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && - n.localName == 'name') { - return ol.format.XSD.readString(n); - } - } - for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { - var localName = ol.xml.getLocalName(n); - if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && - (localName == 'Document' || - localName == 'Folder' || - localName == 'Placemark' || - localName == 'kml')) { - var name = this.readNameFromNode(n); - if (goog.isDef(name)) { - return name; - } - } - } - return undefined; -}; - - -/** - * Read the projection from a KML source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.proj.Projection} Projection. - * @api stable - */ -ol.format.KML.prototype.readProjection; - - -/** - * @inheritDoc - */ -ol.format.KML.prototype.readProjectionFromDocument = function(doc) { - return this.defaultDataProjection; -}; - - -/** - * @inheritDoc - */ -ol.format.KML.prototype.readProjectionFromNode = function(node) { - return this.defaultDataProjection; -}; - - -/** - * @param {Node} node Node to append a TextNode with the color to. - * @param {ol.Color|string} color Color. - * @private - */ -ol.format.KML.writeColorTextNode_ = function(node, color) { - var rgba = ol.color.asArray(color); - var opacity = (rgba.length == 4) ? rgba[3] : 1; - var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]]; - var i; - for (i = 0; i < 4; ++i) { - var hex = parseInt(abgr[i], 10).toString(16); - abgr[i] = (hex.length == 1) ? '0' + hex : hex; - } - ol.format.XSD.writeStringTextNode(node, abgr.join('')); -}; - - -/** - * @param {Node} node Node to append a TextNode with the coordinates to. - * @param {Array.<number>} coordinates Coordinates. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.KML.writeCoordinatesTextNode_ = - function(node, coordinates, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - - var layout = goog.object.get(context, 'layout'); - var stride = goog.object.get(context, 'stride'); - - var dimension; - if (layout == ol.geom.GeometryLayout.XY || - layout == ol.geom.GeometryLayout.XYM) { - dimension = 2; - } else if (layout == ol.geom.GeometryLayout.XYZ || - layout == ol.geom.GeometryLayout.XYZM) { - dimension = 3; - } else { - goog.asserts.fail(); - } - - var d, i; - var ii = coordinates.length; - var text = ''; - if (ii > 0) { - text += coordinates[0]; - for (d = 1; d < dimension; ++d) { - text += ',' + coordinates[d]; - } - for (i = stride; i < ii; i += stride) { - text += ' ' + coordinates[i]; - for (d = 1; d < dimension; ++d) { - text += ',' + coordinates[i + d]; - } - } - } - ol.format.XSD.writeStringTextNode(node, text); -}; - - -/** - * @param {Node} node Node. - * @param {Array.<ol.Feature>} features Features. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.KML.writeDocument_ = function(node, features, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_, - ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack); + return []; + } + } + return []; }; /** - * @param {Node} node Node. - * @param {Object} icon Icon object. - * @param {Array.<*>} objectStack Object stack. - * @private + * Read the projection from a GPX source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable */ -ol.format.KML.writeIcon_ = function(node, icon, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI]; - var values = ol.xml.makeSequence(icon, orderedKeys); - ol.xml.pushSerializeAndPop(context, - ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, - values, objectStack, orderedKeys); - orderedKeys = - ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]]; - values = ol.xml.makeSequence(icon, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_, - ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys); -}; +ol.format.GPX.prototype.readProjection; /** * @param {Node} node Node. - * @param {ol.style.Icon} style Icon style. - * @param {Array.<*>} objectStack Object stack. + * @param {string} value Value for the link's `href` attribute. + * @param {Array.<*>} objectStack Node stack. * @private */ -ol.format.KML.writeIconStyle_ = function(node, style, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - var properties = {}; - var src = style.getSrc(); - var size = style.getSize(); - var iconImageSize = style.getImageSize(); - var iconProperties = { - 'href': src - }; - - if (!goog.isNull(size)) { - goog.object.set(iconProperties, 'w', size[0]); - goog.object.set(iconProperties, 'h', size[1]); - var anchor = style.getAnchor(); // top-left - var origin = style.getOrigin(); // top-left - - if (!goog.isNull(origin) && !goog.isNull(iconImageSize) && - origin[0] !== 0 && origin[1] !== size[1]) { - goog.object.set(iconProperties, 'x', origin[0]); - goog.object.set(iconProperties, 'y', - iconImageSize[1] - (origin[1] + size[1])); - } - - if (!goog.isNull(anchor) && - anchor[0] !== 0 && anchor[1] !== size[1]) { - var /** @type {ol.format.KMLVec2_} */ hotSpot = { - x: anchor[0], - xunits: ol.style.IconAnchorUnits.PIXELS, - y: size[1] - anchor[1], - yunits: ol.style.IconAnchorUnits.PIXELS - }; - goog.object.set(properties, 'hotSpot', hotSpot); - } - } - - goog.object.set(properties, 'Icon', iconProperties); - - var scale = style.getScale(); - if (scale !== 1) { - goog.object.set(properties, 'scale', scale); - } - - var rotation = style.getRotation(); - if (rotation !== 0) { - goog.object.set(properties, 'heading', rotation); // 0-360 - } - - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +ol.format.GPX.writeLink_ = function(node, value, objectStack) { + node.setAttribute('href', value); + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var properties = context['properties']; + var link = [ + properties['linkText'], + properties['linkType'] + ]; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ ({node: node}), + ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + link, objectStack, ol.format.GPX.LINK_SEQUENCE_); }; /** * @param {Node} node Node. - * @param {ol.style.Text} style style. + * @param {ol.Coordinate} coordinate Coordinate. * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - var properties = {}; - var fill = style.getFill(); - if (!goog.isNull(fill)) { - goog.object.set(properties, 'color', fill.getColor()); - } - var scale = style.getScale(); - if (goog.isDef(scale) && scale !== 1) { - goog.object.set(properties, 'scale', scale); +ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var parentNode = context.node; + goog.asserts.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + var namespaceURI = parentNode.namespaceURI; + var properties = context['properties']; + //FIXME Projection handling + ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]); + ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]); + var geometryLayout = context['geometryLayout']; + /* jshint -W086 */ + switch (geometryLayout) { + case ol.geom.GeometryLayout.XYZM: + if (coordinate[3] !== 0) { + properties['time'] = coordinate[3]; + } + case ol.geom.GeometryLayout.XYZ: + if (coordinate[2] !== 0) { + properties['ele'] = coordinate[2]; + } + break; + case ol.geom.GeometryLayout.XYM: + if (coordinate[2] !== 0) { + properties['time'] = coordinate[2]; + } } - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = - ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI]; + /* jshint +W086 */ + var orderedKeys = ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI]; var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + ({node: node, 'properties': properties}), + ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); }; /** * @param {Node} node Node. - * @param {ol.style.Stroke} style style. + * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.writeLineStyle_ = function(node, style, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - var properties = { - 'color': style.getColor(), - 'width': style.getWidth() - }; +ol.format.GPX.writeRte_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var properties = feature.getProperties(); + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + geometry = /** @type {ol.geom.LineString} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + context['geometryLayout'] = geometry.getLayout(); + properties['rtept'] = geometry.getCoordinates(); + } var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI]; var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), + ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); }; /** * @param {Node} node Node. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.writeMultiGeometry_ = - function(node, geometry, objectStack) { - goog.asserts.assert( - (geometry instanceof ol.geom.MultiPoint) || - (geometry instanceof ol.geom.MultiLineString) || - (geometry instanceof ol.geom.MultiPolygon)); - /** @type {ol.xml.NodeStackItem} */ - var context = {node: node}; - var type = geometry.getType(); - /** @type {Array.<ol.geom.Geometry>} */ - var geometries; - /** @type {function(*, Array.<*>, string=): (Node|undefined)} */ - var factory; - if (type == ol.geom.GeometryType.MULTI_POINT) { - geometries = - (/** @type {ol.geom.MultiPoint} */ (geometry)).getPoints(); - factory = ol.format.KML.POINT_NODE_FACTORY_; - } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) { - geometries = - (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings(); - factory = ol.format.KML.LINE_STRING_NODE_FACTORY_; - } else if (type == ol.geom.GeometryType.MULTI_POLYGON) { - geometries = - (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons(); - factory = ol.format.KML.POLYGON_NODE_FACTORY_; - } else { - goog.asserts.fail(); +ol.format.GPX.writeTrk_ = function(node, feature, objectStack) { + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var properties = feature.getProperties(); + var context = {node: node, 'properties': properties}; + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, + 'geometry should be an ol.geom.MultiLineString'); + geometry = /** @type {ol.geom.MultiLineString} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + properties['trkseg'] = geometry.getLineStrings(); } - ol.xml.pushSerializeAndPop(context, - ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory, - geometries, objectStack); + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), + ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); }; /** * @param {Node} node Node. - * @param {ol.geom.LinearRing} linearRing Linear ring. + * @param {ol.geom.LineString} lineString LineString. * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - ol.xml.pushSerializeAndPop(context, - ol.format.KML.BOUNDARY_IS_SERIALIZERS_, - ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack); +ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) { + var context = {node: node, 'geometryLayout': lineString.getLayout(), + 'properties': {}}; + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ (context), + ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_, + lineString.getCoordinates(), objectStack); }; /** - * FIXME currently we do serialize arbitrary/custom feature properties - * (ExtendedData). * @param {Node} node Node. * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.KML.writePlacemark_ = function(node, feature, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - - // set id - if (goog.isDefAndNotNull(feature.getId())) { - node.setAttribute('id', feature.getId()); - } - - // serialize properties (properties unknown to KML are not serialized) - var properties = feature.getProperties(); - var styleFunction = feature.getStyleFunction(); - if (goog.isDef(styleFunction)) { - // FIXME the styles returned by the style function are supposed to be - // resolution-independent here - var styles = styleFunction.call(feature, 0); - if (!goog.isNull(styles) && styles.length > 0) { - goog.object.set(properties, 'Style', styles[0]); - var textStyle = styles[0].getText(); - if (!goog.isNull(textStyle)) { - goog.object.set(properties, 'name', textStyle.getText()); - } - } - } - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.KML.PLACEMARK_SEQUENCE_[parentNode.namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); - - // serialize geometry +ol.format.GPX.writeWpt_ = function(node, feature, objectStack) { var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + context['properties'] = feature.getProperties(); var geometry = feature.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - geometry = - ol.format.Feature.transformWithOptions(geometry, true, options); + if (goog.isDef(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + geometry = /** @type {ol.geom.Point} */ + (ol.format.Feature.transformWithOptions(geometry, true, options)); + context['geometryLayout'] = geometry.getLayout(); + ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack); } - ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, - ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack); }; /** - * @param {Node} node Node. - * @param {ol.geom.SimpleGeometry} geometry Geometry. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {Array.<string>} * @private */ -ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) { - goog.asserts.assert( - (geometry instanceof ol.geom.Point) || - (geometry instanceof ol.geom.LineString) || - (geometry instanceof ol.geom.LinearRing)); - var flatCoordinates = geometry.getFlatCoordinates(); - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - goog.object.set(context, 'layout', geometry.getLayout()); - goog.object.set(context, 'stride', geometry.getStride()); - ol.xml.pushSerializeAndPop(context, - ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_, - ol.format.KML.COORDINATES_NODE_FACTORY_, - [flatCoordinates], objectStack); -}; +ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type']; /** - * @param {Node} node Node. - * @param {ol.geom.Polygon} polygon Polygon. - * @param {Array.<*>} objectStack Object stack. + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.writePolygon_ = function(node, polygon, objectStack) { - goog.asserts.assertInstanceof(polygon, ol.geom.Polygon); - var linearRings = polygon.getLinearRings(); - goog.asserts.assert(linearRings.length > 0); - var outerRing = linearRings.shift(); - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - // inner rings - ol.xml.pushSerializeAndPop(context, - ol.format.KML.POLYGON_SERIALIZERS_, - ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_, - linearRings, objectStack); - // outer ring - ol.xml.pushSerializeAndPop(context, - ol.format.KML.POLYGON_SERIALIZERS_, - ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_, - [outerRing], objectStack); -}; +ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + }); /** - * @param {Node} node Node. - * @param {ol.style.Fill} style Style. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.KML.writePolyStyle_ = function(node, style, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_, - ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack); -}; +ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept' + ]); /** - * @param {Node} node Node to append a TextNode with the scale to. - * @param {number|undefined} scale Scale. + * @const + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.writeScaleTextNode_ = function(node, scale) { - ol.format.XSD.writeDecimalTextNode(node, scale * scale); -}; +ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeWptType_)) + }); /** - * @param {Node} node Node. - * @param {ol.style.Style} style Style. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.KML.writeStyle_ = function(node, style, objectStack) { - var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; - var properties = {}; - var fillStyle = style.getFill(); - var strokeStyle = style.getStroke(); - var imageStyle = style.getImage(); - var textStyle = style.getText(); - if (!goog.isNull(imageStyle)) { - goog.object.set(properties, 'IconStyle', imageStyle); - } - if (!goog.isNull(textStyle)) { - goog.object.set(properties, 'LabelStyle', textStyle); - } - if (!goog.isNull(strokeStyle)) { - goog.object.set(properties, 'LineStyle', strokeStyle); - } - if (!goog.isNull(fillStyle)) { - goog.object.set(properties, 'PolyStyle', fillStyle); - } - var parentNode = objectStack[objectStack.length - 1].node; - var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); -}; +ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg' + ]); /** - * @param {Node} node Node to append a TextNode with the Vec2 to. - * @param {ol.format.KMLVec2_} vec2 Vec2. + * @const + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.writeVec2_ = function(node, vec2) { - node.setAttribute('x', vec2.x); - node.setAttribute('y', vec2.y); - node.setAttribute('xunits', vec2.xunits); - node.setAttribute('yunits', vec2.yunits); -}; +ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'number': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender( + ol.format.GPX.writeTrkSeg_)) + }); /** * @const - * @type {Object.<string, Array.<string>>} + * @type {function(*, Array.<*>, string=): (Node|undefined)} * @private */ -ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'Document', 'Placemark' - ]); +ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt'); /** @@ -90109,21 +92569,53 @@ ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS( * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_), - 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) +ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_) }); /** * @const + * @type {Object.<string, Array.<string>>} + * @private + */ +ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, [ + 'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src', + 'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop', + 'ageofdgpsdata', 'dgpsid' + ]); + + +/** * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) +ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode), + 'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'geoidheight': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_), + 'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'sat': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode), + 'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'ageofdgpsdata': ol.xml.makeChildAppender( + ol.format.XSD.writeDecimalTextNode), + 'dgpsid': ol.xml.makeChildAppender( + ol.format.XSD.writeNonNegativeIntegerTextNode) }); @@ -90132,30 +92624,33 @@ ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS( * @type {Object.<string, string>} * @private */ -ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = { - 'Point': 'Point', - 'LineString': 'LineString', - 'LinearRing': 'LinearRing', - 'Polygon': 'Polygon', - 'MultiPoint': 'MultiGeometry', - 'MultiLineString': 'MultiGeometry', - 'MultiPolygon': 'MultiGeometry' +ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'wpt', + 'LineString': 'rte', + 'MultiLineString': 'trk' }; /** * @const - * @type {Object.<string, Array.<string>>} + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. * @private */ -ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'href' - ], - ol.xml.makeStructureNS( - ol.format.KML.GX_NAMESPACE_URIS_, [ - 'x', 'y', 'w', 'h' - ])); +ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + goog.asserts.assertInstanceof(value, ol.Feature, + 'value should be an ol.Feature'); + var geometry = value.getGeometry(); + if (goog.isDef(geometry)) { + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, + ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()]); + } +}; /** @@ -90163,13692 +92658,13364 @@ ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS( * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) - }, ol.xml.makeStructureNS( - ol.format.KML.GX_NAMESPACE_URIS_, { - 'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) - })); +ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.GPX.NAMESPACE_URIS_, { + 'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_), + 'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_), + 'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_) + }); + + +/** + * Encode an array of features in the GPX format. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Result. + * @api stable + */ +ol.format.GPX.prototype.writeFeatures; + + +/** + * Encode an array of features in the GPX format as an XML node. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api + */ +ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + //FIXME Serialize metadata + var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx'); + + ol.xml.pushSerializeAndPop(/** @type {ol.xml.NodeStackItem} */ + ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_, + ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]); + return gpx; +}; +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @const - * @type {Object.<string, Array.<string>>} - * @private + * @fileoverview Utilities for string newlines. + * @author nnaze@google.com (Nathan Naze) */ -ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'scale', 'heading', 'Icon', 'hotSpot' - ]); /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * Namespace for string utilities */ -ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_), - 'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), - 'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_), - 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) - }); +goog.provide('goog.string.newlines'); +goog.provide('goog.string.newlines.Line'); + +goog.require('goog.array'); /** - * @const - * @type {Object.<string, Array.<string>>} - * @private + * Splits a string into lines, properly handling universal newlines. + * @param {string} str String to split. + * @param {boolean=} opt_keepNewlines Whether to keep the newlines in the + * resulting strings. Defaults to false. + * @return {!Array<string>} String split into lines. */ -ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'color', 'scale' - ]); +goog.string.newlines.splitLines = function(str, opt_keepNewlines) { + var lines = goog.string.newlines.getLines(str); + return goog.array.map(lines, function(line) { + return opt_keepNewlines ? line.getFullLine() : line.getContent(); + }); +}; + /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * Line metadata class that records the start/end indicies of lines + * in a string. Can be used to implement common newline use cases such as + * splitLines() or determining line/column of an index in a string. + * Also implements methods to get line contents. + * + * Indexes are expressed as string indicies into string.substring(), inclusive + * at the start, exclusive at the end. + * + * Create an array of these with goog.string.newlines.getLines(). + * @param {string} string The original string. + * @param {number} startLineIndex The index of the start of the line. + * @param {number} endContentIndex The index of the end of the line, excluding + * newlines. + * @param {number} endLineIndex The index of the end of the line, index + * newlines. + * @constructor + * @struct + * @final */ -ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), - 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) - }); +goog.string.newlines.Line = function(string, startLineIndex, + endContentIndex, endLineIndex) { + /** + * The original string. + * @type {string} + */ + this.string = string; + /** + * Index of the start of the line. + * @type {number} + */ + this.startLineIndex = startLineIndex; -/** - * @const - * @type {Object.<string, Array.<string>>} - * @private - */ -ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'color', 'width' - ]); + /** + * Index of the end of the line, excluding any newline characters. + * Index is the first character after the line, suitable for + * String.substring(). + * @type {number} + */ + this.endContentIndex = endContentIndex; + /** + * Index of the end of the line, excluding any newline characters. + * Index is the first character after the line, suitable for + * String.substring(). + * @type {number} + */ -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private - */ -ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), - 'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) - }); + this.endLineIndex = endLineIndex; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @return {string} The content of the line, excluding any newline characters. */ -ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'LinearRing': ol.xml.makeChildAppender( - ol.format.KML.writePrimitiveGeometry_) - }); +goog.string.newlines.Line.prototype.getContent = function() { + return this.string.substring(this.startLineIndex, this.endContentIndex); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @return {string} The full line, including any newline characters. */ -ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'LineString': ol.xml.makeChildAppender( - ol.format.KML.writePrimitiveGeometry_), - 'Point': ol.xml.makeChildAppender( - ol.format.KML.writePrimitiveGeometry_), - 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_) - }); +goog.string.newlines.Line.prototype.getFullLine = function() { + return this.string.substring(this.startLineIndex, this.endLineIndex); +}; /** - * @const - * @type {Object.<string, Array.<string>>} - * @private + * @return {string} The newline characters, if any ('\n', \r', '\r\n', '', etc). */ -ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'name', 'open', 'visibility', 'address', 'phoneNumber', 'description', - 'styleUrl', 'Style' - ]); +goog.string.newlines.Line.prototype.getNewline = function() { + return this.string.substring(this.endContentIndex, this.endLineIndex); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * Splits a string into an array of line metadata. + * @param {string} str String to split. + * @return {!Array<!goog.string.newlines.Line>} Array of line metadata. */ -ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'MultiGeometry': ol.xml.makeChildAppender( - ol.format.KML.writeMultiGeometry_), - 'LineString': ol.xml.makeChildAppender( - ol.format.KML.writePrimitiveGeometry_), - 'LinearRing': ol.xml.makeChildAppender( - ol.format.KML.writePrimitiveGeometry_), - 'Point': ol.xml.makeChildAppender( - ol.format.KML.writePrimitiveGeometry_), - 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_), - 'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_), - 'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'description': ol.xml.makeChildAppender( - ol.format.XSD.writeStringTextNode), - 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode), - 'phoneNumber': ol.xml.makeChildAppender( - ol.format.XSD.writeStringTextNode), - 'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), - 'visibility': ol.xml.makeChildAppender( - ol.format.XSD.writeBooleanTextNode) - }); +goog.string.newlines.getLines = function(str) { + // We use the constructor because literals are evaluated only once in + // < ES 3.1. + // See http://www.mail-archive.com/es-discuss@mozilla.org/msg01796.html + var re = RegExp('\r\n|\r|\n', 'g'); + var sliceIndex = 0; + var result; + var lines = []; + while (result = re.exec(str)) { + var line = new goog.string.newlines.Line( + str, sliceIndex, result.index, result.index + result[0].length); + lines.push(line); -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private - */ -ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'coordinates': ol.xml.makeChildAppender( - ol.format.KML.writeCoordinatesTextNode_) - }); + // remember where to start the slice from + sliceIndex = re.lastIndex; + } + + // If the string does not end with a newline, add the last line. + if (sliceIndex < str.length) { + var line = new goog.string.newlines.Line( + str, sliceIndex, str.length, str.length); + lines.push(line); + } + return lines; +}; + +goog.provide('ol.format.TextFeature'); + +goog.require('goog.asserts'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private - */ -ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'outerBoundaryIs': ol.xml.makeChildAppender( - ol.format.KML.writeBoundaryIs_), - 'innerBoundaryIs': ol.xml.makeChildAppender( - ol.format.KML.writeBoundaryIs_) - }); /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @classdesc + * Abstract base class; normally only used for creating subclasses and not + * instantiated in apps. + * Base class for text feature formats. + * + * @constructor + * @extends {ol.format.Feature} */ -ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_) - }); +ol.format.TextFeature = function() { + goog.base(this); +}; +goog.inherits(ol.format.TextFeature, ol.format.Feature); /** - * @const - * @type {Object.<string, Array.<string>>} + * @param {Document|Node|Object|string} source Source. * @private + * @return {string} Text. */ -ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, [ - 'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle' - ]); +ol.format.TextFeature.prototype.getText_ = function(source) { + if (goog.isString(source)) { + return source; + } else { + goog.asserts.fail(); + return ''; + } +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} - * @private + * @inheritDoc */ -ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( - ol.format.KML.NAMESPACE_URIS_, { - 'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_), - 'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_), - 'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_), - 'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_) - }); +ol.format.TextFeature.prototype.getType = function() { + return ol.format.FormatType.TEXT; +}; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. - * @private + * @inheritDoc */ -ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { - return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0], - 'gx:' + opt_nodeName); +ol.format.TextFeature.prototype.readFeature = function(source, opt_options) { + return this.readFeatureFromText( + this.getText_(source), this.adaptOptions(opt_options)); }; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. - * @private + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.Feature} Feature. */ -ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack, - opt_nodeName) { - goog.asserts.assertInstanceof(value, ol.Feature); - var parentNode = objectStack[objectStack.length - 1].node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark'); -}; +ol.format.TextFeature.prototype.readFeatureFromText = goog.abstractMethod; /** - * @const - * @param {*} value Value. - * @param {Array.<*>} objectStack Object stack. - * @param {string=} opt_nodeName Node name. - * @return {Node|undefined} Node. - * @private + * @inheritDoc */ -ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, - opt_nodeName) { - if (goog.isDefAndNotNull(value)) { - goog.asserts.assertInstanceof(value, ol.geom.Geometry); - var parentNode = objectStack[objectStack.length - 1].node; - goog.asserts.assert(ol.xml.isNode(parentNode)); - return ol.xml.createElementNS(parentNode.namespaceURI, - ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[value.getType()]); - } +ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) { + return this.readFeaturesFromText( + this.getText_(source), this.adaptOptions(opt_options)); }; /** - * A factory for creating coordinates nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {Array.<ol.Feature>} Features. */ -ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color'); +ol.format.TextFeature.prototype.readFeaturesFromText = goog.abstractMethod; /** - * A factory for creating coordinates nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @inheritDoc */ -ol.format.KML.COORDINATES_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('coordinates'); +ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) { + return this.readGeometryFromText( + this.getText_(source), this.adaptOptions(opt_options)); +}; /** - * A factory for creating innerBoundaryIs nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @param {string} text Text. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @protected + * @return {ol.geom.Geometry} Geometry. */ -ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('innerBoundaryIs'); +ol.format.TextFeature.prototype.readGeometryFromText = goog.abstractMethod; /** - * A factory for creating Point nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @inheritDoc */ -ol.format.KML.POINT_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('Point'); +ol.format.TextFeature.prototype.readProjection = function(source) { + return this.readProjectionFromText(this.getText_(source)); +}; /** - * A factory for creating LineString nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @param {string} text Text. + * @protected + * @return {ol.proj.Projection} Projection. */ -ol.format.KML.LINE_STRING_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('LineString'); +ol.format.TextFeature.prototype.readProjectionFromText = function(text) { + return this.defaultDataProjection; +}; /** - * A factory for creating LinearRing nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @inheritDoc */ -ol.format.KML.LINEAR_RING_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('LinearRing'); +ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) { + return this.writeFeatureText(feature, this.adaptOptions(opt_options)); +}; /** - * A factory for creating Polygon nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @param {ol.Feature} feature Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. */ -ol.format.KML.POLYGON_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('Polygon'); +ol.format.TextFeature.prototype.writeFeatureText = goog.abstractMethod; /** - * A factory for creating outerBoundaryIs nodes. - * @const - * @type {function(*, Array.<*>, string=): (Node|undefined)} - * @private + * @inheritDoc */ -ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ = - ol.xml.makeSimpleNodeFactory('outerBoundaryIs'); +ol.format.TextFeature.prototype.writeFeatures = function( + features, opt_options) { + return this.writeFeaturesText(features, this.adaptOptions(opt_options)); +}; /** - * Encode an array of features in the KML format. - * - * @function * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {string} Result. - * @api stable + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. */ -ol.format.KML.prototype.writeFeatures; +ol.format.TextFeature.prototype.writeFeaturesText = goog.abstractMethod; /** - * Encode an array of features in the KML format as an XML node. - * - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Options. - * @return {Node} Node. - * @api + * @inheritDoc */ -ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) { - opt_options = this.adaptOptions(opt_options); - var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml'); - var xmlnsUri = 'http://www.w3.org/2000/xmlns/'; - var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance'; - ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx', - ol.format.KML.GX_NAMESPACE_URIS_[0]); - ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri); - ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation', - ol.format.KML.SCHEMA_LOCATION_); - - var /** @type {ol.xml.NodeStackItem} */ context = {node: kml}; - var properties = {}; - if (features.length > 1) { - goog.object.set(properties, 'Document', features); - } else if (features.length == 1) { - goog.object.set(properties, 'Placemark', features[0]); - } - var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI]; - var values = ol.xml.makeSequence(properties, orderedKeys); - ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_, - ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys); - return kml; +ol.format.TextFeature.prototype.writeGeometry = function( + geometry, opt_options) { + return this.writeGeometryText(geometry, this.adaptOptions(opt_options)); }; -// FIXME add typedef for stack state objects -goog.provide('ol.format.OSMXML'); -goog.require('goog.array'); +/** + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @protected + * @return {string} Text. + */ +ol.format.TextFeature.prototype.writeGeometryText = goog.abstractMethod; + +goog.provide('ol.format.IGC'); +goog.provide('ol.format.IGCZ'); + goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.string.newlines'); goog.require('ol.Feature'); goog.require('ol.format.Feature'); -goog.require('ol.format.XMLFeature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryLayout'); goog.require('ol.geom.LineString'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); goog.require('ol.proj'); -goog.require('ol.xml'); - /** - * @classdesc - * Feature format for reading data in the - * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML). - * - * @constructor - * @extends {ol.format.XMLFeature} - * @api stable + * IGC altitude/z. One of 'barometric', 'gps', 'none'. + * @enum {string} + * @api */ -ol.format.OSMXML = function() { - goog.base(this); - - /** - * @inheritDoc - */ - this.defaultDataProjection = ol.proj.get('EPSG:4326'); +ol.format.IGCZ = { + BAROMETRIC: 'barometric', + GPS: 'gps', + NONE: 'none' }; -goog.inherits(ol.format.OSMXML, ol.format.XMLFeature); + /** - * @const - * @type {Array.<string>} - * @private + * @classdesc + * Feature format for `*.igc` flight recording files. + * + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.IGCOptions=} opt_options Options. + * @api */ -ol.format.OSMXML.EXTENSIONS_ = ['.osm']; +ol.format.IGC = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -/** - * @inheritDoc - */ -ol.format.OSMXML.prototype.getExtensions = function() { - return ol.format.OSMXML.EXTENSIONS_; -}; + goog.base(this); + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.OSMXML.readNode_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'node'); - var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); - var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); - var id = node.getAttribute('id'); - var coordinates = /** @type {Array.<number>} */ ([ - parseFloat(node.getAttribute('lon')), - parseFloat(node.getAttribute('lat')) - ]); - goog.object.set(state.nodes, id, coordinates); + /** + * @private + * @type {ol.format.IGCZ} + */ + this.altitudeMode_ = goog.isDef(options.altitudeMode) ? + options.altitudeMode : ol.format.IGCZ.NONE; - var values = ol.xml.pushParseAndPop({ - tags: {} - }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack); - if (!goog.object.isEmpty(values.tags)) { - var geometry = new ol.geom.Point(coordinates); - ol.format.Feature.transformWithOptions(geometry, false, options); - var feature = new ol.Feature(geometry); - feature.setId(id); - feature.setProperties(values.tags); - state.features.push(feature); - } }; +goog.inherits(ol.format.IGC, ol.format.TextFeature); /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {Array.<string>} * @private */ -ol.format.OSMXML.readWay_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'way'); - var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); - var id = node.getAttribute('id'); - var values = ol.xml.pushParseAndPop({ - ndrefs: [], - tags: {} - }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack); - var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); - var flatCoordinates = /** @type {Array.<number>} */ ([]); - for (var i = 0, ii = values.ndrefs.length; i < ii; i++) { - var point = goog.object.get(state.nodes, values.ndrefs[i]); - goog.array.extend(flatCoordinates, point); - } - var geometry; - if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) { - // closed way - geometry = new ol.geom.Polygon(null); - geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, - [flatCoordinates.length]); - } else { - geometry = new ol.geom.LineString(null); - geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); - } - ol.format.Feature.transformWithOptions(geometry, false, options); - var feature = new ol.Feature(geometry); - feature.setId(id); - feature.setProperties(values.tags); - state.features.push(feature); -}; +ol.format.IGC.EXTENSIONS_ = ['.igc']; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {RegExp} * @private - * @return {ol.Feature|undefined} Track. */ -ol.format.OSMXML.readNd_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'nd'); - var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); - values.ndrefs.push(node.getAttribute('ref')); -}; +ol.format.IGC.B_RECORD_RE_ = + /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @const + * @type {RegExp} * @private - * @return {ol.Feature|undefined} Track. */ -ol.format.OSMXML.readTag_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'tag'); - var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); - goog.object.set(values.tags, node.getAttribute('k'), node.getAttribute('v')); -}; +ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/; /** * @const + * @type {RegExp} * @private - * @type {Array.<string>} */ -ol.format.OSMXML.NAMESPACE_URIS_ = [ - null -]; +ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @inheritDoc */ -ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OSMXML.NAMESPACE_URIS_, { - 'nd': ol.format.OSMXML.readNd_, - 'tag': ol.format.OSMXML.readTag_ - }); +ol.format.IGC.prototype.getExtensions = function() { + return ol.format.IGC.EXTENSIONS_; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Read the feature from the IGC source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api */ -ol.format.OSMXML.PARSERS_ = ol.xml.makeParsersNS( - ol.format.OSMXML.NAMESPACE_URIS_, { - 'node': ol.format.OSMXML.readNode_, - 'way': ol.format.OSMXML.readWay_ - }); +ol.format.IGC.prototype.readFeature; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @inheritDoc */ -ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OSMXML.NAMESPACE_URIS_, { - 'tag': ol.format.OSMXML.readTag_ - }); +ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) { + var altitudeMode = this.altitudeMode_; + var lines = goog.string.newlines.splitLines(text); + /** @type {Object.<string, string>} */ + var properties = {}; + var flatCoordinates = []; + var year = 2000; + var month = 0; + var day = 1; + var i, ii; + for (i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + var m; + if (line.charAt(0) == 'B') { + m = ol.format.IGC.B_RECORD_RE_.exec(line); + if (m) { + var hour = parseInt(m[1], 10); + var minute = parseInt(m[2], 10); + var second = parseInt(m[3], 10); + var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000; + if (m[6] == 'S') { + y = -y; + } + var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000; + if (m[9] == 'W') { + x = -x; + } + flatCoordinates.push(x, y); + if (altitudeMode != ol.format.IGCZ.NONE) { + var z; + if (altitudeMode == ol.format.IGCZ.GPS) { + z = parseInt(m[11], 10); + } else if (altitudeMode == ol.format.IGCZ.BAROMETRIC) { + z = parseInt(m[12], 10); + } else { + goog.asserts.fail(); + z = 0; + } + flatCoordinates.push(z); + } + var dateTime = Date.UTC(year, month, day, hour, minute, second); + flatCoordinates.push(dateTime / 1000); + } + } else if (line.charAt(0) == 'H') { + m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line); + if (m) { + day = parseInt(m[1], 10); + month = parseInt(m[2], 10) - 1; + year = 2000 + parseInt(m[3], 10); + } else { + m = ol.format.IGC.H_RECORD_RE_.exec(line); + if (m) { + properties[m[1]] = goog.string.trim(m[2]); + m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line); + } + } + } + } + if (flatCoordinates.length === 0) { + return null; + } + var lineString = new ol.geom.LineString(null); + var layout = altitudeMode == ol.format.IGCZ.NONE ? + ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM; + lineString.setFlatCoordinates(layout, flatCoordinates); + var feature = new ol.Feature(ol.format.Feature.transformWithOptions( + lineString, false, opt_options)); + feature.setProperties(properties); + return feature; +}; /** - * Read all features from an OSM source. + * Read the feature from the source. As IGC sources contain a single + * feature, this will return the feature in an array. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {Document|Node|Object|string} source Source. * @param {olx.format.ReadOptions=} opt_options Read options. * @return {Array.<ol.Feature>} Features. - * @api stable + * @api */ -ol.format.OSMXML.prototype.readFeatures; +ol.format.IGC.prototype.readFeatures; /** * @inheritDoc */ -ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - var options = this.getReadOptions(node, opt_options); - if (node.localName == 'osm') { - var state = ol.xml.pushParseAndPop({ - nodes: {}, - features: [] - }, ol.format.OSMXML.PARSERS_, node, [options]); - if (goog.isDef(state.features)) { - return state.features; - } +ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) { + var feature = this.readFeatureFromText(text, opt_options); + if (!goog.isNull(feature)) { + return [feature]; + } else { + return []; } - return []; }; /** - * Read the projection from an OSM source. + * Read the projection from the IGC source. * * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. + * @param {Document|Node|Object|string} source Source. * @return {ol.proj.Projection} Projection. - * @api stable + * @api */ -ol.format.OSMXML.prototype.readProjection; +ol.format.IGC.prototype.readProjection; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @inheritDoc + * @fileoverview Class for parsing and formatting URIs. + * + * Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to + * create a new instance of the goog.Uri object from Uri parts. + * + * e.g: <code>var myUri = new goog.Uri(window.location);</code> + * + * Implements RFC 3986 for parsing/formatting URIs. + * http://www.ietf.org/rfc/rfc3986.txt + * + * Some changes have been made to the interface (more like .NETs), though the + * internal representation is now of un-encoded parts, this will change the + * behavior slightly. + * */ -ol.format.OSMXML.prototype.readProjectionFromDocument = function(doc) { - return this.defaultDataProjection; -}; + +goog.provide('goog.Uri'); +goog.provide('goog.Uri.QueryData'); + +goog.require('goog.array'); +goog.require('goog.string'); +goog.require('goog.structs'); +goog.require('goog.structs.Map'); +goog.require('goog.uri.utils'); +goog.require('goog.uri.utils.ComponentIndex'); +goog.require('goog.uri.utils.StandardQueryParam'); + /** - * @inheritDoc + * This class contains setters and getters for the parts of the URI. + * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part + * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the + * decoded path, <code>/foo bar</code>. + * + * Reserved characters (see RFC 3986 section 2.2) can be present in + * their percent-encoded form in scheme, domain, and path URI components and + * will not be auto-decoded. For example: + * <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will + * return <code>relative/path%2fto/resource</code>. + * + * The constructor accepts an optional unparsed, raw URI string. The parser + * is relaxed, so special characters that aren't escaped but don't cause + * ambiguities will not cause parse failures. + * + * All setters return <code>this</code> and so may be chained, a la + * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>. + * + * @param {*=} opt_uri Optional string URI to parse + * (use goog.Uri.create() to create a URI from parts), or if + * a goog.Uri is passed, a clone is created. + * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore + * the case of the parameter name. + * + * @constructor + * @struct */ -ol.format.OSMXML.prototype.readProjectionFromNode = function(node) { - return this.defaultDataProjection; +goog.Uri = function(opt_uri, opt_ignoreCase) { + // Parse in the uri string + var m; + if (opt_uri instanceof goog.Uri) { + this.ignoreCase_ = goog.isDef(opt_ignoreCase) ? + opt_ignoreCase : opt_uri.getIgnoreCase(); + this.setScheme(opt_uri.getScheme()); + this.setUserInfo(opt_uri.getUserInfo()); + this.setDomain(opt_uri.getDomain()); + this.setPort(opt_uri.getPort()); + this.setPath(opt_uri.getPath()); + this.setQueryData(opt_uri.getQueryData().clone()); + this.setFragment(opt_uri.getFragment()); + } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) { + this.ignoreCase_ = !!opt_ignoreCase; + + // Set the parts -- decoding as we do so. + // COMPATABILITY NOTE - In IE, unmatched fields may be empty strings, + // whereas in other browsers they will be undefined. + this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true); + this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true); + this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true); + this.setPort(m[goog.uri.utils.ComponentIndex.PORT]); + this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true); + this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true); + this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true); + + } else { + this.ignoreCase_ = !!opt_ignoreCase; + this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_); + } }; -goog.provide('ol.format.XLink'); + +/** + * If true, we preserve the type of query parameters set programmatically. + * + * This means that if you set a parameter to a boolean, and then call + * getParameterValue, you will get a boolean back. + * + * If false, we will coerce parameters to strings, just as they would + * appear in real URIs. + * + * TODO(nicksantos): Remove this once people have time to fix all tests. + * + * @type {boolean} + */ +goog.Uri.preserveParameterTypesCompatibilityFlag = false; /** - * @const + * Parameter name added to stop caching. * @type {string} */ -ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; +goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM; /** - * @param {Node} node Node. - * @return {boolean|undefined} Boolean. + * Scheme such as "http". + * @type {string} + * @private */ -ol.format.XLink.readHref = function(node) { - return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href'); -}; - -goog.provide('ol.format.XML'); +goog.Uri.prototype.scheme_ = ''; -goog.require('goog.asserts'); -goog.require('ol.xml'); +/** + * User credentials in the form "username:password". + * @type {string} + * @private + */ +goog.Uri.prototype.userInfo_ = ''; /** - * @classdesc - * Generic format for reading non-feature XML data - * - * @constructor + * Domain part, e.g. "www.google.com". + * @type {string} + * @private */ -ol.format.XML = function() { -}; +goog.Uri.prototype.domain_ = ''; /** - * @param {Document|Node|string} source Source. - * @return {Object} + * Port, e.g. 8080. + * @type {?number} + * @private */ -ol.format.XML.prototype.read = function(source) { - if (ol.xml.isDocument(source)) { - return this.readFromDocument(/** @type {Document} */ (source)); - } else if (ol.xml.isNode(source)) { - return this.readFromNode(/** @type {Node} */ (source)); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readFromDocument(doc); - } else { - goog.asserts.fail(); - return null; - } -}; +goog.Uri.prototype.port_ = null; /** - * @param {Document} doc Document. - * @return {Object} + * Path, e.g. "/tests/img.png". + * @type {string} + * @private */ -ol.format.XML.prototype.readFromDocument = goog.abstractMethod; +goog.Uri.prototype.path_ = ''; /** - * @param {Node} node Node. - * @return {Object} + * Object representing query data. + * @type {!goog.Uri.QueryData} + * @private */ -ol.format.XML.prototype.readFromNode = goog.abstractMethod; +goog.Uri.prototype.queryData_; -goog.provide('ol.format.OWS'); -goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('ol.format.XLink'); -goog.require('ol.format.XML'); -goog.require('ol.format.XSD'); -goog.require('ol.xml'); +/** + * The fragment without the #. + * @type {string} + * @private + */ +goog.Uri.prototype.fragment_ = ''; +/** + * Whether or not this Uri should be treated as Read Only. + * @type {boolean} + * @private + */ +goog.Uri.prototype.isReadOnly_ = false; + /** - * @constructor - * @extends {ol.format.XML} + * Whether or not to ignore case when comparing query params. + * @type {boolean} + * @private */ -ol.format.OWS = function() { - goog.base(this); -}; -goog.inherits(ol.format.OWS, ol.format.XML); +goog.Uri.prototype.ignoreCase_ = false; /** - * @param {Document} doc Document. - * @return {Object} OWS object. + * @return {string} The string form of the url. + * @override */ -ol.format.OWS.prototype.readFromDocument = function(doc) { - goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT); - for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - return this.readFromNode(n); +goog.Uri.prototype.toString = function() { + var out = []; + + var scheme = this.getScheme(); + if (scheme) { + out.push(goog.Uri.encodeSpecialChars_( + scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), ':'); + } + + var domain = this.getDomain(); + if (domain) { + out.push('//'); + + var userInfo = this.getUserInfo(); + if (userInfo) { + out.push(goog.Uri.encodeSpecialChars_( + userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), '@'); + } + + out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain))); + + var port = this.getPort(); + if (port != null) { + out.push(':', String(port)); + } + } + + var path = this.getPath(); + if (path) { + if (this.hasDomain() && path.charAt(0) != '/') { + out.push('/'); } + out.push(goog.Uri.encodeSpecialChars_( + path, + path.charAt(0) == '/' ? + goog.Uri.reDisallowedInAbsolutePath_ : + goog.Uri.reDisallowedInRelativePath_, + true)); } - return null; -}; + var query = this.getEncodedQuery(); + if (query) { + out.push('?', query); + } -/** - * @param {Node} node Node. - * @return {Object} OWS object. - */ -ol.format.OWS.prototype.readFromNode = function(node) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - var owsObject = ol.xml.pushParseAndPop({}, - ol.format.OWS.PARSERS_, node, []); - return goog.isDef(owsObject) ? owsObject : null; + var fragment = this.getFragment(); + if (fragment) { + out.push('#', goog.Uri.encodeSpecialChars_( + fragment, goog.Uri.reDisallowedInFragment_)); + } + return out.join(''); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * Resolves the given relative URI (a goog.Uri object), using the URI + * represented by this instance as the base URI. + * + * There are several kinds of relative URIs:<br> + * 1. foo - replaces the last part of the path, the whole query and fragment<br> + * 2. /foo - replaces the the path, the query and fragment<br> + * 3. //foo - replaces everything from the domain on. foo is a domain name<br> + * 4. ?foo - replace the query and fragment<br> + * 5. #foo - replace the fragment only + * + * Additionally, if relative URI has a non-empty path, all ".." and "." + * segments will be resolved, as described in RFC 3986. + * + * @param {!goog.Uri} relativeUri The relative URI to resolve. + * @return {!goog.Uri} The resolved URI. */ -ol.format.OWS.readAddress_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Address'); - return ol.xml.pushParseAndPop({}, - ol.format.OWS.ADDRESS_PARSERS_, node, objectStack); -}; +goog.Uri.prototype.resolve = function(relativeUri) { + var absoluteUri = this.clone(); -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} - */ -ol.format.OWS.readAllowedValues_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'AllowedValues'); - return ol.xml.pushParseAndPop({}, - ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack); -}; + // we satisfy these conditions by looking for the first part of relativeUri + // that is not blank and applying defaults to the rest + var overridden = relativeUri.hasScheme(); -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} - */ -ol.format.OWS.readConstraint_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Constraint'); - var object = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(object)); - var name = node.getAttribute('name'); - var value = ol.xml.pushParseAndPop({}, - ol.format.OWS.CONSTRAINT_PARSERS_, node, - objectStack); - if (!goog.isDef(value)) { - return undefined; + if (overridden) { + absoluteUri.setScheme(relativeUri.getScheme()); + } else { + overridden = relativeUri.hasUserInfo(); + } + + if (overridden) { + absoluteUri.setUserInfo(relativeUri.getUserInfo()); + } else { + overridden = relativeUri.hasDomain(); } - if (!goog.isDef(object.constraints)) { - object.constraints = {}; + + if (overridden) { + absoluteUri.setDomain(relativeUri.getDomain()); + } else { + overridden = relativeUri.hasPort(); } - object.constraints[name] = value; -}; + var path = relativeUri.getPath(); + if (overridden) { + absoluteUri.setPort(relativeUri.getPort()); + } else { + overridden = relativeUri.hasPath(); + if (overridden) { + // resolve path properly + if (path.charAt(0) != '/') { + // path is relative + if (this.hasDomain() && !this.hasPath()) { + // RFC 3986, section 5.2.3, case 1 + path = '/' + path; + } else { + // RFC 3986, section 5.2.3, case 2 + var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/'); + if (lastSlashIndex != -1) { + path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path; + } + } + } + path = goog.Uri.removeDotSegments(path); + } + } + if (overridden) { + absoluteUri.setPath(path); + } else { + overridden = relativeUri.hasQuery(); + } -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} - */ -ol.format.OWS.readContactInfo_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ContactInfo'); - return ol.xml.pushParseAndPop({}, - ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack); -}; + if (overridden) { + absoluteUri.setQueryData(relativeUri.getDecodedQuery()); + } else { + overridden = relativeUri.hasFragment(); + } + if (overridden) { + absoluteUri.setFragment(relativeUri.getFragment()); + } -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} - */ -ol.format.OWS.readDcp_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'DCP'); - return ol.xml.pushParseAndPop({}, - ol.format.OWS.DCP_PARSERS_, node, objectStack); + return absoluteUri; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * Clones the URI instance. + * @return {!goog.Uri} New instance of the URI object. */ -ol.format.OWS.readGet_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Get'); - var object = objectStack[objectStack.length - 1]; - var url = ol.format.XLink.readHref(node); - goog.asserts.assert(goog.isObject(object)); - var value = ol.xml.pushParseAndPop({'url': url}, - ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack); - if (!goog.isDef(value)) { - return undefined; - } - var get = goog.object.get(object, 'get'); - if (!goog.isDef(get)) { - goog.object.set(object, 'get', [value]); - }else { - goog.asserts.assert(goog.isArray(get)); - get.push(value); - } - +goog.Uri.prototype.clone = function() { + return new goog.Uri(this); }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * @return {string} The encoded scheme/protocol for the URI. */ -ol.format.OWS.readHttp_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'HTTP'); - return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_, - node, objectStack); +goog.Uri.prototype.getScheme = function() { + return this.scheme_; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * Sets the scheme/protocol. + * @param {string} newScheme New scheme value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.readOperation_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Operation'); - var name = node.getAttribute('name'); - var value = ol.xml.pushParseAndPop({}, - ol.format.OWS.OPERATION_PARSERS_, node, objectStack); - if (!goog.isDef(value)) { - return undefined; - } - var object = /** @type {Object} */ - (objectStack[objectStack.length - 1]); - goog.asserts.assert(goog.isObject(object)); - goog.object.set(object, name, value); +goog.Uri.prototype.setScheme = function(newScheme, opt_decode) { + this.enforceReadOnly(); + this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) : + newScheme; + // remove an : at the end of the scheme so somebody can pass in + // window.location.protocol + if (this.scheme_) { + this.scheme_ = this.scheme_.replace(/:$/, ''); + } + return this; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * @return {boolean} Whether the scheme has been set. */ -ol.format.OWS.readOperationsMetadata_ = function(node, - objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'OperationsMetadata'); - return ol.xml.pushParseAndPop({}, - ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node, - objectStack); +goog.Uri.prototype.hasScheme = function() { + return !!this.scheme_; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * @return {string} The decoded user info. */ -ol.format.OWS.readPhone_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Phone'); - return ol.xml.pushParseAndPop({}, - ol.format.OWS.PHONE_PARSERS_, node, objectStack); +goog.Uri.prototype.getUserInfo = function() { + return this.userInfo_; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * Sets the userInfo. + * @param {string} newUserInfo New userInfo value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.readServiceIdentification_ = function(node, - objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ServiceIdentification'); - return ol.xml.pushParseAndPop( - {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node, - objectStack); +goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) { + this.enforceReadOnly(); + this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) : + newUserInfo; + return this; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * @return {boolean} Whether the user info has been set. */ -ol.format.OWS.readServiceContact_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ServiceContact'); - return ol.xml.pushParseAndPop( - {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node, - objectStack); +goog.Uri.prototype.hasUserInfo = function() { + return !!this.userInfo_; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * @return {string} The decoded domain. */ -ol.format.OWS.readServiceProvider_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ServiceProvider'); - return ol.xml.pushParseAndPop( - {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node, - objectStack); +goog.Uri.prototype.getDomain = function() { + return this.domain_; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} + * Sets the domain. + * @param {string} newDomain New domain value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.readValue_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Value'); - var object = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(object)); - var key = ol.format.XSD.readString(node); - if (!goog.isDef(key)) { - return undefined; - } - goog.object.set(object, key, true); +goog.Uri.prototype.setDomain = function(newDomain, opt_decode) { + this.enforceReadOnly(); + this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) : + newDomain; + return this; }; /** - * @const - * @type {Array.<string>} - * @private + * @return {boolean} Whether the domain has been set. */ -ol.format.OWS.NAMESPACE_URIS_ = [ - null, - 'http://www.opengis.net/ows/1.1' -]; +goog.Uri.prototype.hasDomain = function() { + return !!this.domain_; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {?number} The port number. */ -ol.format.OWS.PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'ServiceIdentification': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readServiceIdentification_, - 'serviceIdentification'), - 'ServiceProvider': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readServiceProvider_, - 'serviceProvider'), - 'OperationsMetadata': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readOperationsMetadata_, - 'operationsMetadata') - }); +goog.Uri.prototype.getPort = function() { + return this.port_; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Sets the port number. + * @param {*} newPort Port number. Will be explicitly casted to a number. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'DeliveryPoint': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'deliveryPoint'), - 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'city'), - 'AdministrativeArea': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'administrativeArea'), - 'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'postalCode'), - 'Country': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'country'), - 'ElectronicMailAddress': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'electronicMailAddress') - }); +goog.Uri.prototype.setPort = function(newPort) { + this.enforceReadOnly(); + if (newPort) { + newPort = Number(newPort); + if (isNaN(newPort) || newPort < 0) { + throw Error('Bad port number ' + newPort); + } + this.port_ = newPort; + } else { + this.port_ = null; + } -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Value': ol.format.OWS.readValue_ - }); + return this; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {boolean} Whether the port has been set. */ -ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'AllowedValues': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readAllowedValues_, 'allowedValues' - ) - }); +goog.Uri.prototype.hasPort = function() { + return this.port_ != null; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {string} The decoded path. */ -ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Phone': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readPhone_, 'phone'), - 'Address': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readAddress_, 'address') - }); +goog.Uri.prototype.getPath = function() { + return this.path_; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Sets the path. + * @param {string} newPath New path value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.DCP_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'HTTP': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readHttp_, 'http') - }); +goog.Uri.prototype.setPath = function(newPath, opt_decode) { + this.enforceReadOnly(); + this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath; + return this; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {boolean} Whether the path has been set. */ -ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Get': ol.format.OWS.readGet_, - 'Post': undefined // TODO - }); +goog.Uri.prototype.hasPath = function() { + return !!this.path_; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {boolean} Whether the query string has been set. */ -ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'DCP': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readDcp_, 'dcp') - }); +goog.Uri.prototype.hasQuery = function() { + return this.queryData_.toString() !== ''; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Sets the query data. + * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * Applies only if queryData is a string. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Operation': ol.format.OWS.readOperation_ - }); +goog.Uri.prototype.setQueryData = function(queryData, opt_decode) { + this.enforceReadOnly(); + if (queryData instanceof goog.Uri.QueryData) { + this.queryData_ = queryData; + this.queryData_.setIgnoreCase(this.ignoreCase_); + } else { + if (!opt_decode) { + // QueryData accepts encoded query string, so encode it if + // opt_decode flag is not true. + queryData = goog.Uri.encodeSpecialChars_(queryData, + goog.Uri.reDisallowedInQuery_); + } + this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_); + } -/** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private - */ -ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'voice'), - 'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'facsimile') - }); + return this; +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Sets the URI query. + * @param {string} newQuery New query value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Constraint': ol.format.OWS.readConstraint_ - }); +goog.Uri.prototype.setQuery = function(newQuery, opt_decode) { + return this.setQueryData(newQuery, opt_decode); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {string} The encoded URI query, not including the ?. */ -ol.format.OWS.SERVICE_CONTACT_PARSERS_ = - ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'IndividualName': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'individualName'), - 'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'positionName'), - 'ContactInfo': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readContactInfo_, 'contactInfo') - }); +goog.Uri.prototype.getEncodedQuery = function() { + return this.queryData_.toString(); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @return {string} The decoded URI query, not including the ?. */ -ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ = - ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'title'), - 'ServiceTypeVersion': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'serviceTypeVersion'), - 'ServiceType': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString, 'serviceType') - }); +goog.Uri.prototype.getDecodedQuery = function() { + return this.queryData_.toDecodedString(); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Returns the query data. + * @return {!goog.Uri.QueryData} QueryData object. */ -ol.format.OWS.SERVICE_PROVIDER_PARSERS_ = - ol.xml.makeParsersNS( - ol.format.OWS.NAMESPACE_URIS_, { - 'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, - 'providerName'), - 'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref, - 'providerSite'), - 'ServiceContact': ol.xml.makeObjectPropertySetter( - ol.format.OWS.readServiceContact_, 'serviceContact') - }); - -goog.provide('ol.geom.flat.flip'); - -goog.require('goog.asserts'); +goog.Uri.prototype.getQueryData = function() { + return this.queryData_; +}; /** - * @param {Array.<number>} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {Array.<number>=} opt_dest Destination. - * @param {number=} opt_destOffset Destination offset. - * @return {Array.<number>} Flat coordinates. - */ -ol.geom.flat.flip.flipXY = - function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) { - var dest, destOffset; - if (goog.isDef(opt_dest)) { - dest = opt_dest; - destOffset = goog.isDef(opt_destOffset) ? opt_destOffset : 0; - } else { - goog.asserts.assert(!goog.isDef(opt_destOffset)); - dest = []; - destOffset = 0; - } - var j, k; - for (j = offset; j < end; ) { - var x = flatCoordinates[j++]; - dest[destOffset++] = flatCoordinates[j++]; - dest[destOffset++] = x; - for (k = 2; k < stride; ++k) { - dest[destOffset++] = flatCoordinates[j++]; - } - } - dest.length = destOffset; - return dest; + * @return {string} The encoded URI query, not including the ?. + * + * Warning: This method, unlike other getter methods, returns encoded + * value, instead of decoded one. + */ +goog.Uri.prototype.getQuery = function() { + return this.getEncodedQuery(); }; -goog.provide('ol.format.Polyline'); - -goog.require('goog.asserts'); -goog.require('ol.Feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.TextFeature'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.flat.flip'); -goog.require('ol.geom.flat.inflate'); -goog.require('ol.proj'); +/** + * Sets the value of the named query parameters, clearing previous values for + * that key. + * + * @param {string} key The parameter to set. + * @param {*} value The new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setParameterValue = function(key, value) { + this.enforceReadOnly(); + this.queryData_.set(key, value); + return this; +}; /** - * @classdesc - * Feature format for reading and writing data in the Encoded - * Polyline Algorithm Format. + * Sets the values of the named query parameters, clearing previous values for + * that key. Not new values will currently be moved to the end of the query + * string. * - * @constructor - * @extends {ol.format.TextFeature} - * @param {olx.format.PolylineOptions=} opt_options - * Optional configuration object. - * @api stable + * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new']) + * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p> + * + * @param {string} key The parameter to set. + * @param {*} values The new values. If values is a single + * string then it will be treated as the sole value. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.Polyline = function(opt_options) { +goog.Uri.prototype.setParameterValues = function(key, values) { + this.enforceReadOnly(); - var options = goog.isDef(opt_options) ? opt_options : {}; + if (!goog.isArray(values)) { + values = [String(values)]; + } - goog.base(this); + this.queryData_.setValues(key, values); - /** - * @inheritDoc - */ - this.defaultDataProjection = ol.proj.get('EPSG:4326'); + return this; +}; - /** - * @private - * @type {number} - */ - this.factor_ = goog.isDef(options.factor) ? options.factor : 1e5; + +/** + * Returns the value<b>s</b> for a given cgi parameter as a list of decoded + * query parameter values. + * @param {string} name The parameter to get values for. + * @return {!Array<?>} The values for a given cgi parameter as a list of + * decoded query parameter values. + */ +goog.Uri.prototype.getParameterValues = function(name) { + return this.queryData_.getValues(name); }; -goog.inherits(ol.format.Polyline, ol.format.TextFeature); /** - * Encode a list of n-dimensional points and return an encoded string - * - * Attention: This function will modify the passed array! - * - * @param {Array.<number>} numbers A list of n-dimensional points. - * @param {number} stride The number of dimension of the points in the list. - * @param {number=} opt_factor The factor by which the numbers will be - * multiplied. The remaining decimal places will get rounded away. - * Default is `1e5`. - * @return {string} The encoded string. - * @api + * Returns the first value for a given cgi parameter or undefined if the given + * parameter name does not appear in the query string. + * @param {string} paramName Unescaped parameter name. + * @return {string|undefined} The first value for a given cgi parameter or + * undefined if the given parameter name does not appear in the query + * string. */ -ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) { - var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; - var d; +goog.Uri.prototype.getParameterValue = function(paramName) { + // NOTE(nicksantos): This type-cast is a lie when + // preserveParameterTypesCompatibilityFlag is set to true. + // But this should only be set to true in tests. + return /** @type {string|undefined} */ (this.queryData_.get(paramName)); +}; - var lastNumbers = new Array(stride); - for (d = 0; d < stride; ++d) { - lastNumbers[d] = 0; - } - var i, ii; - for (i = 0, ii = numbers.length; i < ii;) { - for (d = 0; d < stride; ++d, ++i) { - var num = numbers[i]; - var delta = num - lastNumbers[d]; - lastNumbers[d] = num; +/** + * @return {string} The URI fragment, not including the #. + */ +goog.Uri.prototype.getFragment = function() { + return this.fragment_; +}; - numbers[i] = delta; - } - } - return ol.format.Polyline.encodeFloats(numbers, factor); +/** + * Sets the URI fragment. + * @param {string} newFragment New fragment value. + * @param {boolean=} opt_decode Optional param for whether to decode new value. + * @return {!goog.Uri} Reference to this URI object. + */ +goog.Uri.prototype.setFragment = function(newFragment, opt_decode) { + this.enforceReadOnly(); + this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) : + newFragment; + return this; }; /** - * Decode a list of n-dimensional points from an encoded string - * - * @param {string} encoded An encoded string. - * @param {number} stride The number of dimension of the points in the - * encoded string. - * @param {number=} opt_factor The factor by which the resulting numbers will - * be divided. Default is `1e5`. - * @return {Array.<number>} A list of n-dimensional points. - * @api + * @return {boolean} Whether the URI has a fragment set. */ -ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) { - var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; - var d; +goog.Uri.prototype.hasFragment = function() { + return !!this.fragment_; +}; - /** @type {Array.<number>} */ - var lastNumbers = new Array(stride); - for (d = 0; d < stride; ++d) { - lastNumbers[d] = 0; - } - var numbers = ol.format.Polyline.decodeFloats(encoded, factor); +/** + * Returns true if this has the same domain as that of uri2. + * @param {!goog.Uri} uri2 The URI object to compare to. + * @return {boolean} true if same domain; false otherwise. + */ +goog.Uri.prototype.hasSameDomainAs = function(uri2) { + return ((!this.hasDomain() && !uri2.hasDomain()) || + this.getDomain() == uri2.getDomain()) && + ((!this.hasPort() && !uri2.hasPort()) || + this.getPort() == uri2.getPort()); +}; - var i, ii; - for (i = 0, ii = numbers.length; i < ii;) { - for (d = 0; d < stride; ++d, ++i) { - lastNumbers[d] += numbers[i]; - numbers[i] = lastNumbers[d]; - } - } +/** + * Adds a random parameter to the Uri. + * @return {!goog.Uri} Reference to this Uri object. + */ +goog.Uri.prototype.makeUnique = function() { + this.enforceReadOnly(); + this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString()); - return numbers; + return this; }; /** - * Encode a list of floating point numbers and return an encoded string - * - * Attention: This function will modify the passed array! + * Removes the named query parameter. * - * @param {Array.<number>} numbers A list of floating point numbers. - * @param {number=} opt_factor The factor by which the numbers will be - * multiplied. The remaining decimal places will get rounded away. - * Default is `1e5`. - * @return {string} The encoded string. - * @api + * @param {string} key The parameter to remove. + * @return {!goog.Uri} Reference to this URI object. */ -ol.format.Polyline.encodeFloats = function(numbers, opt_factor) { - var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; - var i, ii; - for (i = 0, ii = numbers.length; i < ii; ++i) { - numbers[i] = Math.round(numbers[i] * factor); - } - - return ol.format.Polyline.encodeSignedIntegers(numbers); +goog.Uri.prototype.removeParameter = function(key) { + this.enforceReadOnly(); + this.queryData_.remove(key); + return this; }; /** - * Decode a list of floating point numbers from an encoded string - * - * @param {string} encoded An encoded string. - * @param {number=} opt_factor The factor by which the result will be divided. - * Default is `1e5`. - * @return {Array.<number>} A list of floating point numbers. - * @api + * Sets whether Uri is read only. If this goog.Uri is read-only, + * enforceReadOnly_ will be called at the start of any function that may modify + * this Uri. + * @param {boolean} isReadOnly whether this goog.Uri should be read only. + * @return {!goog.Uri} Reference to this Uri object. */ -ol.format.Polyline.decodeFloats = function(encoded, opt_factor) { - var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; - var numbers = ol.format.Polyline.decodeSignedIntegers(encoded); - var i, ii; - for (i = 0, ii = numbers.length; i < ii; ++i) { - numbers[i] /= factor; - } - return numbers; +goog.Uri.prototype.setReadOnly = function(isReadOnly) { + this.isReadOnly_ = isReadOnly; + return this; }; /** - * Encode a list of signed integers and return an encoded string - * - * Attention: This function will modify the passed array! - * - * @param {Array.<number>} numbers A list of signed integers. - * @return {string} The encoded string. + * @return {boolean} Whether the URI is read only. */ -ol.format.Polyline.encodeSignedIntegers = function(numbers) { - var i, ii; - for (i = 0, ii = numbers.length; i < ii; ++i) { - var num = numbers[i]; - numbers[i] = (num < 0) ? ~(num << 1) : (num << 1); - } - return ol.format.Polyline.encodeUnsignedIntegers(numbers); +goog.Uri.prototype.isReadOnly = function() { + return this.isReadOnly_; }; /** - * Decode a list of signed integers from an encoded string - * - * @param {string} encoded An encoded string. - * @return {Array.<number>} A list of signed integers. + * Checks if this Uri has been marked as read only, and if so, throws an error. + * This should be called whenever any modifying function is called. */ -ol.format.Polyline.decodeSignedIntegers = function(encoded) { - var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded); - var i, ii; - for (i = 0, ii = numbers.length; i < ii; ++i) { - var num = numbers[i]; - numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); +goog.Uri.prototype.enforceReadOnly = function() { + if (this.isReadOnly_) { + throw Error('Tried to modify a read-only Uri'); } - return numbers; }; /** - * Encode a list of unsigned integers and return an encoded string - * - * @param {Array.<number>} numbers A list of unsigned integers. - * @return {string} The encoded string. + * Sets whether to ignore case. + * NOTE: If there are already key/value pairs in the QueryData, and + * ignoreCase_ is set to false, the keys will all be lower-cased. + * @param {boolean} ignoreCase whether this goog.Uri should ignore case. + * @return {!goog.Uri} Reference to this Uri object. */ -ol.format.Polyline.encodeUnsignedIntegers = function(numbers) { - var encoded = ''; - var i, ii; - for (i = 0, ii = numbers.length; i < ii; ++i) { - encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]); +goog.Uri.prototype.setIgnoreCase = function(ignoreCase) { + this.ignoreCase_ = ignoreCase; + if (this.queryData_) { + this.queryData_.setIgnoreCase(ignoreCase); } - return encoded; + return this; }; /** - * Decode a list of unsigned integers from an encoded string - * - * @param {string} encoded An encoded string. - * @return {Array.<number>} A list of unsigned integers. + * @return {boolean} Whether to ignore case. */ -ol.format.Polyline.decodeUnsignedIntegers = function(encoded) { - var numbers = []; - var current = 0; - var shift = 0; - var i, ii; - for (i = 0, ii = encoded.length; i < ii; ++i) { - var b = encoded.charCodeAt(i) - 63; - current |= (b & 0x1f) << shift; - if (b < 0x20) { - numbers.push(current); - current = 0; - shift = 0; - } else { - shift += 5; - } - } - return numbers; +goog.Uri.prototype.getIgnoreCase = function() { + return this.ignoreCase_; }; +//============================================================================== +// Static members +//============================================================================== + + /** - * Encode one single unsigned integer and return an encoded string + * Creates a uri from the string form. Basically an alias of new goog.Uri(). + * If a Uri object is passed to parse then it will return a clone of the object. * - * @param {number} num Unsigned integer that should be encoded. - * @return {string} The encoded string. + * @param {*} uri Raw URI string or instance of Uri + * object. + * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter + * names in #getParameterValue. + * @return {!goog.Uri} The new URI object. */ -ol.format.Polyline.encodeUnsignedInteger = function(num) { - var value, encoded = ''; - while (num >= 0x20) { - value = (0x20 | (num & 0x1f)) + 63; - encoded += String.fromCharCode(value); - num >>= 5; - } - value = num + 63; - encoded += String.fromCharCode(value); - return encoded; +goog.Uri.parse = function(uri, opt_ignoreCase) { + return uri instanceof goog.Uri ? + uri.clone() : new goog.Uri(uri, opt_ignoreCase); }; /** - * Read the feature from the Polyline source. The coordinates are assumed to be - * in two dimensions and in latitude, longitude order. + * Creates a new goog.Uri object from unencoded parts. * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. - * @api stable + * @param {?string=} opt_scheme Scheme/protocol or full URI to parse. + * @param {?string=} opt_userInfo username:password. + * @param {?string=} opt_domain www.google.com. + * @param {?number=} opt_port 9830. + * @param {?string=} opt_path /some/path/to/a/file.html. + * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2. + * @param {?string=} opt_fragment The fragment without the #. + * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in + * #getParameterValue. + * + * @return {!goog.Uri} The new URI object. */ -ol.format.Polyline.prototype.readFeature; +goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port, + opt_path, opt_query, opt_fragment, opt_ignoreCase) { + + var uri = new goog.Uri(null, opt_ignoreCase); + + // Only set the parts if they are defined and not empty strings. + opt_scheme && uri.setScheme(opt_scheme); + opt_userInfo && uri.setUserInfo(opt_userInfo); + opt_domain && uri.setDomain(opt_domain); + opt_port && uri.setPort(opt_port); + opt_path && uri.setPath(opt_path); + opt_query && uri.setQueryData(opt_query); + opt_fragment && uri.setFragment(opt_fragment); + + return uri; +}; /** - * @inheritDoc + * Resolves a relative Uri against a base Uri, accepting both strings and + * Uri objects. + * + * @param {*} base Base Uri. + * @param {*} rel Relative Uri. + * @return {!goog.Uri} Resolved uri. */ -ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) { - var geometry = this.readGeometryFromText(text, opt_options); - return new ol.Feature(geometry); +goog.Uri.resolve = function(base, rel) { + if (!(base instanceof goog.Uri)) { + base = goog.Uri.parse(base); + } + + if (!(rel instanceof goog.Uri)) { + rel = goog.Uri.parse(rel); + } + + return base.resolve(rel); }; /** - * Read the feature from the source. As Polyline sources contain a single - * feature, this will return the feature in an array. + * Removes dot segments in given path component, as described in + * RFC 3986, section 5.2.4. * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @param {string} path A non-empty path component. + * @return {string} Path component with removed dot segments. */ -ol.format.Polyline.prototype.readFeatures; +goog.Uri.removeDotSegments = function(path) { + if (path == '..' || path == '.') { + return ''; + + } else if (!goog.string.contains(path, './') && + !goog.string.contains(path, '/.')) { + // This optimization detects uris which do not contain dot-segments, + // and as a consequence do not require any processing. + return path; + + } else { + var leadingSlash = goog.string.startsWith(path, '/'); + var segments = path.split('/'); + var out = []; + + for (var pos = 0; pos < segments.length; ) { + var segment = segments[pos++]; + + if (segment == '.') { + if (leadingSlash && pos == segments.length) { + out.push(''); + } + } else if (segment == '..') { + if (out.length > 1 || out.length == 1 && out[0] != '') { + out.pop(); + } + if (leadingSlash && pos == segments.length) { + out.push(''); + } + } else { + out.push(segment); + leadingSlash = true; + } + } + + return out.join('/'); + } +}; /** - * @inheritDoc + * Decodes a value or returns the empty string if it isn't defined or empty. + * @param {string|undefined} val Value to decode. + * @param {boolean=} opt_preserveReserved If true, restricted characters will + * not be decoded. + * @return {string} Decoded value. + * @private */ -ol.format.Polyline.prototype.readFeaturesFromText = - function(text, opt_options) { - var feature = this.readFeatureFromText(text, opt_options); - return [feature]; +goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) { + // Don't use UrlDecode() here because val is not a query parameter. + if (!val) { + return ''; + } + + return opt_preserveReserved ? decodeURI(val) : decodeURIComponent(val); }; /** - * Read the geometry from the source. + * If unescapedPart is non null, then escapes any characters in it that aren't + * valid characters in a url and also escapes any special characters that + * appear in extra. * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.geom.Geometry} Geometry. - * @api stable + * @param {*} unescapedPart The string to encode. + * @param {RegExp} extra A character set of characters in [\01-\177]. + * @param {boolean=} opt_removeDoubleEncoding If true, remove double percent + * encoding. + * @return {?string} null iff unescapedPart == null. + * @private */ -ol.format.Polyline.prototype.readGeometry; +goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra, + opt_removeDoubleEncoding) { + if (goog.isString(unescapedPart)) { + var encoded = encodeURI(unescapedPart). + replace(extra, goog.Uri.encodeChar_); + if (opt_removeDoubleEncoding) { + // encodeURI double-escapes %XX sequences used to represent restricted + // characters in some URI components, remove the double escaping here. + encoded = goog.Uri.removeDoubleEncoding_(encoded); + } + return encoded; + } + return null; +}; /** - * @inheritDoc + * Converts a character in [\01-\177] to its unicode character equivalent. + * @param {string} ch One character string. + * @return {string} Encoded string. + * @private */ -ol.format.Polyline.prototype.readGeometryFromText = - function(text, opt_options) { - var flatCoordinates = ol.format.Polyline.decodeDeltas(text, 2, this.factor_); - ol.geom.flat.flip.flipXY( - flatCoordinates, 0, flatCoordinates.length, 2, flatCoordinates); - var coordinates = ol.geom.flat.inflate.coordinates( - flatCoordinates, 0, flatCoordinates.length, 2); +goog.Uri.encodeChar_ = function(ch) { + var n = ch.charCodeAt(0); + return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16); +}; - return /** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions( - new ol.geom.LineString(coordinates), false, - this.adaptOptions(opt_options))); + +/** + * Removes double percent-encoding from a string. + * @param {string} doubleEncodedString String + * @return {string} String with double encoding removed. + * @private + */ +goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) { + return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1'); }; /** - * Read the projection from a Polyline source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.proj.Projection} Projection. - * @api stable + * Regular expression for characters that are disallowed in the scheme or + * userInfo part of the URI. + * @type {RegExp} + * @private */ -ol.format.Polyline.prototype.readProjection; +goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g; /** - * @inheritDoc + * Regular expression for characters that are disallowed in a relative path. + * Colon is included due to RFC 3986 3.3. + * @type {RegExp} + * @private */ -ol.format.Polyline.prototype.readProjectionFromText = function(text) { - return this.defaultDataProjection; -}; +goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g; /** - * @inheritDoc + * Regular expression for characters that are disallowed in an absolute path. + * @type {RegExp} + * @private */ -ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) { - var geometry = feature.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - return this.writeGeometryText(geometry, opt_options); - } else { - goog.asserts.fail(); - return ''; - } -}; +goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g; /** - * @inheritDoc + * Regular expression for characters that are disallowed in the query. + * @type {RegExp} + * @private */ -ol.format.Polyline.prototype.writeFeaturesText = - function(features, opt_options) { - goog.asserts.assert(features.length == 1); - return this.writeFeatureText(features[0], opt_options); -}; +goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g; /** - * Write a single geometry in Polyline format. - * - * @function - * @param {ol.geom.Geometry} geometry Geometry. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} Geometry. - * @api stable + * Regular expression for characters that are disallowed in the fragment. + * @type {RegExp} + * @private */ -ol.format.Polyline.prototype.writeGeometry; +goog.Uri.reDisallowedInFragment_ = /#/g; /** - * @inheritDoc + * Checks whether two URIs have the same domain. + * @param {string} uri1String First URI string. + * @param {string} uri2String Second URI string. + * @return {boolean} true if the two URIs have the same domain; false otherwise. */ -ol.format.Polyline.prototype.writeGeometryText = - function(geometry, opt_options) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - geometry = /** @type {ol.geom.LineString} */ - (ol.format.Feature.transformWithOptions( - geometry, true, this.adaptOptions(opt_options))); - var flatCoordinates = geometry.getFlatCoordinates(); - var stride = geometry.getStride(); - ol.geom.flat.flip.flipXY( - flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); - return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_); +goog.Uri.haveSameDomain = function(uri1String, uri2String) { + // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme. + // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain. + var pieces1 = goog.uri.utils.split(uri1String); + var pieces2 = goog.uri.utils.split(uri2String); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == + pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && + pieces1[goog.uri.utils.ComponentIndex.PORT] == + pieces2[goog.uri.utils.ComponentIndex.PORT]; }; -goog.provide('ol.format.TopoJSON'); - -goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('ol.Feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.JSONFeature'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.proj'); - /** - * @classdesc - * Feature format for reading and writing data in the TopoJSON format. + * Class used to represent URI query parameters. It is essentially a hash of + * name-value pairs, though a name can be present more than once. + * + * Has the same interface as the collections in goog.structs. * + * @param {?string=} opt_query Optional encoded query string to parse into + * the object. + * @param {goog.Uri=} opt_uri Optional uri object that should have its + * cache invalidated when this object updates. Deprecated -- this + * is no longer required. + * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter + * name in #get. * @constructor - * @extends {ol.format.JSONFeature} - * @param {olx.format.TopoJSONOptions=} opt_options Options. - * @api stable + * @struct + * @final */ -ol.format.TopoJSON = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this); - +goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) { /** - * @inheritDoc + * Encoded query string, or null if it requires computing from the key map. + * @type {?string} + * @private */ - this.defaultDataProjection = ol.proj.get( - goog.isDefAndNotNull(options.defaultDataProjection) ? - options.defaultDataProjection : 'EPSG:4326'); + this.encodedQuery_ = opt_query || null; + /** + * If true, ignore the case of the parameter name in #get. + * @type {boolean} + * @private + */ + this.ignoreCase_ = !!opt_ignoreCase; }; -goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature); /** - * @const {Array.<string>} + * If the underlying key map is not yet initialized, it parses the + * query string and fills the map with parsed data. * @private */ -ol.format.TopoJSON.EXTENSIONS_ = ['.topojson']; +goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() { + if (!this.keyMap_) { + this.keyMap_ = new goog.structs.Map(); + this.count_ = 0; + if (this.encodedQuery_) { + var self = this; + goog.uri.utils.parseQueryData(this.encodedQuery_, function(name, value) { + self.add(goog.string.urlDecode(name), value); + }); + } + } +}; /** - * Concatenate arcs into a coordinate array. - * @param {Array.<number>} indices Indices of arcs to concatenate. Negative - * values indicate arcs need to be reversed. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already - * transformed). - * @return {Array.<ol.Coordinate>} Coordinates array. - * @private + * Creates a new query data instance from a map of names and values. + * + * @param {!goog.structs.Map<string, ?>|!Object} map Map of string parameter + * names to parameter value. If parameter value is an array, it is + * treated as if the key maps to each individual value in the + * array. + * @param {goog.Uri=} opt_uri URI object that should have its cache + * invalidated when this object updates. + * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter + * name in #get. + * @return {!goog.Uri.QueryData} The populated query data instance. */ -ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) { - /** @type {Array.<ol.Coordinate>} */ - var coordinates = []; - var index, arc; - var i, ii; - var j, jj; - for (i = 0, ii = indices.length; i < ii; ++i) { - index = indices[i]; - if (i > 0) { - // splicing together arcs, discard last point - coordinates.pop(); - } - if (index >= 0) { - // forward arc - arc = arcs[index]; +goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) { + var keys = goog.structs.getKeys(map); + if (typeof keys == 'undefined') { + throw Error('Keys are undefined'); + } + + var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase); + var values = goog.structs.getValues(map); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = values[i]; + if (!goog.isArray(value)) { + queryData.add(key, value); } else { - // reverse arc - arc = arcs[~index].slice().reverse(); + queryData.setValues(key, value); } - coordinates.push.apply(coordinates, arc); - } - // provide fresh copies of coordinate arrays - for (j = 0, jj = coordinates.length; j < jj; ++j) { - coordinates[j] = coordinates[j].slice(); } - return coordinates; + return queryData; }; /** - * Create a point from a TopoJSON geometry object. + * Creates a new query data instance from parallel arrays of parameter names + * and values. Allows for duplicate parameter names. Throws an error if the + * lengths of the arrays differ. * - * @param {TopoJSONGeometry} object TopoJSON object. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. - * @return {ol.geom.Point} Geometry. - * @private + * @param {!Array<string>} keys Parameter names. + * @param {!Array<?>} values Parameter values. + * @param {goog.Uri=} opt_uri URI object that should have its cache + * invalidated when this object updates. + * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter + * name in #get. + * @return {!goog.Uri.QueryData} The populated query data instance. */ -ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) { - var coordinates = object.coordinates; - if (!goog.isNull(scale) && !goog.isNull(translate)) { - ol.format.TopoJSON.transformVertex_(coordinates, scale, translate); +goog.Uri.QueryData.createFromKeysValues = function( + keys, values, opt_uri, opt_ignoreCase) { + if (keys.length != values.length) { + throw Error('Mismatched lengths for keys/values'); } - return new ol.geom.Point(coordinates); + var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase); + for (var i = 0; i < keys.length; i++) { + queryData.add(keys[i], values[i]); + } + return queryData; }; /** - * Create a multi-point from a TopoJSON geometry object. + * The map containing name/value or name/array-of-values pairs. + * May be null if it requires parsing from the query string. * - * @param {TopoJSONGeometry} object TopoJSON object. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. - * @return {ol.geom.MultiPoint} Geometry. - * @private + * We need to use a Map because we cannot guarantee that the key names will + * not be problematic for IE. + * + * @private {goog.structs.Map<string, !Array<*>>} */ -ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale, - translate) { - var coordinates = object.coordinates; - var i, ii; - if (!goog.isNull(scale) && !goog.isNull(translate)) { - for (i = 0, ii = coordinates.length; i < ii; ++i) { - ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate); - } - } - return new ol.geom.MultiPoint(coordinates); -}; +goog.Uri.QueryData.prototype.keyMap_ = null; /** - * Create a linestring from a TopoJSON geometry object. - * - * @param {TopoJSONGeometry} object TopoJSON object. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @return {ol.geom.LineString} Geometry. + * The number of params, or null if it requires computing. + * @type {?number} * @private */ -ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) { - var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs); - return new ol.geom.LineString(coordinates); +goog.Uri.QueryData.prototype.count_ = null; + + +/** + * @return {?number} The number of parameters. + */ +goog.Uri.QueryData.prototype.getCount = function() { + this.ensureKeyMapInitialized_(); + return this.count_; }; /** - * Create a multi-linestring from a TopoJSON geometry object. - * - * @param {TopoJSONGeometry} object TopoJSON object. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @return {ol.geom.MultiLineString} Geometry. - * @private + * Adds a key value pair. + * @param {string} key Name. + * @param {*} value Value. + * @return {!goog.Uri.QueryData} Instance of this object. */ -ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) { - var coordinates = []; - var i, ii; - for (i = 0, ii = object.arcs.length; i < ii; ++i) { - coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); +goog.Uri.QueryData.prototype.add = function(key, value) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + + key = this.getKeyName_(key); + var values = this.keyMap_.get(key); + if (!values) { + this.keyMap_.set(key, (values = [])); } - return new ol.geom.MultiLineString(coordinates); + values.push(value); + this.count_++; + return this; }; /** - * Create a polygon from a TopoJSON geometry object. - * - * @param {TopoJSONGeometry} object TopoJSON object. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @return {ol.geom.Polygon} Geometry. - * @private + * Removes all the params with the given key. + * @param {string} key Name. + * @return {boolean} Whether any parameter was removed. */ -ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) { - var coordinates = []; - var i, ii; - for (i = 0, ii = object.arcs.length; i < ii; ++i) { - coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); +goog.Uri.QueryData.prototype.remove = function(key) { + this.ensureKeyMapInitialized_(); + + key = this.getKeyName_(key); + if (this.keyMap_.containsKey(key)) { + this.invalidateCache_(); + + // Decrement parameter count. + this.count_ -= this.keyMap_.get(key).length; + return this.keyMap_.remove(key); } - return new ol.geom.Polygon(coordinates); + return false; }; /** - * Create a multi-polygon from a TopoJSON geometry object. - * - * @param {TopoJSONGeometry} object TopoJSON object. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @return {ol.geom.MultiPolygon} Geometry. - * @private + * Clears the parameters. */ -ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) { - var coordinates = []; - var polyArray, ringCoords, j, jj; - var i, ii; - for (i = 0, ii = object.arcs.length; i < ii; ++i) { - // for each polygon - polyArray = object.arcs[i]; - ringCoords = []; - for (j = 0, jj = polyArray.length; j < jj; ++j) { - // for each ring - ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs); +goog.Uri.QueryData.prototype.clear = function() { + this.invalidateCache_(); + this.keyMap_ = null; + this.count_ = 0; +}; + + +/** + * @return {boolean} Whether we have any parameters. + */ +goog.Uri.QueryData.prototype.isEmpty = function() { + this.ensureKeyMapInitialized_(); + return this.count_ == 0; +}; + + +/** + * Whether there is a parameter with the given name + * @param {string} key The parameter name to check for. + * @return {boolean} Whether there is a parameter with the given name. + */ +goog.Uri.QueryData.prototype.containsKey = function(key) { + this.ensureKeyMapInitialized_(); + key = this.getKeyName_(key); + return this.keyMap_.containsKey(key); +}; + + +/** + * Whether there is a parameter with the given value. + * @param {*} value The value to check for. + * @return {boolean} Whether there is a parameter with the given value. + */ +goog.Uri.QueryData.prototype.containsValue = function(value) { + // NOTE(arv): This solution goes through all the params even if it was the + // first param. We can get around this by not reusing code or by switching to + // iterators. + var vals = this.getValues(); + return goog.array.contains(vals, value); +}; + + +/** + * Returns all the keys of the parameters. If a key is used multiple times + * it will be included multiple times in the returned array + * @return {!Array<string>} All the keys of the parameters. + */ +goog.Uri.QueryData.prototype.getKeys = function() { + this.ensureKeyMapInitialized_(); + // We need to get the values to know how many keys to add. + var vals = /** @type {!Array<*>} */ (this.keyMap_.getValues()); + var keys = this.keyMap_.getKeys(); + var rv = []; + for (var i = 0; i < keys.length; i++) { + var val = vals[i]; + for (var j = 0; j < val.length; j++) { + rv.push(keys[i]); } - coordinates[i] = ringCoords; } - return new ol.geom.MultiPolygon(coordinates); + return rv; }; /** - * @inheritDoc + * Returns all the values of the parameters with the given name. If the query + * data has no such key this will return an empty array. If no key is given + * all values wil be returned. + * @param {string=} opt_key The name of the parameter to get the values for. + * @return {!Array<?>} All the values of the parameters with the given name. */ -ol.format.TopoJSON.prototype.getExtensions = function() { - return ol.format.TopoJSON.EXTENSIONS_; +goog.Uri.QueryData.prototype.getValues = function(opt_key) { + this.ensureKeyMapInitialized_(); + var rv = []; + if (goog.isString(opt_key)) { + if (this.containsKey(opt_key)) { + rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key))); + } + } else { + // Return all values. + var values = this.keyMap_.getValues(); + for (var i = 0; i < values.length; i++) { + rv = goog.array.concat(rv, values[i]); + } + } + return rv; }; /** - * Create features from a TopoJSON GeometryCollection object. + * Sets a key value pair and removes all other keys with the same value. * - * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry - * object. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Array of features. - * @private + * @param {string} key Name. + * @param {*} value Value. + * @return {!goog.Uri.QueryData} Instance of this object. */ -ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function( - collection, arcs, scale, translate, opt_options) { - var geometries = collection.geometries; - var features = []; - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - features[i] = ol.format.TopoJSON.readFeatureFromGeometry_( - geometries[i], arcs, scale, translate, opt_options); +goog.Uri.QueryData.prototype.set = function(key, value) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + + // TODO(chrishenry): This could be better written as + // this.remove(key), this.add(key, value), but that would reorder + // the key (since the key is first removed and then added at the + // end) and we would have to fix unit tests that depend on key + // ordering. + key = this.getKeyName_(key); + if (this.containsKey(key)) { + this.count_ -= this.keyMap_.get(key).length; } - return features; + this.keyMap_.set(key, [value]); + this.count_++; + return this; }; /** - * Create a feature from a TopoJSON geometry object. - * - * @param {TopoJSONGeometry} object TopoJSON geometry object. - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. - * @private + * Returns the first value associated with the key. If the query data has no + * such key this will return undefined or the optional default. + * @param {string} key The name of the parameter to get the value for. + * @param {*=} opt_default The default value to return if the query data + * has no such key. + * @return {*} The first string value associated with the key, or opt_default + * if there's no value. */ -ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs, - scale, translate, opt_options) { - var geometry; - var type = object.type; - var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type]; - goog.asserts.assert(goog.isDef(geometryReader)); - if ((type === 'Point') || (type === 'MultiPoint')) { - geometry = geometryReader(object, scale, translate); +goog.Uri.QueryData.prototype.get = function(key, opt_default) { + var values = key ? this.getValues(key) : []; + if (goog.Uri.preserveParameterTypesCompatibilityFlag) { + return values.length > 0 ? values[0] : opt_default; } else { - geometry = geometryReader(object, arcs); - } - var feature = new ol.Feature(); - feature.setGeometry(/** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions(geometry, false, opt_options))); - if (goog.isDef(object.id)) { - feature.setId(object.id); + return values.length > 0 ? String(values[0]) : opt_default; } - if (goog.isDef(object.properties)) { - feature.setProperties(object.properties); +}; + + +/** + * Sets the values for a key. If the key already exists, this will + * override all of the existing values that correspond to the key. + * @param {string} key The key to set values for. + * @param {!Array<?>} values The values to set. + */ +goog.Uri.QueryData.prototype.setValues = function(key, values) { + this.remove(key); + + if (values.length > 0) { + this.invalidateCache_(); + this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values)); + this.count_ += values.length; } - return feature; }; /** - * Read all features from a TopoJSON source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @return {string} Encoded query string. + * @override */ -ol.format.TopoJSON.prototype.readFeatures; +goog.Uri.QueryData.prototype.toString = function() { + if (this.encodedQuery_) { + return this.encodedQuery_; + } + if (!this.keyMap_) { + return ''; + } -/** - * @inheritDoc - */ -ol.format.TopoJSON.prototype.readFeaturesFromObject = function( - object, opt_options) { - if (object.type == 'Topology') { - var topoJSONTopology = /** @type {TopoJSONTopology} */ (object); - var transform, scale = null, translate = null; - if (goog.isDef(topoJSONTopology.transform)) { - transform = /** @type {TopoJSONTransform} */ - (topoJSONTopology.transform); - scale = transform.scale; - translate = transform.translate; - } - var arcs = topoJSONTopology.arcs; - if (goog.isDef(transform)) { - ol.format.TopoJSON.transformArcs_(arcs, scale, translate); - } - /** @type {Array.<ol.Feature>} */ - var features = []; - var topoJSONFeatures = goog.object.getValues(topoJSONTopology.objects); - var i, ii; - var feature; - for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) { - if (topoJSONFeatures[i].type === 'GeometryCollection') { - feature = /** @type {TopoJSONGeometryCollection} */ - (topoJSONFeatures[i]); - features.push.apply(features, - ol.format.TopoJSON.readFeaturesFromGeometryCollection_( - feature, arcs, scale, translate, opt_options)); - } else { - feature = /** @type {TopoJSONGeometry} */ - (topoJSONFeatures[i]); - features.push(ol.format.TopoJSON.readFeatureFromGeometry_( - feature, arcs, scale, translate, opt_options)); + var sb = []; + + // In the past, we use this.getKeys() and this.getVals(), but that + // generates a lot of allocations as compared to simply iterating + // over the keys. + var keys = this.keyMap_.getKeys(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var encodedKey = goog.string.urlEncode(key); + var val = this.getValues(key); + for (var j = 0; j < val.length; j++) { + var param = encodedKey; + // Ensure that null and undefined are encoded into the url as + // literal strings. + if (val[j] !== '') { + param += '=' + goog.string.urlEncode(val[j]); } + sb.push(param); } - return features; - } else { - return []; } + + return this.encodedQuery_ = sb.join('&'); }; /** - * Apply a linear transform to array of arcs. The provided array of arcs is - * modified in place. - * - * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. - * @private + * @return {string} Decoded query string. */ -ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) { - var i, ii; - for (i = 0, ii = arcs.length; i < ii; ++i) { - ol.format.TopoJSON.transformArc_(arcs[i], scale, translate); - } +goog.Uri.QueryData.prototype.toDecodedString = function() { + return goog.Uri.decodeOrEmpty_(this.toString()); }; /** - * Apply a linear transform to an arc. The provided arc is modified in place. - * - * @param {Array.<ol.Coordinate>} arc Arc. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. + * Invalidate the cache. * @private */ -ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) { - var x = 0; - var y = 0; - var vertex; - var i, ii; - for (i = 0, ii = arc.length; i < ii; ++i) { - vertex = arc[i]; - x += vertex[0]; - y += vertex[1]; - vertex[0] = x; - vertex[1] = y; - ol.format.TopoJSON.transformVertex_(vertex, scale, translate); - } +goog.Uri.QueryData.prototype.invalidateCache_ = function() { + this.encodedQuery_ = null; }; /** - * Apply a linear transform to a vertex. The provided vertex is modified in - * place. - * - * @param {ol.Coordinate} vertex Vertex. - * @param {Array.<number>} scale Scale for each dimension. - * @param {Array.<number>} translate Translation for each dimension. - * @private + * Removes all keys that are not in the provided list. (Modifies this object.) + * @param {Array<string>} keys The desired keys. + * @return {!goog.Uri.QueryData} a reference to this object. */ -ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) { - vertex[0] = vertex[0] * scale[0] + translate[0]; - vertex[1] = vertex[1] * scale[1] + translate[1]; +goog.Uri.QueryData.prototype.filterKeys = function(keys) { + this.ensureKeyMapInitialized_(); + this.keyMap_.forEach( + function(value, key) { + if (!goog.array.contains(keys, key)) { + this.remove(key); + } + }, this); + return this; }; /** - * Read the projection from a TopoJSON source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} object Source. - * @return {ol.proj.Projection} Projection. - * @api stable + * Clone the query data instance. + * @return {!goog.Uri.QueryData} New instance of the QueryData object. */ -ol.format.TopoJSON.prototype.readProjection = function(object) { - return this.defaultDataProjection; +goog.Uri.QueryData.prototype.clone = function() { + var rv = new goog.Uri.QueryData(); + rv.encodedQuery_ = this.encodedQuery_; + if (this.keyMap_) { + rv.keyMap_ = this.keyMap_.clone(); + rv.count_ = this.count_; + } + return rv; }; /** - * @const + * Helper function to get the key name from a JavaScript object. Converts + * the object to a string, and to lower case if necessary. * @private - * @type {Object.<string, function(TopoJSONGeometry, Array, ...[Array]): ol.geom.Geometry>} + * @param {*} arg The object to get a key name from. + * @return {string} valid key name which can be looked up in #keyMap_. */ -ol.format.TopoJSON.GEOMETRY_READERS_ = { - 'Point': ol.format.TopoJSON.readPointGeometry_, - 'LineString': ol.format.TopoJSON.readLineStringGeometry_, - 'Polygon': ol.format.TopoJSON.readPolygonGeometry_, - 'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_, - 'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_, - 'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_ +goog.Uri.QueryData.prototype.getKeyName_ = function(arg) { + var keyName = String(arg); + if (this.ignoreCase_) { + keyName = keyName.toLowerCase(); + } + return keyName; }; -goog.provide('ol.format.WFS'); -goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('ol.format.GML3'); -goog.require('ol.format.GMLBase'); -goog.require('ol.format.XMLFeature'); -goog.require('ol.format.XSD'); -goog.require('ol.geom.Geometry'); -goog.require('ol.proj'); -goog.require('ol.xml'); +/** + * Ignore case in parameter names. + * NOTE: If there are already key/value pairs in the QueryData, and + * ignoreCase_ is set to false, the keys will all be lower-cased. + * @param {boolean} ignoreCase whether this goog.Uri should ignore case. + */ +goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) { + var resetKeys = ignoreCase && !this.ignoreCase_; + if (resetKeys) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + this.keyMap_.forEach( + function(value, key) { + var lowerCase = key.toLowerCase(); + if (key != lowerCase) { + this.remove(key); + this.setValues(lowerCase, value); + } + }, this); + } + this.ignoreCase_ = ignoreCase; +}; + + +/** + * Extends a query data object with another query data or map like object. This + * operates 'in-place', it does not create a new QueryData object. + * + * @param {...(goog.Uri.QueryData|goog.structs.Map<?, ?>|Object)} var_args + * The object from which key value pairs will be copied. + */ +goog.Uri.QueryData.prototype.extend = function(var_args) { + for (var i = 0; i < arguments.length; i++) { + var data = arguments[i]; + goog.structs.forEach(data, + /** @this {goog.Uri.QueryData} */ + function(value, key) { + this.add(key, value); + }, this); + } +}; + +goog.provide('ol.style.Text'); /** * @classdesc - * Feature format for reading and writing data in the WFS format. - * By default, supports WFS version 1.1.0. You can pass a GML format - * as option if you want to read a WFS that contains GML2 (WFS 1.0.0). - * Also see {@link ol.format.GMLBase} which is used by this format. + * Set text style for vector features. * * @constructor - * @param {olx.format.WFSOptions=} opt_options - * Optional configuration object. - * @extends {ol.format.XMLFeature} - * @api stable + * @param {olx.style.TextOptions=} opt_options Options. + * @api */ -ol.format.WFS = function(opt_options) { +ol.style.Text = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private - * @type {string} + * @type {string|undefined} */ - this.featureType_ = options.featureType; + this.font_ = options.font; /** * @private - * @type {string} + * @type {number|undefined} */ - this.featureNS_ = options.featureNS; + this.rotation_ = options.rotation; /** * @private - * @type {ol.format.GMLBase} + * @type {number|undefined} */ - this.gmlFormat_ = goog.isDef(options.gmlFormat) ? - options.gmlFormat : new ol.format.GML3(); + this.scale_ = options.scale; /** * @private - * @type {string} + * @type {string|undefined} */ - this.schemaLocation_ = goog.isDef(options.schemaLocation) ? - options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION; + this.text_ = options.text; - goog.base(this); + /** + * @private + * @type {string|undefined} + */ + this.textAlign_ = options.textAlign; + + /** + * @private + * @type {string|undefined} + */ + this.textBaseline_ = options.textBaseline; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fill_ = goog.isDef(options.fill) ? options.fill : null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.stroke_ = goog.isDef(options.stroke) ? options.stroke : null; + + /** + * @private + * @type {number} + */ + this.offsetX_ = goog.isDef(options.offsetX) ? options.offsetX : 0; + + /** + * @private + * @type {number} + */ + this.offsetY_ = goog.isDef(options.offsetY) ? options.offsetY : 0; }; -goog.inherits(ol.format.WFS, ol.format.XMLFeature); /** - * @const - * @type {string} + * Get the font name. + * @return {string|undefined} Font. + * @api */ -ol.format.WFS.FEATURE_PREFIX = 'feature'; +ol.style.Text.prototype.getFont = function() { + return this.font_; +}; /** - * @const - * @type {string} + * Get the x-offset for the text. + * @return {number} Horizontal text offset. + * @api */ -ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/'; +ol.style.Text.prototype.getOffsetX = function() { + return this.offsetX_; +}; /** - * Number of features; bounds/extent. - * @typedef {{numberOfFeatures: number, - * bounds: ol.Extent}} - * @api stable + * Get the y-offset for the text. + * @return {number} Vertical text offset. + * @api */ -ol.format.WFS.FeatureCollectionMetadata; +ol.style.Text.prototype.getOffsetY = function() { + return this.offsetY_; +}; /** - * Total deleted; total inserted; total updated; array of insert ids. - * @typedef {{totalDeleted: number, - * totalInserted: number, - * totalUpdated: number, - * insertIds: Array.<string>}} - * @api stable + * Get the fill style for the text. + * @return {ol.style.Fill} Fill style. + * @api */ -ol.format.WFS.TransactionResponse; +ol.style.Text.prototype.getFill = function() { + return this.fill_; +}; /** - * @const - * @type {string} + * Get the text rotation. + * @return {number|undefined} Rotation. + * @api */ -ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' + - 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd'; +ol.style.Text.prototype.getRotation = function() { + return this.rotation_; +}; /** - * Read all features from a WFS FeatureCollection. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. - * @api stable + * Get the text scale. + * @return {number|undefined} Scale. + * @api */ -ol.format.WFS.prototype.readFeatures; +ol.style.Text.prototype.getScale = function() { + return this.scale_; +}; /** - * @inheritDoc + * Get the stroke style for the text. + * @return {ol.style.Stroke} Stroke style. + * @api */ -ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) { - var context = { - 'featureType': this.featureType_, - 'featureNS': this.featureNS_ - }; - goog.object.extend(context, this.getReadOptions(node, - goog.isDef(opt_options) ? opt_options : {})); - var objectStack = [context]; - var features = ol.xml.pushParseAndPop([], - this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, - objectStack, this.gmlFormat_); - if (!goog.isDef(features)) { - features = []; - } - return features; +ol.style.Text.prototype.getStroke = function() { + return this.stroke_; }; /** - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response. - * @api stable + * Get the text to be rendered. + * @return {string|undefined} Text. + * @api */ -ol.format.WFS.prototype.readTransactionResponse = function(source) { - if (ol.xml.isDocument(source)) { - return this.readTransactionResponseFromDocument( - /** @type {Document} */ (source)); - } else if (ol.xml.isNode(source)) { - return this.readTransactionResponseFromNode(/** @type {Node} */ (source)); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readTransactionResponseFromDocument(doc); - } else { - goog.asserts.fail(); - return undefined; - } +ol.style.Text.prototype.getText = function() { + return this.text_; }; /** - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {ol.format.WFS.FeatureCollectionMetadata|undefined} - * FeatureCollection metadata. - * @api stable + * Get the text alignment. + * @return {string|undefined} Text align. + * @api */ -ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) { - if (ol.xml.isDocument(source)) { - return this.readFeatureCollectionMetadataFromDocument( - /** @type {Document} */ (source)); - } else if (ol.xml.isNode(source)) { - return this.readFeatureCollectionMetadataFromNode( - /** @type {Node} */ (source)); - } else if (goog.isString(source)) { - var doc = ol.xml.parse(source); - return this.readFeatureCollectionMetadataFromDocument(doc); - } else { - goog.asserts.fail(); - return undefined; - } +ol.style.Text.prototype.getTextAlign = function() { + return this.textAlign_; }; /** - * @param {Document} doc Document. - * @return {ol.format.WFS.FeatureCollectionMetadata|undefined} - * FeatureCollection metadata. + * Get the text baseline. + * @return {string|undefined} Text baseline. + * @api */ -ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = - function(doc) { - goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT); - for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - return this.readFeatureCollectionMetadataFromNode(n); - } - } - return undefined; +ol.style.Text.prototype.getTextBaseline = function() { + return this.textBaseline_; }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Set the font. + * + * @param {string|undefined} font Font. + * @api */ -ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = { - 'http://www.opengis.net/gml': { - 'boundedBy': ol.xml.makeObjectPropertySetter( - ol.format.GMLBase.prototype.readGeometryElement, 'bounds') - } +ol.style.Text.prototype.setFont = function(font) { + this.font_ = font; }; /** - * @param {Node} node Node. - * @return {ol.format.WFS.FeatureCollectionMetadata|undefined} - * FeatureCollection metadata. + * Set the x offset. + * + * @param {number} offsetX Horizontal text offset. */ -ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'FeatureCollection'); - var result = {}; - var value = ol.format.XSD.readNonNegativeIntegerString( - node.getAttribute('numberOfFeatures')); - goog.object.set(result, 'numberOfFeatures', value); - return ol.xml.pushParseAndPop( - /** @type {ol.format.WFS.FeatureCollectionMetadata} */ (result), - ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_); +ol.style.Text.prototype.setOffsetX = function(offsetX) { + this.offsetX_ = offsetX; }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Set the y offset. + * + * @param {number} offsetY Vertical text offset. */ -ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = { - 'http://www.opengis.net/wfs': { - 'totalInserted': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger), - 'totalUpdated': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger), - 'totalDeleted': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger) - } +ol.style.Text.prototype.setOffsetY = function(offsetY) { + this.offsetY_ = offsetY; +}; + + +/** + * Set the fill. + * + * @param {ol.style.Fill} fill Fill style. + * @api + */ +ol.style.Text.prototype.setFill = function(fill) { + this.fill_ = fill; +}; + + +/** + * Set the rotation. + * + * @param {number|undefined} rotation Rotation. + * @api + */ +ol.style.Text.prototype.setRotation = function(rotation) { + this.rotation_ = rotation; +}; + + +/** + * Set the scale. + * + * @param {number|undefined} scale Scale. + * @api + */ +ol.style.Text.prototype.setScale = function(scale) { + this.scale_ = scale; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Transaction Summary. - * @private + * Set the stroke. + * + * @param {ol.style.Stroke} stroke Stroke style. + * @api */ -ol.format.WFS.readTransactionSummary_ = function(node, objectStack) { - return ol.xml.pushParseAndPop( - {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack); +ol.style.Text.prototype.setStroke = function(stroke) { + this.stroke_ = stroke; }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Set the text. + * + * @param {string|undefined} text Text. + * @api */ -ol.format.WFS.OGC_FID_PARSERS_ = { - 'http://www.opengis.net/ogc': { - 'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) { - return node.getAttribute('fid'); - }) - } +ol.style.Text.prototype.setText = function(text) { + this.text_ = text; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private + * Set the text alignment. + * + * @param {string|undefined} textAlign Text align. + * @api */ -ol.format.WFS.fidParser_ = function(node, objectStack) { - ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack); +ol.style.Text.prototype.setTextAlign = function(textAlign) { + this.textAlign_ = textAlign; }; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * Set the text baseline. + * + * @param {string|undefined} textBaseline Text baseline. + * @api */ -ol.format.WFS.INSERT_RESULTS_PARSERS_ = { - 'http://www.opengis.net/wfs': { - 'Feature': ol.format.WFS.fidParser_ - } +ol.style.Text.prototype.setTextBaseline = function(textBaseline) { + this.textBaseline_ = textBaseline; }; +// FIXME http://earth.google.com/kml/1.0 namespace? +// FIXME why does node.getAttribute return an unknown type? +// FIXME text +// FIXME serialize arbitrary feature properties +// FIXME don't parse style if extractStyles is false + +goog.provide('ol.format.KML'); + +goog.require('goog.Uri'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.math'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('ol.Feature'); +goog.require('ol.FeatureStyleFunction'); +goog.require('ol.color'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.LinearRing'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Icon'); +goog.require('ol.style.IconAnchorUnits'); +goog.require('ol.style.IconOrigin'); +goog.require('ol.style.Image'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); +goog.require('ol.xml'); + /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Array.<string>|undefined} Insert results. - * @private + * @typedef {{x: number, xunits: (ol.style.IconAnchorUnits|undefined), + * y: number, yunits: (ol.style.IconAnchorUnits|undefined)}} */ -ol.format.WFS.readInsertResults_ = function(node, objectStack) { - return ol.xml.pushParseAndPop( - [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack); -}; +ol.format.KMLVec2_; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} - * @private + * @typedef {{flatCoordinates: Array.<number>, + * whens: Array.<number>}} */ -ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = { - 'http://www.opengis.net/wfs': { - 'TransactionSummary': ol.xml.makeObjectPropertySetter( - ol.format.WFS.readTransactionSummary_, 'transactionSummary'), - 'InsertResults': ol.xml.makeObjectPropertySetter( - ol.format.WFS.readInsertResults_, 'insertIds') - } -}; +ol.format.KMLGxTrackObject_; + /** - * @param {Document} doc Document. - * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response. + * @classdesc + * Feature format for reading and writing data in the KML format. + * + * @constructor + * @extends {ol.format.XMLFeature} + * @param {olx.format.KMLOptions=} opt_options Options. + * @api stable */ -ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) { - goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT); - for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - return this.readTransactionResponseFromNode(n); +ol.format.KML = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + goog.base(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); + + var defaultStyle = goog.isDef(options.defaultStyle) ? + options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_; + + /** @type {Object.<string, (Array.<ol.style.Style>|string)>} */ + var sharedStyles = {}; + + var findStyle = + /** + * @param {Array.<ol.style.Style>|string|undefined} styleValue Style + * value. + * @return {Array.<ol.style.Style>} Style. + */ + function(styleValue) { + if (goog.isArray(styleValue)) { + return styleValue; + } else if (goog.isString(styleValue)) { + // KML files in the wild occasionally forget the leading `#` on styleUrls + // defined in the same document. Add a leading `#` if it enables to find + // a style. + if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) { + styleValue = '#' + styleValue; + } + return findStyle(sharedStyles[styleValue]); + } else { + return defaultStyle; } - } - return undefined; + }; + + /** + * @private + * @type {boolean} + */ + this.extractStyles_ = goog.isDef(options.extractStyles) ? + options.extractStyles : true; + + /** + * @private + * @type {Object.<string, (Array.<ol.style.Style>|string)>} + */ + this.sharedStyles_ = sharedStyles; + + /** + * @private + * @type {ol.FeatureStyleFunction} + */ + this.featureStyleFunction_ = + /** + * @param {number} resolution Resolution. + * @return {Array.<ol.style.Style>} Style. + * @this {ol.Feature} + */ + function(resolution) { + var style = /** @type {Array.<ol.style.Style>|undefined} */ + (this.get('Style')); + if (goog.isDef(style)) { + return style; + } + var styleUrl = /** @type {string|undefined} */ (this.get('styleUrl')); + if (goog.isDef(styleUrl)) { + return findStyle(styleUrl); + } + return defaultStyle; + }; + }; +goog.inherits(ol.format.KML, ol.format.XMLFeature); /** - * @param {Node} node Node. - * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response. + * @const + * @type {Array.<string>} + * @private */ -ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'TransactionResponse'); - return ol.xml.pushParseAndPop( - /** @type {ol.format.WFS.TransactionResponse} */({}), - ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []); -}; +ol.format.KML.EXTENSIONS_ = ['.kml']; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @const + * @type {Array.<string>} * @private */ -ol.format.WFS.QUERY_SERIALIZERS_ = { - 'http://www.opengis.net/wfs': { - 'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) - } -}; +ol.format.KML.GX_NAMESPACE_URIS_ = [ + 'http://www.google.com/kml/ext/2.2' +]; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {Array.<string>} * @private */ -ol.format.WFS.writeFeature_ = function(node, feature, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var featureType = goog.object.get(context, 'featureType'); - var featureNS = goog.object.get(context, 'featureNS'); - var child = ol.xml.createElementNS(featureNS, featureType); - node.appendChild(child); - ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack); -}; +ol.format.KML.NAMESPACE_URIS_ = [ + null, + 'http://earth.google.com/kml/2.0', + 'http://earth.google.com/kml/2.1', + 'http://earth.google.com/kml/2.2', + 'http://www.opengis.net/kml/2.2' +]; /** - * @param {Node} node Node. - * @param {number|string} fid Feature identifier. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {string} * @private */ -ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) { - var filter = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter'); - var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'FeatureId'); - filter.appendChild(child); - child.setAttribute('fid', fid); - node.appendChild(filter); -}; +ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' + + 'https://developers.google.com/kml/schema/kml22gx.xsd'; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.Color} * @private */ -ol.format.WFS.writeDelete_ = function(node, feature, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var featureType = goog.object.get(context, 'featureType'); - var featurePrefix = goog.object.get(context, 'featurePrefix'); - featurePrefix = goog.isDef(featurePrefix) ? featurePrefix : - ol.format.WFS.FEATURE_PREFIX; - var featureNS = goog.object.get(context, 'featureNS'); - node.setAttribute('typeName', featurePrefix + ':' + featureType); - ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, - featureNS); - var fid = feature.getId(); - if (goog.isDef(fid)) { - ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); - } -}; +ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1]; /** - * @param {Node} node Node. - * @param {ol.Feature} feature Feature. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.style.Fill} * @private */ -ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var featureType = goog.object.get(context, 'featureType'); - var featurePrefix = goog.object.get(context, 'featurePrefix'); - featurePrefix = goog.isDef(featurePrefix) ? featurePrefix : - ol.format.WFS.FEATURE_PREFIX; - var featureNS = goog.object.get(context, 'featureNS'); - node.setAttribute('typeName', featurePrefix + ':' + featureType); - ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, - featureNS); - var fid = feature.getId(); - if (goog.isDef(fid)) { - var keys = feature.getKeys(); - var values = []; - for (var i = 0, ii = keys.length; i < ii; i++) { - var value = feature.get(keys[i]); - if (goog.isDef(value)) { - values.push({name: keys[i], value: value}); - } - } - ol.xml.pushSerializeAndPop({node: node, srsName: - goog.object.get(context, 'srsName')}, - ol.format.WFS.TRANSACTION_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('Property'), values, - objectStack); - ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); - } -}; +ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({ + color: ol.format.KML.DEFAULT_COLOR_ +}); /** - * @param {Node} node Node. - * @param {Object} pair Property name and value. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.Size} * @private */ -ol.format.WFS.writeProperty_ = function(node, pair, objectStack) { - var name = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Name'); - node.appendChild(name); - ol.format.XSD.writeStringTextNode(name, pair.name); - if (goog.isDefAndNotNull(pair.value)) { - var value = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Value'); - node.appendChild(value); - if (pair.value instanceof ol.geom.Geometry) { - ol.format.GML3.prototype.writeGeometryElement(value, - pair.value, objectStack); - } else { - ol.format.XSD.writeStringTextNode(value, pair.value); - } - } -}; +ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ? /** - * @param {Node} node Node. - * @param {{vendorId: string, safeToIgnore: boolean, value: string}} - * nativeElement The native element. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.style.IconAnchorUnits} * @private */ -ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) { - if (goog.isDef(nativeElement.vendorId)) { - node.setAttribute('vendorId', nativeElement.vendorId); - } - if (goog.isDef(nativeElement.safeToIgnore)) { - node.setAttribute('safeToIgnore', nativeElement.safeToIgnore); - } - if (goog.isDef(nativeElement.value)) { - ol.format.XSD.writeStringTextNode(node, nativeElement.value); - } -}; +ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ = + ol.style.IconAnchorUnits.PIXELS; /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @const + * @type {ol.style.IconAnchorUnits} * @private */ -ol.format.WFS.TRANSACTION_SERIALIZERS_ = { - 'http://www.opengis.net/wfs': { - 'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_), - 'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_), - 'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_), - 'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_), - 'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_) - } -}; +ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ = + ol.style.IconAnchorUnits.PIXELS; /** - * @param {Node} node Node. - * @param {string} featureType Feature type. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.Size} * @private */ -ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var featurePrefix = goog.object.get(context, 'featurePrefix'); - var featureNS = goog.object.get(context, 'featureNS'); - var propertyNames = goog.object.get(context, 'propertyNames'); - var srsName = goog.object.get(context, 'srsName'); - var prefix = goog.isDef(featurePrefix) ? featurePrefix + ':' : ''; - node.setAttribute('typeName', prefix + featureType); - if (goog.isDef(srsName)) { - node.setAttribute('srsName', srsName); - } - if (goog.isDef(featureNS)) { - ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, - featureNS); - } - var item = goog.object.clone(context); - item.node = node; - ol.xml.pushSerializeAndPop(item, - ol.format.WFS.QUERY_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames, - objectStack); - var bbox = goog.object.get(context, 'bbox'); - if (goog.isDef(bbox)) { - var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter'); - ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack); - node.appendChild(child); - } -}; +ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64]; /** - * @param {Node} node Node. - * @param {string} value PropertyName value. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {string} * @private */ -ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) { - var property = ol.xml.createElementNS('http://www.opengis.net/ogc', - 'PropertyName'); - ol.format.XSD.writeStringTextNode(property, value); - node.appendChild(property); -}; +ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ = + 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png'; /** - * @param {Node} node Node. - * @param {ol.Extent} bbox Bounding box. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.style.Image} * @private */ -ol.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var geometryName = goog.object.get(context, 'geometryName'); - var bboxNode = ol.xml.createElementNS('http://www.opengis.net/ogc', 'BBOX'); - node.appendChild(bboxNode); - ol.format.WFS.writeOgcPropertyName_(bboxNode, geometryName, objectStack); - ol.format.GML3.prototype.writeGeometryElement(bboxNode, bbox, objectStack); -}; +ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({ + anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_, + anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT, + anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_, + anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_, + crossOrigin: 'anonymous', + rotation: 0, + scale: 0.5, + size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_, + src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ +}); /** - * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @const + * @type {ol.style.Stroke} * @private */ -ol.format.WFS.GETFEATURE_SERIALIZERS_ = { - 'http://www.opengis.net/wfs': { - 'Query': ol.xml.makeChildAppender( - ol.format.WFS.writeQuery_) - } -}; +ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ + color: ol.format.KML.DEFAULT_COLOR_, + width: 1 +}); /** - * @param {Node} node Node. - * @param {Array.<{string}>} featureTypes Feature types. - * @param {Array.<*>} objectStack Node stack. + * @const + * @type {ol.style.Text} * @private */ -ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) { - var context = objectStack[objectStack.length - 1]; - goog.asserts.assert(goog.isObject(context)); - var item = goog.object.clone(context); - item.node = node; - ol.xml.pushSerializeAndPop(item, - ol.format.WFS.GETFEATURE_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('Query'), featureTypes, - objectStack); -}; +ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({ + font: 'normal 16px Helvetica', + fill: ol.format.KML.DEFAULT_FILL_STYLE_, + stroke: ol.format.KML.DEFAULT_STROKE_STYLE_, + scale: 1 +}); /** - * @param {olx.format.WFSWriteGetFeatureOptions} options Options. - * @return {Node} Result. - * @api stable + * @const + * @type {ol.style.Style} + * @private */ -ol.format.WFS.prototype.writeGetFeature = function(options) { - var node = ol.xml.createElementNS('http://www.opengis.net/wfs', - 'GetFeature'); - node.setAttribute('service', 'WFS'); - node.setAttribute('version', '1.1.0'); - if (goog.isDef(options)) { - if (goog.isDef(options.handle)) { - node.setAttribute('handle', options.handle); - } - if (goog.isDef(options.outputFormat)) { - node.setAttribute('outputFormat', options.outputFormat); - } - if (goog.isDef(options.maxFeatures)) { - node.setAttribute('maxFeatures', options.maxFeatures); - } - if (goog.isDef(options.resultType)) { - node.setAttribute('resultType', options.resultType); - } - if (goog.isDef(options.startIndex)) { - node.setAttribute('startIndex', options.startIndex); - } - if (goog.isDef(options.count)) { - node.setAttribute('count', options.count); - } - } - ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:schemaLocation', this.schemaLocation_); - var context = { - node: node, - srsName: options.srsName, - featureNS: goog.isDef(options.featureNS) ? - options.featureNS : this.featureNS_, - featurePrefix: options.featurePrefix, - geometryName: options.geometryName, - bbox: options.bbox, - propertyNames: goog.isDef(options.propertyNames) ? - options.propertyNames : [] - }; - goog.asserts.assert(goog.isArray(options.featureTypes)); - ol.format.WFS.writeGetFeature_(node, options.featureTypes, [context]); - return node; -}; +ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({ + fill: ol.format.KML.DEFAULT_FILL_STYLE_, + image: ol.format.KML.DEFAULT_IMAGE_STYLE_, + text: ol.format.KML.DEFAULT_TEXT_STYLE_, + stroke: ol.format.KML.DEFAULT_STROKE_STYLE_, + zIndex: 0 +}); /** - * @param {Array.<ol.Feature>} inserts The features to insert. - * @param {Array.<ol.Feature>} updates The features to update. - * @param {Array.<ol.Feature>} deletes The features to delete. - * @param {olx.format.WFSWriteTransactionOptions} options Write options. - * @return {Node} Result. - * @api stable + * @const + * @type {Array.<ol.style.Style>} + * @private */ -ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes, - options) { - var objectStack = []; - var node = ol.xml.createElementNS('http://www.opengis.net/wfs', - 'Transaction'); - node.setAttribute('service', 'WFS'); - node.setAttribute('version', '1.1.0'); - var baseObj, obj; - if (goog.isDef(options)) { - baseObj = goog.isDef(options.gmlOptions) ? options.gmlOptions : {}; - if (goog.isDef(options.handle)) { - node.setAttribute('handle', options.handle); - } - } - ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:schemaLocation', this.schemaLocation_); - if (goog.isDefAndNotNull(inserts)) { - obj = {node: node, featureNS: options.featureNS, - featureType: options.featureType, featurePrefix: options.featurePrefix}; - goog.object.extend(obj, baseObj); - ol.xml.pushSerializeAndPop(obj, - ol.format.WFS.TRANSACTION_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('Insert'), inserts, - objectStack); - } - if (goog.isDefAndNotNull(updates)) { - obj = {node: node, featureNS: options.featureNS, - featureType: options.featureType, featurePrefix: options.featurePrefix}; - goog.object.extend(obj, baseObj); - ol.xml.pushSerializeAndPop(obj, - ol.format.WFS.TRANSACTION_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('Update'), updates, - objectStack); - } - if (goog.isDefAndNotNull(deletes)) { - ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, - featureType: options.featureType, featurePrefix: options.featurePrefix}, - ol.format.WFS.TRANSACTION_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('Delete'), deletes, - objectStack); - } - if (goog.isDef(options.nativeElements)) { - ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, - featureType: options.featureType, featurePrefix: options.featurePrefix}, - ol.format.WFS.TRANSACTION_SERIALIZERS_, - ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements, - objectStack); - } - return node; -}; +ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_]; /** - * Read the projection from a WFS source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {?ol.proj.Projection} Projection. - * @api stable + * @const + * @type {Object.<string, ol.style.IconAnchorUnits>} + * @private */ -ol.format.WFS.prototype.readProjection; +ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = { + 'fraction': ol.style.IconAnchorUnits.FRACTION, + 'pixels': ol.style.IconAnchorUnits.PIXELS +}; /** - * @inheritDoc + * @param {Node} node Node. + * @private + * @return {ol.Color|undefined} Color. */ -ol.format.WFS.prototype.readProjectionFromDocument = function(doc) { - goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT); - for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - return this.readProjectionFromNode(n); - } +ol.format.KML.readColor_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + // The KML specification states that colors should not include a leading `#` + // but we tolerate them. + var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s); + if (m) { + var hexColor = m[1]; + return [ + parseInt(hexColor.substr(6, 2), 16), + parseInt(hexColor.substr(4, 2), 16), + parseInt(hexColor.substr(2, 2), 16), + parseInt(hexColor.substr(0, 2), 16) / 255 + ]; + + } else { + return undefined; } - return null; }; /** - * @inheritDoc + * @param {Node} node Node. + * @private + * @return {Array.<number>|undefined} Flat coordinates. */ -ol.format.WFS.prototype.readProjectionFromNode = function(node) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'FeatureCollection'); - node = node.firstElementChild.firstElementChild; - if (goog.isDefAndNotNull(node)) { - for (var n = node.firstElementChild; !goog.isNull(n); - n = n.nextElementSibling) { - if (!(n.childNodes.length === 0 || - (n.childNodes.length === 1 && - n.firstChild.nodeType === 3))) { - var objectStack = [{}]; - this.gmlFormat_.readGeometryElement(n, objectStack); - return ol.proj.get(objectStack.pop().srsName); - } - } +ol.format.KML.readFlatCoordinates_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + var flatCoordinates = []; + // The KML specification states that coordinate tuples should not include + // spaces, but we tolerate them. + var re = + /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i; + var m; + while ((m = re.exec(s))) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = m[3] ? parseFloat(m[3]) : 0; + flatCoordinates.push(x, y, z); + s = s.substr(m[0].length); } - return null; + if (s !== '') { + return undefined; + } + return flatCoordinates; }; -goog.provide('ol.format.WKT'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('ol.Feature'); -goog.require('ol.format.Feature'); -goog.require('ol.format.TextFeature'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.GeometryCollection'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); +/** + * @param {Node} node Node. + * @private + * @return {string|undefined} Style URL. + */ +ol.format.KML.readStyleUrl_ = function(node) { + var s = goog.string.trim(ol.xml.getAllTextContent(node, false)); + if (goog.isDefAndNotNull(node.baseURI)) { + return goog.Uri.resolve(node.baseURI, s).toString(); + } else { + return s; + } +}; /** - * @constructor - * @extends {ol.format.TextFeature} - * @param {olx.format.WKTOptions=} opt_options Options. - * @api stable + * @param {Node} node Node. + * @private + * @return {string} URI. */ -ol.format.WKT = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this); +ol.format.KML.readURI_ = function(node) { + var s = ol.xml.getAllTextContent(node, false); + if (goog.isDefAndNotNull(node.baseURI)) { + return goog.Uri.resolve(node.baseURI, goog.string.trim(s)).toString(); + } else { + return goog.string.trim(s); + } +}; - /** - * Split GeometryCollection into multiple features. - * @type {boolean} - * @private - */ - this.splitCollection_ = goog.isDef(options.splitCollection) ? - options.splitCollection : false; +/** + * @param {Node} node Node. + * @private + * @return {ol.format.KMLVec2_} Vec2. + */ +ol.format.KML.readVec2_ = function(node) { + var xunits = node.getAttribute('xunits'); + var yunits = node.getAttribute('yunits'); + return { + x: parseFloat(node.getAttribute('x')), + xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits], + y: parseFloat(node.getAttribute('y')), + yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits] + }; }; -goog.inherits(ol.format.WKT, ol.format.TextFeature); /** - * @const - * @type {string} + * @param {Node} node Node. + * @private + * @return {number|undefined} Scale. */ -ol.format.WKT.EMPTY = 'EMPTY'; +ol.format.KML.readScale_ = function(node) { + var number = ol.format.XSD.readDecimal(node); + if (goog.isDef(number)) { + return Math.sqrt(number); + } else { + return undefined; + } +}; /** - * @param {ol.geom.Point} geom Point geometry. - * @return {string} Coordinates part of Point as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Array.<ol.style.Style>|string|undefined} StyleMap. */ -ol.format.WKT.encodePointGeometry_ = function(geom) { - var coordinates = geom.getCoordinates(); - if (goog.array.isEmpty(coordinates)) { - return ''; - } - return coordinates[0] + ' ' + coordinates[1]; +ol.format.KML.readStyleMapValue_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + /** @type {Array.<ol.style.Style>|string|undefined} */ (undefined), + ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); }; /** - * @param {ol.geom.MultiPoint} geom MultiPoint geometry. - * @return {string} Coordinates part of MultiPoint as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.encodeMultiPointGeometry_ = function(geom) { - var array = []; - var components = geom.getPoints(); - for (var i = 0, ii = components.length; i < ii; ++i) { - array.push('(' + ol.format.WKT.encodePointGeometry_(components[i]) + ')'); +ol.format.KML.IconStyleParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be an ELEMENT'); + goog.asserts.assert(node.localName == 'IconStyle', + 'localName should be IconStyle'); + // FIXME refreshMode + // FIXME refreshInterval + // FIXME viewRefreshTime + // FIXME viewBoundScale + // FIXME viewFormat + // FIXME httpQuery + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.ICON_STYLE_PARSERS_, node, objectStack); + if (!goog.isDef(object)) { + return; } - return array.join(','); + var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(styleObject), + 'styleObject should be an Object'); + var IconObject = /** @type {Object} */ (goog.object.get(object, 'Icon', {})); + var src; + var href = /** @type {string|undefined} */ + (IconObject['href']); + if (goog.isDef(href)) { + src = href; + } else { + src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_; + } + var anchor, anchorXUnits, anchorYUnits; + var hotSpot = /** @type {ol.format.KMLVec2_|undefined} */ + (object['hotSpot']); + if (goog.isDef(hotSpot)) { + anchor = [hotSpot.x, hotSpot.y]; + anchorXUnits = hotSpot.xunits; + anchorYUnits = hotSpot.yunits; + } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { + anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_; + anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_; + anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_; + } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) { + anchor = [0.5, 0]; + anchorXUnits = ol.style.IconAnchorUnits.FRACTION; + anchorYUnits = ol.style.IconAnchorUnits.FRACTION; + } + + var offset; + var x = /** @type {number|undefined} */ + (IconObject['x']); + var y = /** @type {number|undefined} */ + (IconObject['y']); + if (goog.isDef(x) && goog.isDef(y)) { + offset = [x, y]; + } + + var size; + var w = /** @type {number|undefined} */ + (IconObject['w']); + var h = /** @type {number|undefined} */ + (IconObject['h']); + if (goog.isDef(w) && goog.isDef(h)) { + size = [w, h]; + } + + var rotation; + var heading = /** @type {number|undefined} */ + (object['heading']); + if (goog.isDef(heading)) { + rotation = goog.math.toRadians(heading); + } + + var scale = /** @type {number|undefined} */ + (object['scale']); + if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) { + size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_; + } + + var imageStyle = new ol.style.Icon({ + anchor: anchor, + anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT, + anchorXUnits: anchorXUnits, + anchorYUnits: anchorYUnits, + crossOrigin: 'anonymous', // FIXME should this be configurable? + offset: offset, + offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT, + rotation: rotation, + scale: scale, + size: size, + src: src + }); + styleObject['imageStyle'] = imageStyle; }; /** - * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry. - * @return {string} Coordinates part of GeometryCollection as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.encodeGeometryCollectionGeometry_ = function(geom) { - var array = []; - var geoms = geom.getGeometries(); - for (var i = 0, ii = geoms.length; i < ii; ++i) { - array.push(ol.format.WKT.encode_(geoms[i])); +ol.format.KML.LabelStyleParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LabelStyle', + 'localName should be LabelStyle'); + // FIXME colorMode + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack); + if (!goog.isDef(object)) { + return; } - return array.join(','); + var styleObject = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(styleObject), + 'styleObject should be an Object'); + var textStyle = new ol.style.Text({ + fill: new ol.style.Fill({ + color: /** @type {ol.Color} */ + (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)) + }), + scale: /** @type {number|undefined} */ + (object['scale']) + }); + styleObject['textStyle'] = textStyle; }; /** - * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry. - * @return {string} Coordinates part of LineString as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.encodeLineStringGeometry_ = function(geom) { - var coordinates = geom.getCoordinates(); - var array = []; - for (var i = 0, ii = coordinates.length; i < ii; ++i) { - array.push(coordinates[i][0] + ' ' + coordinates[i][1]); +ol.format.KML.LineStyleParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LineStyle', + 'localName should be LineStyle'); + // FIXME colorMode + // FIXME gx:outerColor + // FIXME gx:outerWidth + // FIXME gx:physicalWidth + // FIXME gx:labelVisibility + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.LINE_STYLE_PARSERS_, node, objectStack); + if (!goog.isDef(object)) { + return; } - return array.join(','); + var styleObject = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(styleObject), + 'styleObject should be an Object'); + var strokeStyle = new ol.style.Stroke({ + color: /** @type {ol.Color} */ + (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)), + width: /** @type {number} */ (goog.object.get(object, 'width', 1)) + }); + styleObject['strokeStyle'] = strokeStyle; }; /** - * @param {ol.geom.MultiLineString} geom MultiLineString geometry. - * @return {string} Coordinates part of MultiLineString as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) { - var array = []; - var components = geom.getLineStrings(); - for (var i = 0, ii = components.length; i < ii; ++i) { - array.push('(' + ol.format.WKT.encodeLineStringGeometry_( - components[i]) + ')'); +ol.format.KML.PolyStyleParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'PolyStyle', + 'localName should be PolyStyle'); + // FIXME colorMode + var object = ol.xml.pushParseAndPop( + {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack); + if (!goog.isDef(object)) { + return; + } + var styleObject = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(styleObject), + 'styleObject should be an Object'); + var fillStyle = new ol.style.Fill({ + color: /** @type {ol.Color} */ + (goog.object.get(object, 'color', ol.format.KML.DEFAULT_COLOR_)) + }); + styleObject['fillStyle'] = fillStyle; + var fill = /** @type {boolean|undefined} */ (object['fill']); + if (goog.isDef(fill)) { + styleObject['fill'] = fill; + } + var outline = + /** @type {boolean|undefined} */ (object['outline']); + if (goog.isDef(outline)) { + styleObject['outline'] = outline; } - return array.join(','); }; /** - * @param {ol.geom.Polygon} geom Polygon geometry. - * @return {string} Coordinates part of Polygon as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Array.<number>} LinearRing flat coordinates. */ -ol.format.WKT.encodePolygonGeometry_ = function(geom) { - var array = []; - var rings = geom.getLinearRings(); - for (var i = 0, ii = rings.length; i < ii; ++i) { - array.push('(' + ol.format.WKT.encodeLineStringGeometry_( - rings[i]) + ')'); - } - return array.join(','); +ol.format.KML.readFlatLinearRing_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop( + null, ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack)); }; /** - * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry. - * @return {string} Coordinates part of MultiPolygon as WKT. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) { - var array = []; - var components = geom.getPolygons(); - for (var i = 0, ii = components.length; i < ii; ++i) { - array.push('(' + ol.format.WKT.encodePolygonGeometry_( - components[i]) + ')'); +ol.format.KML.gxCoordParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(goog.array.contains( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + goog.asserts.assert(node.localName == 'coord', 'localName should be coord'); + var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(gxTrackObject), + 'gxTrackObject should be an Object'); + var flatCoordinates = gxTrackObject.flatCoordinates; + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i; + var m = re.exec(s); + if (m) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = parseFloat(m[3]); + flatCoordinates.push(x, y, z, 0); + } else { + flatCoordinates.push(0, 0, 0, 0); } - return array.join(','); }; /** - * Encode a geometry as WKT. - * @param {ol.geom.Geometry} geom The geometry to encode. - * @return {string} WKT string for the geometry. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. */ -ol.format.WKT.encode_ = function(geom) { - var type = geom.getType(); - var geometryEncoder = ol.format.WKT.GeometryEncoder_[type]; - goog.asserts.assert(goog.isDef(geometryEncoder)); - var enc = geometryEncoder(geom); - type = type.toUpperCase(); - if (enc.length === 0) { - return type + ' ' + ol.format.WKT.EMPTY; +ol.format.KML.readGxMultiTrack_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(goog.array.contains( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + goog.asserts.assert(node.localName == 'MultiTrack', + 'localName should be MultiTrack'); + var lineStrings = ol.xml.pushParseAndPop( + /** @type {Array.<ol.geom.LineString>} */ ([]), + ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack); + if (!goog.isDef(lineStrings)) { + return undefined; } - return type + '(' + enc + ')'; + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; }; /** - * @const - * @type {Object.<string, function(ol.geom.Geometry): string>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {ol.geom.LineString|undefined} LineString. */ -ol.format.WKT.GeometryEncoder_ = { - 'Point': ol.format.WKT.encodePointGeometry_, - 'LineString': ol.format.WKT.encodeLineStringGeometry_, - 'Polygon': ol.format.WKT.encodePolygonGeometry_, - 'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_, - 'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_, - 'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_, - 'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_ +ol.format.KML.readGxTrack_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(goog.array.contains( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI), + 'namespaceURI of the node should be known to the KML parser'); + goog.asserts.assert(node.localName == 'Track', 'localName should be Track'); + var gxTrackObject = ol.xml.pushParseAndPop( + /** @type {ol.format.KMLGxTrackObject_} */ ({ + flatCoordinates: [], + whens: [] + }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack); + if (!goog.isDef(gxTrackObject)) { + return undefined; + } + var flatCoordinates = gxTrackObject.flatCoordinates; + var whens = gxTrackObject.whens; + goog.asserts.assert(flatCoordinates.length / 4 == whens.length, + 'the length of the flatCoordinates array divided by 4 should be the ' + + 'length of the whens array'); + var i, ii; + for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii; + ++i) { + flatCoordinates[4 * i + 3] = whens[i]; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); + return lineString; }; /** - * Parse a WKT string. - * @param {string} wkt WKT string. - * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined} - * The geometry created. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Object} Icon object. */ -ol.format.WKT.prototype.parse_ = function(wkt) { - var lexer = new ol.format.WKT.Lexer(wkt); - var parser = new ol.format.WKT.Parser(lexer); - return parser.parse(); +ol.format.KML.readIcon_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Icon', 'localName should be Icon'); + var iconObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.ICON_PARSERS_, node, objectStack); + if (goog.isDef(iconObject)) { + return iconObject; + } else { + return null; + } }; /** - * Read a feature from a WKT source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.Feature} Feature. - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<number>} Flat coordinates. */ -ol.format.WKT.prototype.readFeature; +ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return /** @type {Array.<number>} */ (ol.xml.pushParseAndPop(null, + ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack)); +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. */ -ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) { - var geom = this.readGeometryFromText(text, opt_options); - if (goog.isDef(geom)) { - var feature = new ol.Feature(); - feature.setGeometry(geom); - return feature; +ol.format.KML.readLineString_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LineString', + 'localName should be LineString'); + var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}), + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (goog.isDef(flatCoordinates)) { + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + lineString.setProperties(properties); + return lineString; + } else { + return undefined; } - return null; }; /** - * Read all features from a WKT source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. */ -ol.format.WKT.prototype.readFeatures; +ol.format.KML.readLinearRing_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'LinearRing', + 'localName should be LinearRing'); + var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}), + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (goog.isDef(flatCoordinates)) { + var polygon = new ol.geom.Polygon(null); + polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates, + [flatCoordinates.length]); + polygon.setProperties(properties); + return polygon; + } else { + return undefined; + } +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Geometry} Geometry. */ -ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) { - var geometries = []; - var geometry = this.readGeometryFromText(text, opt_options); - if (this.splitCollection_ && - geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) { - geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry)) - .getGeometriesArray(); - } else { - geometries = [geometry]; +ol.format.KML.readMultiGeometry_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MultiGeometry', + 'localName should be MultiGeometry'); + var geometries = ol.xml.pushParseAndPop( + /** @type {Array.<ol.geom.Geometry>} */ ([]), + ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack); + if (!goog.isDef(geometries)) { + return null; } - var feature, features = []; - for (var i = 0, ii = geometries.length; i < ii; ++i) { - feature = new ol.Feature(); - feature.setGeometry(geometries[i]); - features.push(feature); + if (geometries.length === 0) { + return new ol.geom.GeometryCollection(geometries); + } + var homogeneous = true; + var type = geometries[0].getType(); + var geometry, i, ii; + for (i = 1, ii = geometries.length; i < ii; ++i) { + geometry = geometries[i]; + if (geometry.getType() != type) { + homogeneous = false; + break; + } + } + if (homogeneous) { + /** @type {ol.geom.GeometryLayout} */ + var layout; + /** @type {Array.<number>} */ + var flatCoordinates; + if (type == ol.geom.GeometryType.POINT) { + var point = geometries[0]; + goog.asserts.assertInstanceof(point, ol.geom.Point, + 'point should be an ol.geom.Point'); + layout = point.getLayout(); + flatCoordinates = point.getFlatCoordinates(); + for (i = 1, ii = geometries.length; i < ii; ++i) { + geometry = geometries[i]; + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + goog.asserts.assert(geometry.getLayout() == layout, + 'geometry layout should be consistent'); + goog.array.extend(flatCoordinates, geometry.getFlatCoordinates()); + } + var multiPoint = new ol.geom.MultiPoint(null); + multiPoint.setFlatCoordinates(layout, flatCoordinates); + ol.format.KML.setCommonGeometryProperties_(multiPoint, geometries); + return multiPoint; + } else if (type == ol.geom.GeometryType.LINE_STRING) { + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(geometries); + ol.format.KML.setCommonGeometryProperties_(multiLineString, geometries); + return multiLineString; + } else if (type == ol.geom.GeometryType.POLYGON) { + var multiPolygon = new ol.geom.MultiPolygon(null); + multiPolygon.setPolygons(geometries); + ol.format.KML.setCommonGeometryProperties_(multiPolygon, geometries); + return multiPolygon; + } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + return new ol.geom.GeometryCollection(geometries); + } else { + goog.asserts.fail('Unexpected type: ' + type); + return null; + } + } else { + return new ol.geom.GeometryCollection(geometries); } - return features; }; /** - * Read a single geometry from a WKT source. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Read options. - * @return {ol.geom.Geometry} Geometry. - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Point|undefined} Point. */ -ol.format.WKT.prototype.readGeometry; +ol.format.KML.readPoint_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Point', 'localName should be Point'); + var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}), + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatCoordinates = + ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack); + if (goog.isDefAndNotNull(flatCoordinates)) { + var point = new ol.geom.Point(null); + goog.asserts.assert(flatCoordinates.length == 3, + 'flatCoordinates should have a length of 3'); + point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates); + point.setProperties(properties); + return point; + } else { + return undefined; + } +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.Polygon|undefined} Polygon. */ -ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) { - var geometry = this.parse_(text); - if (goog.isDef(geometry)) { - return /** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions(geometry, false, opt_options)); +ol.format.KML.readPolygon_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Polygon', + 'localName should be Polygon'); + var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}), + ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node, + objectStack); + var flatLinearRings = ol.xml.pushParseAndPop( + /** @type {Array.<Array.<number>>} */ ([null]), + ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack); + if (goog.isDefAndNotNull(flatLinearRings) && + !goog.isNull(flatLinearRings[0])) { + var polygon = new ol.geom.Polygon(null); + var flatCoordinates = flatLinearRings[0]; + var ends = [flatCoordinates.length]; + var i, ii; + for (i = 1, ii = flatLinearRings.length; i < ii; ++i) { + goog.array.extend(flatCoordinates, flatLinearRings[i]); + ends.push(flatCoordinates.length); + } + polygon.setFlatCoordinates( + ol.geom.GeometryLayout.XYZ, flatCoordinates, ends); + polygon.setProperties(properties); + return polygon; } else { - return null; + return undefined; } }; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.<ol.style.Style>} Style. */ -ol.format.WKT.prototype.readProjectionFromText = function(text) { - return null; +ol.format.KML.readStyle_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Style', 'localName should be Style'); + var styleObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.STYLE_PARSERS_, node, objectStack); + if (!goog.isDef(styleObject)) { + return null; + } + var fillStyle = /** @type {ol.style.Fill} */ (goog.object.get( + styleObject, 'fillStyle', ol.format.KML.DEFAULT_FILL_STYLE_)); + var fill = /** @type {boolean|undefined} */ + (styleObject['fill']); + if (goog.isDef(fill) && !fill) { + fillStyle = null; + } + var imageStyle = /** @type {ol.style.Image} */ (goog.object.get( + styleObject, 'imageStyle', ol.format.KML.DEFAULT_IMAGE_STYLE_)); + var textStyle = /** @type {ol.style.Text} */ (goog.object.get( + styleObject, 'textStyle', ol.format.KML.DEFAULT_TEXT_STYLE_)); + var strokeStyle = /** @type {ol.style.Stroke} */ (goog.object.get( + styleObject, 'strokeStyle', ol.format.KML.DEFAULT_STROKE_STYLE_)); + var outline = /** @type {boolean|undefined} */ + (styleObject['outline']); + if (goog.isDef(outline) && !outline) { + strokeStyle = null; + } + return [new ol.style.Style({ + fill: fillStyle, + image: imageStyle, + stroke: strokeStyle, + text: textStyle, + zIndex: undefined // FIXME + })]; }; /** - * Encode a feature as a WKT string. - * - * @function - * @param {ol.Feature} feature Feature. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} WKT string. - * @api stable + * Reads an array of geometries and creates arrays for common geometry + * properties. Then sets them to the multi geometry. + * @param {ol.geom.MultiPoint|ol.geom.MultiLineString|ol.geom.MultiPolygon} + * multiGeometry + * @param {Array.<ol.geom.Geometry>} geometries + * @private */ -ol.format.WKT.prototype.writeFeature; +ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry, + geometries) { + var ii = geometries.length; + var extrudes = new Array(geometries.length); + var altitudeModes = new Array(geometries.length); + var geometry, i, hasExtrude, hasAltitudeMode; + hasExtrude = hasAltitudeMode = false; + for (i = 0; i < ii; ++i) { + geometry = geometries[i]; + extrudes[i] = geometry.get('extrude'); + altitudeModes[i] = geometry.get('altitudeMode'); + hasExtrude = hasExtrude || goog.isDef(extrudes[i]); + hasAltitudeMode = hasAltitudeMode || goog.isDef(altitudeModes[i]); + } + if (hasExtrude) { + multiGeometry.set('extrude', extrudes); + } + if (hasAltitudeMode) { + multiGeometry.set('altitudeMode', altitudeModes); + } +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) { - var geometry = feature.getGeometry(); - if (goog.isDef(geometry)) { - return this.writeGeometryText(geometry, opt_options); +ol.format.KML.DataParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Data', 'localName should be Data'); + var name = node.getAttribute('name'); + if (!goog.isNull(name)) { + var data = ol.xml.pushParseAndPop( + undefined, ol.format.KML.DATA_PARSERS_, node, objectStack); + if (goog.isDef(data)) { + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(featureObject), + 'featureObject should be an Object'); + featureObject[name] = data; + } } - return ''; }; /** - * Encode an array of features as a WKT string. - * - * @function - * @param {Array.<ol.Feature>} features Features. - * @param {olx.format.WriteOptions=} opt_options Write options. - * @return {string} WKT string. - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.prototype.writeFeatures; +ol.format.KML.ExtendedDataParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ExtendedData', + 'localName should be ExtendedData'); + ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack); +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) { - if (features.length == 1) { - return this.writeFeatureText(features[0], opt_options); +ol.format.KML.PairDataParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Pair', 'localName should be Pair'); + var pairObject = ol.xml.pushParseAndPop( + {}, ol.format.KML.PAIR_PARSERS_, node, objectStack); + if (!goog.isDef(pairObject)) { + return; } - var geometries = []; - for (var i = 0, ii = features.length; i < ii; ++i) { - geometries.push(features[i].getGeometry()); + var key = /** @type {string|undefined} */ + (pairObject['key']); + if (goog.isDef(key) && key == 'normal') { + var styleUrl = /** @type {string|undefined} */ + (pairObject['styleUrl']); + if (goog.isDef(styleUrl)) { + objectStack[objectStack.length - 1] = styleUrl; + } + var Style = /** @type {ol.style.Style} */ + (pairObject['Style']); + if (goog.isDef(Style)) { + objectStack[objectStack.length - 1] = Style; + } } - var collection = new ol.geom.GeometryCollection(geometries); - return this.writeGeometryText(collection, opt_options); }; /** - * Write a single geometry as a WKT string. - * - * @function - * @param {ol.geom.Geometry} geometry Geometry. - * @return {string} WKT string. - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.prototype.writeGeometry; +ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'StyleMap', + 'localName should be StyleMap'); + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!goog.isDef(styleMapValue)) { + return; + } + var placemarkObject = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(placemarkObject), + 'placemarkObject should be an Object'); + if (goog.isArray(styleMapValue)) { + placemarkObject['Style'] = styleMapValue; + } else if (goog.isString(styleMapValue)) { + placemarkObject['styleUrl'] = styleMapValue; + } else { + goog.asserts.fail('styleMapValue has an unknown type'); + } +}; /** - * @inheritDoc + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) { - return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ ( - ol.format.Feature.transformWithOptions(geometry, true, opt_options))); +ol.format.KML.SchemaDataParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'SchemaData', + 'localName should be SchemaData'); + ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack); }; /** - * @typedef {{type: number, value: (number|string|undefined), position: number}} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.Token; +ol.format.KML.SimpleDataParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'SimpleData', + 'localName should be SimpleData'); + var name = node.getAttribute('name'); + if (!goog.isNull(name)) { + var data = ol.format.XSD.readString(node); + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); + featureObject[name] = data; + } +}; /** - * @const - * @enum {number} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.TokenType = { - TEXT: 1, - LEFT_PAREN: 2, - RIGHT_PAREN: 3, - NUMBER: 4, - COMMA: 5, - EOF: 6 +ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'innerBoundaryIs', + 'localName should be innerBoundaryIs'); + var flatLinearRing = ol.xml.pushParseAndPop( + /** @type {Array.<number>|undefined} */ (undefined), + ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack); + if (goog.isDef(flatLinearRing)) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + goog.asserts.assert(flatLinearRings.length > 0, + 'flatLinearRings array should not be empty'); + flatLinearRings.push(flatLinearRing); + } }; - /** - * Class to tokenize a WKT string. - * @param {string} wkt WKT string. - * @constructor - * @protected + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.format.WKT.Lexer = function(wkt) { - - /** - * @type {string} - */ - this.wkt = wkt; - - /** - * @type {number} - * @private - */ - this.index_ = -1; +ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'outerBoundaryIs', + 'localName should be outerBoundaryIs'); + var flatLinearRing = ol.xml.pushParseAndPop( + /** @type {Array.<number>|undefined} */ (undefined), + ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack); + if (goog.isDef(flatLinearRing)) { + var flatLinearRings = /** @type {Array.<Array.<number>>} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isArray(flatLinearRings), + 'flatLinearRings should be an array'); + goog.asserts.assert(flatLinearRings.length > 0, + 'flatLinearRings array should not be empty'); + flatLinearRings[0] = flatLinearRing; + } }; /** - * @param {string} c Character. - * @return {boolean} Whether the character is alphabetic. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) { - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; +ol.format.KML.LinkParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Link', 'localName should be Link'); + ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack); }; /** - * @param {string} c Character. - * @param {boolean=} opt_decimal Whether the string number - * contains a dot, i.e. is a decimal number. - * @return {boolean} Whether the character is numeric. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) { - var decimal = goog.isDef(opt_decimal) ? opt_decimal : false; - return c >= '0' && c <= '9' || c == '.' && !decimal; +ol.format.KML.whenParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'when', 'localName should be when'); + var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(gxTrackObject), + 'gxTrackObject should be an Object'); + var whens = gxTrackObject.whens; + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/; + var m = re.exec(s); + if (m) { + var year = parseInt(m[1], 10); + var month = goog.isDef(m[3]) ? parseInt(m[3], 10) - 1 : 0; + var day = goog.isDef(m[5]) ? parseInt(m[5], 10) : 1; + var hour = goog.isDef(m[7]) ? parseInt(m[7], 10) : 0; + var minute = goog.isDef(m[8]) ? parseInt(m[8], 10) : 0; + var second = goog.isDef(m[9]) ? parseInt(m[9], 10) : 0; + var when = Date.UTC(year, month, day, hour, minute, second); + if (goog.isDef(m[10]) && m[10] != 'Z') { + var sign = m[11] == '-' ? -1 : 1; + when += sign * 60 * parseInt(m[12], 10); + if (goog.isDef(m[13])) { + when += sign * 60 * 60 * parseInt(m[13], 10); + } + } + whens.push(when); + } else { + whens.push(0); + } }; /** - * @param {string} c Character. - * @return {boolean} Whether the character is whitespace. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) { - return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -}; +ol.format.KML.DATA_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'value': ol.xml.makeReplacer(ol.format.XSD.readString) + }); /** - * @return {string} Next string character. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Lexer.prototype.nextChar_ = function() { - return this.wkt.charAt(++this.index_); -}; +ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Data': ol.format.KML.DataParser_, + 'SchemaData': ol.format.KML.SchemaDataParser_ + }); /** - * Fetch and return the next token. - * @return {!ol.format.WKT.Token} Next string token. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.WKT.Lexer.prototype.nextToken = function() { - var c = this.nextChar_(); - var token = {position: this.index_, value: c}; - - if (c == '(') { - token.type = ol.format.WKT.TokenType.LEFT_PAREN; - } else if (c == ',') { - token.type = ol.format.WKT.TokenType.COMMA; - } else if (c == ')') { - token.type = ol.format.WKT.TokenType.RIGHT_PAREN; - } else if (this.isNumeric_(c) || c == '-') { - token.type = ol.format.WKT.TokenType.NUMBER; - token.value = this.readNumber_(); - } else if (this.isAlpha_(c)) { - token.type = ol.format.WKT.TokenType.TEXT; - token.value = this.readText_(); - } else if (this.isWhiteSpace_(c)) { - return this.nextToken(); - } else if (c === '') { - token.type = ol.format.WKT.TokenType.EOF; - } else { - throw new Error('Unexpected character: ' + c); - } - - return token; -}; +ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); /** - * @return {number} Numeric token value. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Lexer.prototype.readNumber_ = function() { - var c, index = this.index_; - var decimal = false; - do { - if (c == '.') { - decimal = true; - } - c = this.nextChar_(); - } while (this.isNumeric_(c, decimal)); - return parseFloat(this.wkt.substring(index, this.index_--)); -}; +ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_, + 'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_ + }); /** - * @return {string} String token value. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Lexer.prototype.readText_ = function() { - var c, index = this.index_; - do { - c = this.nextChar_(); - } while (this.isAlpha_(c)); - return this.wkt.substring(index, this.index_--).toUpperCase(); -}; - +ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'when': ol.format.KML.whenParser_ + }, ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'coord': ol.format.KML.gxCoordParser_ + })); /** - * Class to parse the tokens from the WKT string. - * @param {ol.format.WKT.Lexer} lexer - * @constructor - * @protected + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.WKT.Parser = function(lexer) { - - /** - * @type {ol.format.WKT.Lexer} - * @private - */ - this.lexer_ = lexer; +ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_) + }); - /** - * @type {ol.format.WKT.Token} - * @private - */ - this.token_; - /** - * @type {number} - * @private - */ - this.dimension_ = 2; -}; +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.KML.ICON_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }, ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + })); /** - * Fetch the next token form the lexer and replace the active token. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.consume_ = function() { - this.token_ = this.lexer_.nextToken(); -}; +ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_), + 'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_), + 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) + }); /** - * If the given type matches the current token, consume it. - * @param {ol.format.WKT.TokenType.<number>} type Token type. - * @return {boolean} Whether the token matches the given type. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.WKT.Parser.prototype.match = function(type) { - var isMatch = this.token_.type == type; - if (isMatch) { - this.consume_(); - } - return isMatch; -}; +ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) + }); /** - * Try to parse the tokens provided by the lexer. - * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.format.WKT.Parser.prototype.parse = function() { - this.consume_(); - var geometry = this.parseGeometry_(); - goog.asserts.assert(this.token_.type == ol.format.WKT.TokenType.EOF); - return geometry; -}; +ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_) + }); /** - * @return {!(ol.geom.Geometry|ol.geom.GeometryCollection)} The geometry. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseGeometry_ = function() { - var token = this.token_; - if (this.match(ol.format.WKT.TokenType.TEXT)) { - var geomType = token.value; - if (geomType == ol.geom.GeometryType.GEOMETRY_COLLECTION.toUpperCase()) { - var geometries = this.parseGeometryCollectionText_(); - return new ol.geom.GeometryCollection(geometries); - } else { - var parser = ol.format.WKT.Parser.GeometryParser_[geomType]; - var ctor = ol.format.WKT.Parser.GeometryConstructor_[geomType]; - if (!goog.isDef(parser) || !goog.isDef(ctor)) { - throw new Error('Invalid geometry type: ' + geomType); - } - var coordinates = parser.call(this); - return new ctor(coordinates); - } - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); /** - * @return {!Array.<ol.geom.Geometry>} A collection of geometries. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var geometries = []; - do { - geometries.push(this.parseGeometry_()); - } while (this.match(ol.format.WKT.TokenType.COMMA)); - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return geometries; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_), + 'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_), + 'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_), + 'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_), + 'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_) + }); /** - * @return {Array.<number>} All values in a point. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parsePointText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var coordinates = this.parsePoint_(); - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return null; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_) + }); /** - * @return {!Array.<!Array.<number>>} All points in a linestring. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseLineStringText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var coordinates = this.parsePointList_(); - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Link': ol.format.KML.LinkParser_, + 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }); /** - * @return {!Array.<!Array.<number>>} All points in a polygon. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parsePolygonText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var coordinates = this.parseLineStringTextList_(); - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.LINK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) + }); /** - * @return {!Array.<!Array.<number>>} All points in a multipoint. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var coordinates; - if (this.token_.type == ol.format.WKT.TokenType.LEFT_PAREN) { - coordinates = this.parsePointTextList_(); - } else { - coordinates = this.parsePointList_(); - } - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_) + }); /** - * @return {!Array.<!Array.<number>>} All linestring points - * in a multilinestring. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var coordinates = this.parseLineStringTextList_(); - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.PAIR_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), + 'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyleUrl_) + }); /** - * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() { - if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { - var coordinates = this.parsePolygonTextList_(); - if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { - return coordinates; - } - } else if (this.isEmptyGeometry_()) { - return []; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'MultiGeometry': ol.xml.makeObjectPropertySetter( + ol.format.KML.readMultiGeometry_, 'geometry'), + 'LineString': ol.xml.makeObjectPropertySetter( + ol.format.KML.readLineString_, 'geometry'), + 'LinearRing': ol.xml.makeObjectPropertySetter( + ol.format.KML.readLinearRing_, 'geometry'), + 'Point': ol.xml.makeObjectPropertySetter( + ol.format.KML.readPoint_, 'geometry'), + 'Polygon': ol.xml.makeObjectPropertySetter( + ol.format.KML.readPolygon_, 'geometry'), + 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), + 'StyleMap': ol.format.KML.PlacemarkStyleMapParser_, + 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_), + 'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }, ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'MultiTrack': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxMultiTrack_, 'geometry'), + 'Track': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxTrack_, 'geometry') + } + )); /** - * @return {!Array.<number>} A point. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parsePoint_ = function() { - var coordinates = []; - for (var i = 0; i < this.dimension_; ++i) { - var token = this.token_; - if (this.match(ol.format.WKT.TokenType.NUMBER)) { - coordinates.push(token.value); - } else { - break; - } - } - if (coordinates.length == this.dimension_) { - return coordinates; - } - throw new Error(this.formatErrorMessage_()); -}; +ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_), + 'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean), + 'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean) + }); /** - * @return {!Array.<!Array.<number>>} An array of points. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parsePointList_ = function() { - var coordinates = [this.parsePoint_()]; - while (this.match(ol.format.WKT.TokenType.COMMA)) { - coordinates.push(this.parsePoint_()); - } - return coordinates; -}; +ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'SimpleData': ol.format.KML.SimpleDataParser_ + }); /** - * @return {!Array.<!Array.<number>>} An array of points. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parsePointTextList_ = function() { - var coordinates = [this.parsePointText_()]; - while (this.match(ol.format.WKT.TokenType.COMMA)) { - coordinates.push(this.parsePointText_()); - } - return coordinates; -}; +ol.format.KML.STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'IconStyle': ol.format.KML.IconStyleParser_, + 'LabelStyle': ol.format.KML.LabelStyleParser_, + 'LineStyle': ol.format.KML.LineStyleParser_, + 'PolyStyle': ol.format.KML.PolyStyleParser_ + }); /** - * @return {!Array.<!Array.<number>>} An array of points. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() { - var coordinates = [this.parseLineStringText_()]; - while (this.match(ol.format.WKT.TokenType.COMMA)) { - coordinates.push(this.parseLineStringText_()); - } - return coordinates; -}; +ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Pair': ol.format.KML.PairDataParser_ + }); /** - * @return {!Array.<!Array.<number>>} An array of points. - * @private + * @inheritDoc */ -ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() { - var coordinates = [this.parsePolygonText_()]; - while (this.match(ol.format.WKT.TokenType.COMMA)) { - coordinates.push(this.parsePolygonText_()); - } - return coordinates; +ol.format.KML.prototype.getExtensions = function() { + return ol.format.KML.EXTENSIONS_; }; /** - * @return {boolean} Whether the token implies an empty geometry. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Array.<ol.Feature>|undefined} Features. */ -ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() { - var isEmpty = this.token_.type == ol.format.WKT.TokenType.TEXT && - this.token_.value == ol.format.WKT.EMPTY; - if (isEmpty) { - this.consume_(); +ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + var localName = ol.xml.getLocalName(node); + goog.asserts.assert(localName == 'Document' || localName == 'Folder', + 'localName should be Document or Folder'); + // FIXME use scope somehow + var parsersNS = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this), + 'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this), + 'Style': goog.bind(this.readSharedStyle_, this), + 'StyleMap': goog.bind(this.readSharedStyleMap_, this) + }); + var features = ol.xml.pushParseAndPop(/** @type {Array.<ol.Feature>} */ ([]), + parsersNS, node, objectStack, this); + if (goog.isDef(features)) { + return features; + } else { + return undefined; } - return isEmpty; }; /** - * Create an error message for an unexpected token error. - * @return {string} Error message. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {ol.Feature|undefined} Feature. */ -ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() { - return 'Unexpected `' + this.token_.value + '` at position ' + - this.token_.position + ' in `' + this.lexer_.wkt + '`'; +ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Placemark', + 'localName should be Placemark'); + var object = ol.xml.pushParseAndPop({'geometry': null}, + ol.format.KML.PLACEMARK_PARSERS_, node, objectStack); + if (!goog.isDef(object)) { + return undefined; + } + var feature = new ol.Feature(); + var id = node.getAttribute('id'); + if (!goog.isNull(id)) { + feature.setId(id); + } + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + if (goog.isDefAndNotNull(object.geometry)) { + ol.format.Feature.transformWithOptions(object.geometry, false, options); + } + feature.setProperties(object); + if (this.extractStyles_) { + feature.setStyle(this.featureStyleFunction_); + } + return feature; }; /** - * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout.<string>=)} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.Parser.GeometryConstructor_ = { - 'POINT': ol.geom.Point, - 'LINESTRING': ol.geom.LineString, - 'POLYGON': ol.geom.Polygon, - 'MULTIPOINT': ol.geom.MultiPoint, - 'MULTILINESTRING': ol.geom.MultiLineString, - 'MULTIPOLYGON': ol.geom.MultiPolygon +ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Style', 'localName should be Style'); + var id = node.getAttribute('id'); + if (!goog.isNull(id)) { + var style = ol.format.KML.readStyle_(node, objectStack); + if (goog.isDef(style)) { + var styleUri; + if (goog.isDefAndNotNull(node.baseURI)) { + styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString(); + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = style; + } + } }; /** - * @enum {(function(): Array)} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WKT.Parser.GeometryParser_ = { - 'POINT': ol.format.WKT.Parser.prototype.parsePointText_, - 'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_, - 'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_, - 'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_, - 'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_, - 'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_ -}; - -goog.provide('ol.format.WMSCapabilities'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('ol.format.XLink'); -goog.require('ol.format.XML'); -goog.require('ol.format.XSD'); -goog.require('ol.xml'); - +ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'StyleMap', + 'localName should be StyleMap'); + var id = node.getAttribute('id'); + if (goog.isNull(id)) { + return; + } + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!goog.isDef(styleMapValue)) { + return; + } + var styleUri; + if (goog.isDefAndNotNull(node.baseURI)) { + styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString(); + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = styleMapValue; +}; /** - * @classdesc - * Format for reading WMS capabilities data + * Read the first feature from a KML source. * - * @constructor - * @extends {ol.format.XML} - * @api + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable */ -ol.format.WMSCapabilities = function() { +ol.format.KML.prototype.readFeature; - goog.base(this); - /** - * @type {string|undefined} - */ - this.version = undefined; +/** + * @inheritDoc + */ +ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { + return null; + } + goog.asserts.assert(node.localName == 'Placemark', + 'localName should be Placemark'); + var feature = this.readPlacemark_( + node, [this.getReadOptions(node, opt_options)]); + if (goog.isDef(feature)) { + return feature; + } else { + return null; + } }; -goog.inherits(ol.format.WMSCapabilities, ol.format.XML); /** - * Read a WMS capabilities document. + * Read all features from a KML source. * * @function - * @param {Document|Node|string} source The XML source. - * @return {Object} An object representing the WMS capabilities. - * @api + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable */ -ol.format.WMSCapabilities.prototype.read; +ol.format.KML.prototype.readFeatures; /** - * @param {Document} doc Document. - * @return {Object} WMS Capability object. + * @inheritDoc */ -ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) { - goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT); - for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { - if (n.nodeType == goog.dom.NodeType.ELEMENT) { - return this.readFromNode(n); +ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + if (!goog.array.contains(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) { + return []; + } + var features; + var localName = ol.xml.getLocalName(node); + if (localName == 'Document' || localName == 'Folder') { + features = this.readDocumentOrFolder_( + node, [this.getReadOptions(node, opt_options)]); + if (goog.isDef(features)) { + return features; + } else { + return []; + } + } else if (localName == 'Placemark') { + var feature = this.readPlacemark_( + node, [this.getReadOptions(node, opt_options)]); + if (goog.isDef(feature)) { + return [feature]; + } else { + return []; + } + } else if (localName == 'kml') { + features = []; + var n; + for (n = node.firstElementChild; !goog.isNull(n); + n = n.nextElementSibling) { + var fs = this.readFeaturesFromNode(n, opt_options); + if (goog.isDef(fs)) { + goog.array.extend(features, fs); + } } + return features; + } else { + return []; } - return null; }; /** - * @param {Node} node Node. - * @return {Object} WMS Capability object. + * Read the name of the KML. + * + * @param {Document|Node|string} source Souce. + * @return {string|undefined} Name. + * @api stable */ -ol.format.WMSCapabilities.prototype.readFromNode = function(node) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'WMS_Capabilities' || - node.localName == 'WMT_MS_Capabilities'); - this.version = goog.string.trim(node.getAttribute('version')); - goog.asserts.assertString(this.version); - var wmsCapabilityObject = ol.xml.pushParseAndPop({ - 'version': this.version - }, ol.format.WMSCapabilities.PARSERS_, node, []); - return goog.isDef(wmsCapabilityObject) ? wmsCapabilityObject : null; +ol.format.KML.prototype.readName = function(source) { + if (ol.xml.isDocument(source)) { + return this.readNameFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readNameFromNode(/** @type {Node} */ (source)); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readNameFromDocument(doc); + } else { + goog.asserts.fail('Unknown type for source'); + return undefined; + } }; /** - * @private - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Attribution object. + * @param {Document} doc Document. + * @return {string|undefined} Name. */ -ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Attribution'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack); +ol.format.KML.prototype.readNameFromDocument = function(doc) { + var n; + for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + var name = this.readNameFromNode(n); + if (goog.isDef(name)) { + return name; + } + } + } + return undefined; }; /** - * @private * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Object} Bounding box object. + * @return {string|undefined} Name. */ -ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'BoundingBox'); - - var extent = [ - ol.format.XSD.readDecimalString(node.getAttribute('minx')), - ol.format.XSD.readDecimalString(node.getAttribute('miny')), - ol.format.XSD.readDecimalString(node.getAttribute('maxx')), - ol.format.XSD.readDecimalString(node.getAttribute('maxy')) - ]; - - var resolutions = [ - ol.format.XSD.readDecimalString(node.getAttribute('resx')), - ol.format.XSD.readDecimalString(node.getAttribute('resy')) - ]; - - return { - 'crs': node.getAttribute('CRS'), - 'extent': extent, - 'res': resolutions - }; +ol.format.KML.prototype.readNameFromNode = function(node) { + var n; + for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { + if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'name') { + return ol.format.XSD.readString(n); + } + } + for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { + var localName = ol.xml.getLocalName(n); + if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'Placemark' || + localName == 'kml')) { + var name = this.readNameFromNode(n); + if (goog.isDef(name)) { + return name; + } + } + } + return undefined; }; /** - * @private - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {ol.Extent|undefined} Bounding box object. + * Read the network links of the KML. + * + * @param {Document|Node|string} source Source. + * @return {Array.<Object>} Network links. + * @api */ -ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'EX_GeographicBoundingBox'); - var geographicBoundingBox = ol.xml.pushParseAndPop( - {}, - ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_, - node, objectStack); - if (!goog.isDef(geographicBoundingBox)) { - return undefined; - } - var westBoundLongitude = /** @type {number|undefined} */ (goog.object.get( - geographicBoundingBox, 'westBoundLongitude')); - var southBoundLatitude = /** @type {number|undefined} */ (goog.object.get( - geographicBoundingBox, 'southBoundLatitude')); - var eastBoundLongitude = /** @type {number|undefined} */ (goog.object.get( - geographicBoundingBox, 'eastBoundLongitude')); - var northBoundLatitude = /** @type {number|undefined} */ (goog.object.get( - geographicBoundingBox, 'northBoundLatitude')); - if (!goog.isDef(westBoundLongitude) || !goog.isDef(southBoundLatitude) || - !goog.isDef(eastBoundLongitude) || !goog.isDef(northBoundLatitude)) { - return undefined; +ol.format.KML.prototype.readNetworkLinks = function(source) { + var networkLinks = []; + if (ol.xml.isDocument(source)) { + goog.array.extend(networkLinks, this.readNetworkLinksFromDocument( + /** @type {Document} */ (source))); + } else if (ol.xml.isNode(source)) { + goog.array.extend(networkLinks, this.readNetworkLinksFromNode( + /** @type {Node} */ (source))); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + goog.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc)); + } else { + goog.asserts.fail('unknown type for source'); } - return [ - westBoundLongitude, southBoundLatitude, - eastBoundLongitude, northBoundLatitude - ]; + return networkLinks; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} Capability object. + * @param {Document} doc Document. + * @return {Array.<Object>} Network links. */ -ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Capability'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack); +ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) { + var n, networkLinks = []; + for (n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + goog.array.extend(networkLinks, this.readNetworkLinksFromNode(n)); + } + } + return networkLinks; }; /** * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} Service object. + * @return {Array.<Object>} Network links. */ -ol.format.WMSCapabilities.readService_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Service'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack); +ol.format.KML.prototype.readNetworkLinksFromNode = function(node) { + var n, networkLinks = []; + for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { + if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'NetworkLink') { + var obj = ol.xml.pushParseAndPop({}, ol.format.KML.NETWORK_LINK_PARSERS_, + n, []); + networkLinks.push(obj); + } + } + for (n = node.firstElementChild; !goog.isNull(n); n = n.nextElementSibling) { + var localName = ol.xml.getLocalName(n); + if (goog.array.contains(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'kml')) { + goog.array.extend(networkLinks, this.readNetworkLinksFromNode(n)); + } + } + return networkLinks; }; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - * @return {Object|undefined} Contact information object. + * Read the projection from a KML source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable */ -ol.format.WMSCapabilities.readContactInformation_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ContactInformation'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_, - node, objectStack); -}; +ol.format.KML.prototype.readProjection; /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. + * @param {Node} node Node to append a TextNode with the color to. + * @param {ol.Color|string} color Color. * @private - * @return {Object|undefined} Contact person object. */ -ol.format.WMSCapabilities.readContactPersonPrimary_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ContactPersonPrimary'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_, - node, objectStack); +ol.format.KML.writeColorTextNode_ = function(node, color) { + var rgba = ol.color.asArray(color); + var opacity = (rgba.length == 4) ? rgba[3] : 1; + var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]]; + var i; + for (i = 0; i < 4; ++i) { + var hex = parseInt(abgr[i], 10).toString(16); + abgr[i] = (hex.length == 1) ? '0' + hex : hex; + } + ol.format.XSD.writeStringTextNode(node, abgr.join('')); }; /** - * @param {Node} node Node. + * @param {Node} node Node to append a TextNode with the coordinates to. + * @param {Array.<number>} coordinates Coordinates. * @param {Array.<*>} objectStack Object stack. * @private - * @return {Object|undefined} Contact address object. */ -ol.format.WMSCapabilities.readContactAddress_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'ContactAddress'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_, - node, objectStack); +ol.format.KML.writeCoordinatesTextNode_ = + function(node, coordinates, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + + var layout = context['layout']; + var stride = context['stride']; + + var dimension; + if (layout == ol.geom.GeometryLayout.XY || + layout == ol.geom.GeometryLayout.XYM) { + dimension = 2; + } else if (layout == ol.geom.GeometryLayout.XYZ || + layout == ol.geom.GeometryLayout.XYZM) { + dimension = 3; + } else { + goog.asserts.fail('Unknown geometry layout'); + } + + var d, i; + var ii = coordinates.length; + var text = ''; + if (ii > 0) { + text += coordinates[0]; + for (d = 1; d < dimension; ++d) { + text += ',' + coordinates[d]; + } + for (i = stride; i < ii; i += stride) { + text += ' ' + coordinates[i]; + for (d = 1; d < dimension; ++d) { + text += ',' + coordinates[i + d]; + } + } + } + ol.format.XSD.writeStringTextNode(node, text); }; /** * @param {Node} node Node. + * @param {Array.<ol.Feature>} features Features. * @param {Array.<*>} objectStack Object stack. * @private - * @return {Array.<string>|undefined} Format array. */ -ol.format.WMSCapabilities.readException_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Exception'); - return ol.xml.pushParseAndPop( - [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack); +ol.format.KML.writeDocument_ = function(node, features, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_, + ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack); }; /** * @param {Node} node Node. + * @param {Object} icon Icon object. * @param {Array.<*>} objectStack Object stack. * @private - * @return {Object|undefined} Layer object. */ -ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Layer'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack); +ol.format.KML.writeIcon_ = function(node, icon, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(icon, orderedKeys); + ol.xml.pushSerializeAndPop(context, + ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY, + values, objectStack, orderedKeys); + orderedKeys = + ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]]; + values = ol.xml.makeSequence(icon, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_, + ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys); }; /** - * @private * @param {Node} node Node. + * @param {ol.style.Icon} style Icon style. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Layer object. + * @private */ -ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Layer'); - var parentLayerObject = /** @type {Object.<string,*>} */ - (objectStack[objectStack.length - 1]); - - var layerObject = /** @type {Object.<string,*>} */ (ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack)); +ol.format.KML.writeIconStyle_ = function(node, style, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + var properties = {}; + var src = style.getSrc(); + var size = style.getSize(); + var iconImageSize = style.getImageSize(); + var iconProperties = { + 'href': src + }; - if (!goog.isDef(layerObject)) { - return undefined; - } - var queryable = - ol.format.XSD.readBooleanString(node.getAttribute('queryable')); - if (!goog.isDef(queryable)) { - queryable = goog.object.get(parentLayerObject, 'queryable'); - } - goog.object.set( - layerObject, 'queryable', goog.isDef(queryable) ? queryable : false); + if (!goog.isNull(size)) { + iconProperties['w'] = size[0]; + iconProperties['h'] = size[1]; + var anchor = style.getAnchor(); // top-left + var origin = style.getOrigin(); // top-left - var cascaded = ol.format.XSD.readNonNegativeIntegerString( - node.getAttribute('cascaded')); - if (!goog.isDef(cascaded)) { - cascaded = goog.object.get(parentLayerObject, 'cascaded'); - } - goog.object.set(layerObject, 'cascaded', cascaded); + if (!goog.isNull(origin) && !goog.isNull(iconImageSize) && + origin[0] !== 0 && origin[1] !== size[1]) { + iconProperties['x'] = origin[0]; + iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]); + } - var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque')); - if (!goog.isDef(opaque)) { - opaque = goog.object.get(parentLayerObject, 'opaque'); + if (!goog.isNull(anchor) && + anchor[0] !== 0 && anchor[1] !== size[1]) { + var /** @type {ol.format.KMLVec2_} */ hotSpot = { + x: anchor[0], + xunits: ol.style.IconAnchorUnits.PIXELS, + y: size[1] - anchor[1], + yunits: ol.style.IconAnchorUnits.PIXELS + }; + properties['hotSpot'] = hotSpot; + } } - goog.object.set(layerObject, 'opaque', goog.isDef(opaque) ? opaque : false); - var noSubsets = - ol.format.XSD.readBooleanString(node.getAttribute('noSubsets')); - if (!goog.isDef(noSubsets)) { - noSubsets = goog.object.get(parentLayerObject, 'noSubsets'); - } - goog.object.set( - layerObject, 'noSubsets', goog.isDef(noSubsets) ? noSubsets : false); + properties['Icon'] = iconProperties; - var fixedWidth = - ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth')); - if (!goog.isDef(fixedWidth)) { - fixedWidth = goog.object.get(parentLayerObject, 'fixedWidth'); + var scale = style.getScale(); + if (scale !== 1) { + properties['scale'] = scale; } - goog.object.set(layerObject, 'fixedWidth', fixedWidth); - var fixedHeight = - ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight')); - if (!goog.isDef(fixedHeight)) { - fixedHeight = goog.object.get(parentLayerObject, 'fixedHeight'); + var rotation = style.getRotation(); + if (rotation !== 0) { + properties['heading'] = rotation; // 0-360 } - goog.object.set(layerObject, 'fixedHeight', fixedHeight); - - // See 7.2.4.8 - var addKeys = ['Style', 'CRS', 'AuthorityURL']; - goog.array.forEach(addKeys, function(key) { - var parentValue = goog.object.get(parentLayerObject, key); - if (goog.isDef(parentValue)) { - var childValue = goog.object.setIfUndefined(layerObject, key, []); - childValue = childValue.concat(parentValue); - goog.object.set(layerObject, key, childValue); - } - }); - var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension', - 'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator']; - goog.array.forEach(replaceKeys, function(key) { - var childValue = goog.object.get(layerObject, key); - if (!goog.isDef(childValue)) { - var parentValue = goog.object.get(parentLayerObject, key); - goog.object.set(layerObject, key, parentValue); - } - }); - - return layerObject; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); }; /** - * @private * @param {Node} node Node. + * @param {ol.style.Text} style style. * @param {Array.<*>} objectStack Object stack. - * @return {Object} Dimension object. - */ -ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Dimension'); - var dimensionObject = { - 'name': node.getAttribute('name'), - 'units': node.getAttribute('units'), - 'unitSymbol': node.getAttribute('unitSymbol'), - 'default': node.getAttribute('default'), - 'multipleValues': ol.format.XSD.readBooleanString( - node.getAttribute('multipleValues')), - 'nearestValue': ol.format.XSD.readBooleanString( - node.getAttribute('nearestValue')), - 'current': ol.format.XSD.readBooleanString(node.getAttribute('current')), - 'values': ol.format.XSD.readString(node) - }; - return dimensionObject; -}; - - -/** * @private - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Online resource object. */ -ol.format.WMSCapabilities.readFormatOnlineresource_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_, - node, objectStack); +ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + var properties = {}; + var fill = style.getFill(); + if (!goog.isNull(fill)) { + properties['color'] = fill.getColor(); + } + var scale = style.getScale(); + if (goog.isDef(scale) && scale !== 1) { + properties['scale'] = scale; + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = + ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); }; /** - * @private * @param {Node} node Node. + * @param {ol.style.Stroke} style style. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Request object. + * @private */ -ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Request'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack); +ol.format.KML.writeLineStyle_ = function(node, style, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + var properties = { + 'color': style.getColor(), + 'width': style.getWidth() + }; + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); }; /** - * @private * @param {Node} node Node. + * @param {ol.geom.Geometry} geometry Geometry. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} DCP type object. + * @private */ -ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'DCPType'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack); +ol.format.KML.writeMultiGeometry_ = + function(node, geometry, objectStack) { + goog.asserts.assert( + (geometry instanceof ol.geom.MultiPoint) || + (geometry instanceof ol.geom.MultiLineString) || + (geometry instanceof ol.geom.MultiPolygon), + 'geometry should be one of: ol.geom.MultiPoint, ' + + 'ol.geom.MultiLineString or ol.geom.MultiPolygon'); + /** @type {ol.xml.NodeStackItem} */ + var context = {node: node}; + var type = geometry.getType(); + /** @type {Array.<ol.geom.Geometry>} */ + var geometries; + /** @type {function(*, Array.<*>, string=): (Node|undefined)} */ + var factory; + if (type == ol.geom.GeometryType.MULTI_POINT) { + geometries = + (/** @type {ol.geom.MultiPoint} */ (geometry)).getPoints(); + factory = ol.format.KML.POINT_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) { + geometries = + (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings(); + factory = ol.format.KML.LINE_STRING_NODE_FACTORY_; + } else if (type == ol.geom.GeometryType.MULTI_POLYGON) { + geometries = + (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons(); + factory = ol.format.KML.POLYGON_NODE_FACTORY_; + } else { + goog.asserts.fail('Unknown geometry type: ' + type); + } + ol.xml.pushSerializeAndPop(context, + ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory, + geometries, objectStack); }; /** - * @private * @param {Node} node Node. + * @param {ol.geom.LinearRing} linearRing Linear ring. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} HTTP object. - */ -ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'HTTP'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack); -}; - - -/** * @private - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Operation type object. */ -ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack); +ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, + ol.format.KML.BOUNDARY_IS_SERIALIZERS_, + ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack); }; /** - * @private + * FIXME currently we do serialize arbitrary/custom feature properties + * (ExtendedData). * @param {Node} node Node. + * @param {ol.Feature} feature Feature. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Online resource object. + * @private */ -ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = - function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - var formatOnlineresource = - ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); - if (goog.isDef(formatOnlineresource)) { - var size = [ - ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')), - ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height')) - ]; - goog.object.set(formatOnlineresource, 'size', size); - return formatOnlineresource; +ol.format.KML.writePlacemark_ = function(node, feature, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + + // set id + if (goog.isDefAndNotNull(feature.getId())) { + node.setAttribute('id', feature.getId()); } - return undefined; -}; + // serialize properties (properties unknown to KML are not serialized) + var properties = feature.getProperties(); + var styleFunction = feature.getStyleFunction(); + if (goog.isDef(styleFunction)) { + // FIXME the styles returned by the style function are supposed to be + // resolution-independent here + var styles = styleFunction.call(feature, 0); + if (!goog.isNull(styles) && styles.length > 0) { + properties['Style'] = styles[0]; + var textStyle = styles[0].getText(); + if (!goog.isNull(textStyle)) { + properties['name'] = textStyle.getText(); + } + } + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.PLACEMARK_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); -/** - * @private - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Authority URL object. - */ -ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'AuthorityURL'); - var authorityObject = - ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); - if (goog.isDef(authorityObject)) { - goog.object.set(authorityObject, 'name', node.getAttribute('name')); - return authorityObject; + // serialize geometry + var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]); + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + geometry = + ol.format.Feature.transformWithOptions(geometry, true, options); } - return undefined; + ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_, + ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack); }; /** - * @private * @param {Node} node Node. + * @param {ol.geom.SimpleGeometry} geometry Geometry. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Metadata URL object. + * @private */ -ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'MetadataURL'); - var metadataObject = - ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); - if (goog.isDef(metadataObject)) { - goog.object.set(metadataObject, 'type', node.getAttribute('type')); - return metadataObject; - } - return undefined; +ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) { + goog.asserts.assert( + (geometry instanceof ol.geom.Point) || + (geometry instanceof ol.geom.LineString) || + (geometry instanceof ol.geom.LinearRing), + 'geometry should be one of ol.geom.Point, ol.geom.LineString ' + + 'or ol.geom.LinearRing'); + var flatCoordinates = geometry.getFlatCoordinates(); + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + context['layout'] = geometry.getLayout(); + context['stride'] = geometry.getStride(); + ol.xml.pushSerializeAndPop(context, + ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_, + ol.format.KML.COORDINATES_NODE_FACTORY_, + [flatCoordinates], objectStack); }; /** - * @private * @param {Node} node Node. + * @param {ol.geom.Polygon} polygon Polygon. * @param {Array.<*>} objectStack Object stack. - * @return {Object|undefined} Style object. + * @private */ -ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'Style'); - return ol.xml.pushParseAndPop( - {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack); +ol.format.KML.writePolygon_ = function(node, polygon, objectStack) { + goog.asserts.assertInstanceof(polygon, ol.geom.Polygon, + 'polygon should be an ol.geom.Polygon'); + var linearRings = polygon.getLinearRings(); + goog.asserts.assert(linearRings.length > 0, + 'linearRings should not be empty'); + var outerRing = linearRings.shift(); + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + // inner rings + ol.xml.pushSerializeAndPop(context, + ol.format.KML.POLYGON_SERIALIZERS_, + ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_, + linearRings, objectStack); + // outer ring + ol.xml.pushSerializeAndPop(context, + ol.format.KML.POLYGON_SERIALIZERS_, + ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_, + [outerRing], objectStack); }; /** - * @private * @param {Node} node Node. + * @param {ol.style.Fill} style Style. * @param {Array.<*>} objectStack Object stack. - * @return {Array.<string>|undefined} Keyword list. + * @private */ -ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'KeywordList'); - return ol.xml.pushParseAndPop( - [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack); +ol.format.KML.writePolyStyle_ = function(node, style, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_, + ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack); }; /** - * @const + * @param {Node} node Node to append a TextNode with the scale to. + * @param {number|undefined} scale Scale. * @private - * @type {Array.<string>} */ -ol.format.WMSCapabilities.NAMESPACE_URIS_ = [ - null, - 'http://www.opengis.net/wms' -]; +ol.format.KML.writeScaleTextNode_ = function(node, scale) { + ol.format.XSD.writeDecimalTextNode(node, scale * scale); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node. + * @param {ol.style.Style} style Style. + * @param {Array.<*>} objectStack Object stack. * @private */ -ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Service': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readService_), - 'Capability': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readCapability_) - }); +ol.format.KML.writeStyle_ = function(node, style, objectStack) { + var /** @type {ol.xml.NodeStackItem} */ context = {node: node}; + var properties = {}; + var fillStyle = style.getFill(); + var strokeStyle = style.getStroke(); + var imageStyle = style.getImage(); + var textStyle = style.getText(); + if (!goog.isNull(imageStyle)) { + properties['IconStyle'] = imageStyle; + } + if (!goog.isNull(textStyle)) { + properties['LabelStyle'] = textStyle; + } + if (!goog.isNull(strokeStyle)) { + properties['LineStyle'] = strokeStyle; + } + if (!goog.isNull(fillStyle)) { + properties['PolyStyle'] = fillStyle; + } + var parentNode = objectStack[objectStack.length - 1].node; + var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys); +}; /** - * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @param {Node} node Node to append a TextNode with the Vec2 to. + * @param {ol.format.KMLVec2_} vec2 Vec2. * @private */ -ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Request': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readRequest_), - 'Exception': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readException_), - 'Layer': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readCapabilityLayer_) - }); +ol.format.KML.writeVec2_ = function(node, vec2) { + node.setAttribute('x', vec2.x); + node.setAttribute('y', vec2.y); + node.setAttribute('xunits', vec2.xunits); + node.setAttribute('yunits', vec2.yunits); +}; /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'KeywordList': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readKeywordList_), - 'OnlineResource': ol.xml.makeObjectPropertySetter( - ol.format.XLink.readHref), - 'ContactInformation': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readContactInformation_), - 'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'AccessConstraints': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString), - 'LayerLimit': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger), - 'MaxWidth': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger), - 'MaxHeight': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readNonNegativeInteger) - }); +ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'Document', 'Placemark' + ]); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'ContactPersonPrimary': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readContactPersonPrimary_), - 'ContactPosition': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString), - 'ContactAddress': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readContactAddress_), - 'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString), - 'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString), - 'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString) +ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_), + 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'ContactPerson': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString), - 'ContactOrganization': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString) +ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, string>} * @private */ -ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'StateOrProvince': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readString), - 'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) - }); +ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = { + 'Point': 'Point', + 'LineString': 'LineString', + 'LinearRing': 'LinearRing', + 'Polygon': 'Polygon', + 'MultiPoint': 'MultiGeometry', + 'MultiLineString': 'MultiGeometry', + 'MultiPolygon': 'MultiGeometry' +}; /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString) - }); +ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'href' + ], + ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, [ + 'x', 'y', 'w', 'h' + ])); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'KeywordList': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readKeywordList_), - 'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), - 'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readEXGeographicBoundingBox_), - 'BoundingBox': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readBoundingBox_), - 'Dimension': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readDimension_), - 'Attribution': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readAttribution_), - 'AuthorityURL': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readAuthorityURL_), - 'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), - 'MetadataURL': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readMetadataURL_), - 'DataURL': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readFormatOnlineresource_), - 'FeatureListURL': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readFormatOnlineresource_), - 'Style': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readStyle_), - 'MinScaleDenominator': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readDecimal), - 'MaxScaleDenominator': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readDecimal), - 'Layer': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readLayer_) - }); +ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + }, ol.xml.makeStructureNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) + })); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'OnlineResource': ol.xml.makeObjectPropertySetter( - ol.format.XLink.readHref), - 'LogoURL': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readSizedFormatOnlineresource_) - }); +ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'scale', 'heading', 'Icon', 'hotSpot' + ]); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ = - ol.xml.makeParsersNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'westBoundLongitude': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readDecimal), - 'eastBoundLongitude': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readDecimal), - 'southBoundLatitude': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readDecimal), - 'northBoundLatitude': ol.xml.makeObjectPropertySetter( - ol.format.XSD.readDecimal) +ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_), + 'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode), + 'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_), + 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'GetCapabilities': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readOperationType_), - 'GetMap': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readOperationType_), - 'GetFeatureInfo': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readOperationType_) - }); +ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'color', 'scale' + ]); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), - 'DCPType': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readDCPType_) +ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), + 'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'HTTP': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readHTTP_) - }); +ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'color', 'width' + ]); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Get': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readFormatOnlineresource_), - 'Post': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readFormatOnlineresource_) +ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_), + 'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'LegendURL': ol.xml.makeObjectPropertyPusher( - ol.format.WMSCapabilities.readSizedFormatOnlineresource_), - 'StyleSheetURL': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readFormatOnlineresource_), - 'StyleURL': ol.xml.makeObjectPropertySetter( - ol.format.WMSCapabilities.readFormatOnlineresource_) +ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LinearRing': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), - 'OnlineResource': ol.xml.makeObjectPropertySetter( - ol.format.XLink.readHref) +ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LineString': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Point': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_) }); /** * @const - * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @type {Object.<string, Array.<string>>} * @private */ -ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeParsersNS( - ol.format.WMSCapabilities.NAMESPACE_URIS_, { - 'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString) - }); - -goog.provide('ol.format.WMSGetFeatureInfo'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.dom.NodeType'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('ol.format.GML'); -goog.require('ol.format.GML2'); -goog.require('ol.format.XMLFeature'); -goog.require('ol.xml'); - - - -/** - * @classdesc - * Format for reading WMSGetFeatureInfo format. It uses - * {@link ol.format.GML2} to read features. - * - * @constructor - * @extends {ol.format.XMLFeature} - * @api - */ -ol.format.WMSGetFeatureInfo = function() { - - /** - * @private - * @type {string} - */ - this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver'; - - - /** - * @private - * @type {ol.format.GML2} - */ - this.gmlFormat_ = new ol.format.GML2(); - - goog.base(this); -}; -goog.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature); +ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'name', 'open', 'visibility', 'address', 'phoneNumber', 'description', + 'styleUrl', 'Style' + ]); /** * @const - * @type {string} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature'; +ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'MultiGeometry': ol.xml.makeChildAppender( + ol.format.KML.writeMultiGeometry_), + 'LineString': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'LinearRing': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Point': ol.xml.makeChildAppender( + ol.format.KML.writePrimitiveGeometry_), + 'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_), + 'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_), + 'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'description': ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode), + 'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode), + 'phoneNumber': ol.xml.makeChildAppender( + ol.format.XSD.writeStringTextNode), + 'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode), + 'visibility': ol.xml.makeChildAppender( + ol.format.XSD.writeBooleanTextNode) + }); /** * @const - * @type {string} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer'; +ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'coordinates': ol.xml.makeChildAppender( + ol.format.KML.writeCoordinatesTextNode_) + }); /** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @return {Array.<ol.Feature>} Features. + * @const + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = - function(node, objectStack) { - - node.namespaceURI = this.featureNS_; - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - var localName = ol.xml.getLocalName(node); - /** @type {Array.<ol.Feature>} */ - var features = []; - if (node.childNodes.length === 0) { - return features; - } - if (localName == 'msGMLOutput') { - goog.array.forEach(node.childNodes, function(layer) { - if (layer.nodeType !== goog.dom.NodeType.ELEMENT) { - return; - } - var context = objectStack[0]; - goog.asserts.assert(goog.isObject(context)); - - goog.asserts.assert(layer.localName.indexOf( - ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0); - - var featureType = goog.string.remove(layer.localName, - ol.format.WMSGetFeatureInfo.layerIdentifier_) + - ol.format.WMSGetFeatureInfo.featureIdentifier_; - - goog.object.set(context, 'featureType', featureType); - goog.object.set(context, 'featureNS', this.featureNS_); - - var parsers = {}; - parsers[featureType] = ol.xml.makeArrayPusher( - this.gmlFormat_.readFeatureElement, this.gmlFormat_); - var parsersNS = ol.xml.makeParsersNS( - [goog.object.get(context, 'featureNS'), null], parsers); - layer.namespaceURI = this.featureNS_; - var layerFeatures = ol.xml.pushParseAndPop( - [], parsersNS, layer, objectStack, this.gmlFormat_); - if (goog.isDef(layerFeatures)) { - goog.array.extend(features, layerFeatures); - } - }, this); - } - if (localName == 'FeatureCollection') { - var gmlFeatures = ol.xml.pushParseAndPop([], - this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, - [{}], this.gmlFormat_); - if (goog.isDef(gmlFeatures)) { - features = gmlFeatures; - } - } - return features; -}; +ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'outerBoundaryIs': ol.xml.makeChildAppender( + ol.format.KML.writeBoundaryIs_), + 'innerBoundaryIs': ol.xml.makeChildAppender( + ol.format.KML.writeBoundaryIs_) + }); /** - * Read all features from a WMSGetFeatureInfo response. - * - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @param {olx.format.ReadOptions=} opt_options Options. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @const + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @private */ -ol.format.WMSGetFeatureInfo.prototype.readFeatures; +ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_) + }); /** - * @inheritDoc + * @const + * @type {Object.<string, Array.<string>>} + * @private */ -ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = - function(node, opt_options) { - var options = { - 'featureType': this.featureType, - 'featureNS': this.featureNS - }; - if (goog.isDef(opt_options)) { - goog.object.extend(options, this.getReadOptions(node, opt_options)); - } - return this.readFeatures_(node, [options]); -}; - -goog.provide('ol.sphere.WGS84'); - -goog.require('ol.Sphere'); +ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, [ + 'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle' + ]); /** - * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid. * @const - * @type {ol.Sphere} + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @private */ -ol.sphere.WGS84 = new ol.Sphere(6378137); - -// FIXME handle geolocation not supported - -goog.provide('ol.Geolocation'); -goog.provide('ol.GeolocationProperty'); - -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.math'); -goog.require('ol.Coordinate'); -goog.require('ol.Object'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.Polygon'); -goog.require('ol.has'); -goog.require('ol.proj'); -goog.require('ol.sphere.WGS84'); +ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_), + 'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_), + 'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_), + 'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_) + }); /** - * @enum {string} + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private */ -ol.GeolocationProperty = { - ACCURACY: 'accuracy', - ACCURACY_GEOMETRY: 'accuracyGeometry', - ALTITUDE: 'altitude', - ALTITUDE_ACCURACY: 'altitudeAccuracy', - HEADING: 'heading', - POSITION: 'position', - PROJECTION: 'projection', - SPEED: 'speed', - TRACKING: 'tracking', - TRACKING_OPTIONS: 'trackingOptions' +ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) { + return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0], + 'gx:' + opt_nodeName); }; - /** - * @classdesc - * Helper class for providing HTML5 Geolocation capabilities. - * The [Geolocation API](http://www.w3.org/TR/geolocation-API/) - * is used to locate a user's position. - * - * Example: - * - * var geolocation = new ol.Geolocation({ - * // take the projection to use from the map's view - * projection: view.getProjection() - * }); - * // listen to changes in position - * geolocation.on('change', function(evt) { - * window.console.log(geolocation.getPosition()); - * }); - * - * @constructor - * @extends {ol.Object} - * @fires change Triggered when the position changes. - * @param {olx.GeolocationOptions=} opt_options Options. - * @api stable + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private */ -ol.Geolocation = function(opt_options) { - - goog.base(this); - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * The unprojected (EPSG:4326) device position. - * @private - * @type {ol.Coordinate} - */ - this.position_ = null; - - /** - * @private - * @type {ol.TransformFunction} - */ - this.transform_ = ol.proj.identityTransform; - - /** - * @private - * @type {number|undefined} - */ - this.watchId_ = undefined; - - goog.events.listen( - this, ol.Object.getChangeEventType(ol.GeolocationProperty.PROJECTION), - this.handleProjectionChanged_, false, this); - goog.events.listen( - this, ol.Object.getChangeEventType(ol.GeolocationProperty.TRACKING), - this.handleTrackingChanged_, false, this); - - if (goog.isDef(options.projection)) { - this.setProjection(ol.proj.get(options.projection)); - } - if (goog.isDef(options.trackingOptions)) { - this.setTrackingOptions(options.trackingOptions); - } - - this.setTracking(goog.isDef(options.tracking) ? options.tracking : false); - +ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack, + opt_nodeName) { + goog.asserts.assertInstanceof(value, ol.Feature, + 'value should be an ol.Feature'); + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark'); }; -goog.inherits(ol.Geolocation, ol.Object); /** - * @inheritDoc + * @const + * @param {*} value Value. + * @param {Array.<*>} objectStack Object stack. + * @param {string=} opt_nodeName Node name. + * @return {Node|undefined} Node. + * @private */ -ol.Geolocation.prototype.disposeInternal = function() { - this.setTracking(false); - goog.base(this, 'disposeInternal'); +ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, + opt_nodeName) { + if (goog.isDefAndNotNull(value)) { + goog.asserts.assertInstanceof(value, ol.geom.Geometry, + 'value should be an ol.geom.Geometry'); + var parentNode = objectStack[objectStack.length - 1].node; + goog.asserts.assert(ol.xml.isNode(parentNode), + 'parentNode should be an XML node'); + return ol.xml.createElementNS(parentNode.namespaceURI, + ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[value.getType()]); + } }; /** + * A factory for creating coordinates nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} * @private */ -ol.Geolocation.prototype.handleProjectionChanged_ = function() { - var projection = this.getProjection(); - if (goog.isDefAndNotNull(projection)) { - this.transform_ = ol.proj.getTransformFromProjections( - ol.proj.get('EPSG:4326'), projection); - if (!goog.isNull(this.position_)) { - this.set( - ol.GeolocationProperty.POSITION, this.transform_(this.position_)); - } - } -}; +ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color'); /** + * A factory for creating coordinates nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} * @private */ -ol.Geolocation.prototype.handleTrackingChanged_ = function() { - if (ol.has.GEOLOCATION) { - var tracking = this.getTracking(); - if (tracking && !goog.isDef(this.watchId_)) { - this.watchId_ = goog.global.navigator.geolocation.watchPosition( - goog.bind(this.positionChange_, this), - goog.bind(this.positionError_, this), - this.getTrackingOptions()); - } else if (!tracking && goog.isDef(this.watchId_)) { - goog.global.navigator.geolocation.clearWatch(this.watchId_); - this.watchId_ = undefined; - } - } -}; +ol.format.KML.COORDINATES_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('coordinates'); /** + * A factory for creating innerBoundaryIs nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} * @private - * @param {GeolocationPosition} position position event. */ -ol.Geolocation.prototype.positionChange_ = function(position) { - var coords = position.coords; - this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy); - this.set(ol.GeolocationProperty.ALTITUDE, - goog.isNull(coords.altitude) ? undefined : coords.altitude); - this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY, - goog.isNull(coords.altitudeAccuracy) ? - undefined : coords.altitudeAccuracy); - this.set(ol.GeolocationProperty.HEADING, goog.isNull(coords.heading) ? - undefined : goog.math.toRadians(coords.heading)); - if (goog.isNull(this.position_)) { - this.position_ = [coords.longitude, coords.latitude]; - } else { - this.position_[0] = coords.longitude; - this.position_[1] = coords.latitude; - } - var projectedPosition = this.transform_(this.position_); - this.set(ol.GeolocationProperty.POSITION, projectedPosition); - this.set(ol.GeolocationProperty.SPEED, - goog.isNull(coords.speed) ? undefined : coords.speed); - var geometry = ol.geom.Polygon.circular( - ol.sphere.WGS84, this.position_, coords.accuracy); - geometry.applyTransform(this.transform_); - this.set(ol.GeolocationProperty.ACCURACY_GEOMETRY, geometry); - this.changed(); -}; +ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('innerBoundaryIs'); /** + * A factory for creating Point nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} * @private - * @param {GeolocationPositionError} error error object. */ -ol.Geolocation.prototype.positionError_ = function(error) { - error.type = goog.events.EventType.ERROR; - this.setTracking(false); - this.dispatchEvent(error); -}; +ol.format.KML.POINT_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Point'); /** - * Get the accuracy of the position in meters. - * @return {number|undefined} The accuracy of the position measurement in - * meters. - * @observable - * @api stable + * A factory for creating LineString nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private */ -ol.Geolocation.prototype.getAccuracy = function() { - return /** @type {number|undefined} */ ( - this.get(ol.GeolocationProperty.ACCURACY)); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getAccuracy', - ol.Geolocation.prototype.getAccuracy); +ol.format.KML.LINE_STRING_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('LineString'); /** - * Get a geometry of the position accuracy. - * @return {?ol.geom.Geometry} A geometry of the position accuracy. - * @observable - * @api stable + * A factory for creating LinearRing nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private */ -ol.Geolocation.prototype.getAccuracyGeometry = function() { - return /** @type {?ol.geom.Geometry} */ ( - this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getAccuracyGeometry', - ol.Geolocation.prototype.getAccuracyGeometry); +ol.format.KML.LINEAR_RING_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('LinearRing'); /** - * Get the altitude associated with the position. - * @return {number|undefined} The altitude of the position in meters above mean - * sea level. - * @observable - * @api stable + * A factory for creating Polygon nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private */ -ol.Geolocation.prototype.getAltitude = function() { - return /** @type {number|undefined} */ ( - this.get(ol.GeolocationProperty.ALTITUDE)); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getAltitude', - ol.Geolocation.prototype.getAltitude); +ol.format.KML.POLYGON_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('Polygon'); /** - * Get the altitude accuracy of the position. - * @return {number|undefined} The accuracy of the altitude measurement in - * meters. - * @observable - * @api stable + * A factory for creating outerBoundaryIs nodes. + * @const + * @type {function(*, Array.<*>, string=): (Node|undefined)} + * @private */ -ol.Geolocation.prototype.getAltitudeAccuracy = function() { - return /** @type {number|undefined} */ ( - this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY)); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getAltitudeAccuracy', - ol.Geolocation.prototype.getAltitudeAccuracy); +ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ = + ol.xml.makeSimpleNodeFactory('outerBoundaryIs'); /** - * Get the heading as radians clockwise from North. - * @return {number|undefined} The heading of the device in radians from north. - * @observable + * Encode an array of features in the KML format. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {string} Result. * @api stable */ -ol.Geolocation.prototype.getHeading = function() { - return /** @type {number|undefined} */ ( - this.get(ol.GeolocationProperty.HEADING)); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getHeading', - ol.Geolocation.prototype.getHeading); +ol.format.KML.prototype.writeFeatures; /** - * Get the position of the device. - * @return {ol.Coordinate|undefined} The current position of the device reported - * in the current projection. - * @observable - * @api stable + * Encode an array of features in the KML format as an XML node. + * + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Options. + * @return {Node} Node. + * @api */ -ol.Geolocation.prototype.getPosition = function() { - return /** @type {ol.Coordinate|undefined} */ ( - this.get(ol.GeolocationProperty.POSITION)); +ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) { + opt_options = this.adaptOptions(opt_options); + var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml'); + var xmlnsUri = 'http://www.w3.org/2000/xmlns/'; + var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx', + ol.format.KML.GX_NAMESPACE_URIS_[0]); + ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri); + ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation', + ol.format.KML.SCHEMA_LOCATION_); + + var /** @type {ol.xml.NodeStackItem} */ context = {node: kml}; + var properties = {}; + if (features.length > 1) { + properties['Document'] = features; + } else if (features.length == 1) { + properties['Placemark'] = features[0]; + } + var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI]; + var values = ol.xml.makeSequence(properties, orderedKeys); + ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_, + ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys); + return kml; }; -goog.exportProperty( - ol.Geolocation.prototype, - 'getPosition', - ol.Geolocation.prototype.getPosition); + +// FIXME add typedef for stack state objects +goog.provide('ol.format.OSMXML'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj'); +goog.require('ol.xml'); + /** - * Get the projection associated with the position. - * @return {ol.proj.Projection|undefined} The projection the position is - * reported in. - * @observable + * @classdesc + * Feature format for reading data in the + * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML). + * + * @constructor + * @extends {ol.format.XMLFeature} * @api stable */ -ol.Geolocation.prototype.getProjection = function() { - return /** @type {ol.proj.Projection|undefined} */ ( - this.get(ol.GeolocationProperty.PROJECTION)); +ol.format.OSMXML = function() { + goog.base(this); + + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get('EPSG:4326'); }; -goog.exportProperty( - ol.Geolocation.prototype, - 'getProjection', - ol.Geolocation.prototype.getProjection); +goog.inherits(ol.format.OSMXML, ol.format.XMLFeature); /** - * Get the speed in meters per second. - * @return {number|undefined} The instantaneous speed of the device in meters - * per second. - * @observable - * @api stable + * @const + * @type {Array.<string>} + * @private */ -ol.Geolocation.prototype.getSpeed = function() { - return /** @type {number|undefined} */ ( - this.get(ol.GeolocationProperty.SPEED)); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getSpeed', - ol.Geolocation.prototype.getSpeed); +ol.format.OSMXML.EXTENSIONS_ = ['.osm']; /** - * Are we tracking the user's position? - * @return {boolean} Whether to track the device's position. - * @observable - * @api stable + * @inheritDoc */ -ol.Geolocation.prototype.getTracking = function() { - return /** @type {boolean} */ ( - this.get(ol.GeolocationProperty.TRACKING)); +ol.format.OSMXML.prototype.getExtensions = function() { + return ol.format.OSMXML.EXTENSIONS_; }; -goog.exportProperty( - ol.Geolocation.prototype, - 'getTracking', - ol.Geolocation.prototype.getTracking); /** - * Get the tracking options. - * @see http://www.w3.org/TR/geolocation-API/#position-options - * @return {GeolocationPositionOptions|undefined} PositionOptions as defined by - * the HTML5 Geolocation spec at - * {@link http://www.w3.org/TR/geolocation-API/#position_options_interface} - * @observable - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.Geolocation.prototype.getTrackingOptions = function() { - return /** @type {GeolocationPositionOptions|undefined} */ ( - this.get(ol.GeolocationProperty.TRACKING_OPTIONS)); +ol.format.OSMXML.readNode_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'node', 'localName should be node'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var id = node.getAttribute('id'); + var coordinates = /** @type {Array.<number>} */ ([ + parseFloat(node.getAttribute('lon')), + parseFloat(node.getAttribute('lat')) + ]); + state.nodes[id] = coordinates; + + var values = ol.xml.pushParseAndPop({ + tags: {} + }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack); + if (!goog.object.isEmpty(values.tags)) { + var geometry = new ol.geom.Point(coordinates); + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setId(id); + feature.setProperties(values.tags); + state.features.push(feature); + } }; -goog.exportProperty( - ol.Geolocation.prototype, - 'getTrackingOptions', - ol.Geolocation.prototype.getTrackingOptions); /** - * Set the projection to use for transforming the coordinates. - * @param {ol.proj.Projection} projection The projection the position is - * reported in. - * @observable - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private */ -ol.Geolocation.prototype.setProjection = function(projection) { - this.set(ol.GeolocationProperty.PROJECTION, projection); +ol.format.OSMXML.readWay_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'way', 'localName should be way'); + var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]); + var id = node.getAttribute('id'); + var values = ol.xml.pushParseAndPop({ + ndrefs: [], + tags: {} + }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack); + var state = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var flatCoordinates = /** @type {Array.<number>} */ ([]); + for (var i = 0, ii = values.ndrefs.length; i < ii; i++) { + var point = state.nodes[values.ndrefs[i]]; + goog.array.extend(flatCoordinates, point); + } + var geometry; + if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) { + // closed way + geometry = new ol.geom.Polygon(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, + [flatCoordinates.length]); + } else { + geometry = new ol.geom.LineString(null); + geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + } + ol.format.Feature.transformWithOptions(geometry, false, options); + var feature = new ol.Feature(geometry); + feature.setId(id); + feature.setProperties(values.tags); + state.features.push(feature); }; -goog.exportProperty( - ol.Geolocation.prototype, - 'setProjection', - ol.Geolocation.prototype.setProjection); /** - * Enable/disable tracking. - * @param {boolean} tracking Whether to track the device's position. - * @observable - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. */ -ol.Geolocation.prototype.setTracking = function(tracking) { - this.set(ol.GeolocationProperty.TRACKING, tracking); +ol.format.OSMXML.readNd_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'nd', 'localName should be nd'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values.ndrefs.push(node.getAttribute('ref')); }; -goog.exportProperty( - ol.Geolocation.prototype, - 'setTracking', - ol.Geolocation.prototype.setTracking); /** - * Set the tracking options. - * @see http://www.w3.org/TR/geolocation-API/#position-options - * @param {GeolocationPositionOptions} options PositionOptions as defined by the - * HTML5 Geolocation spec at - * {@link http://www.w3.org/TR/geolocation-API/#position_options_interface} - * @observable - * @api stable + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.Feature|undefined} Track. */ -ol.Geolocation.prototype.setTrackingOptions = function(options) { - this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options); +ol.format.OSMXML.readTag_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'tag', 'localName should be tag'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + values.tags[node.getAttribute('k')] = node.getAttribute('v'); }; -goog.exportProperty( - ol.Geolocation.prototype, - 'setTrackingOptions', - ol.Geolocation.prototype.setTrackingOptions); -goog.provide('ol.geom.flat.geodesic'); -goog.require('goog.asserts'); -goog.require('goog.math'); -goog.require('goog.object'); -goog.require('ol.TransformFunction'); -goog.require('ol.math'); -goog.require('ol.proj'); +/** + * @const + * @private + * @type {Array.<string>} + */ +ol.format.OSMXML.NAMESPACE_URIS_ = [ + null +]; /** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private - * @param {function(number): ol.Coordinate} interpolate Interpolate function. - * @param {ol.TransformFunction} transform Transform from longitude/latitude to - * projected coordinates. - * @param {number} squaredTolerance Squared tolerance. - * @return {Array.<number>} Flat coordinates. */ -ol.geom.flat.geodesic.line_ = - function(interpolate, transform, squaredTolerance) { - // FIXME reduce garbage generation - // FIXME optimize stack operations +ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'nd': ol.format.OSMXML.readNd_, + 'tag': ol.format.OSMXML.readTag_ + }); - /** @type {Array.<number>} */ - var flatCoordinates = []; - var geoA = interpolate(0); - var geoB = interpolate(1); +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OSMXML.PARSERS_ = ol.xml.makeParsersNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'node': ol.format.OSMXML.readNode_, + 'way': ol.format.OSMXML.readWay_ + }); - var a = transform(geoA); - var b = transform(geoB); - /** @type {Array.<ol.Coordinate>} */ - var geoStack = [geoB, geoA]; - /** @type {Array.<ol.Coordinate>} */ - var stack = [b, a]; - /** @type {Array.<number>} */ - var fractionStack = [1, 0]; +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OSMXML.NAMESPACE_URIS_, { + 'tag': ol.format.OSMXML.readTag_ + }); - /** @type {Object.<number, boolean>} */ - var fractions = {}; - var maxIterations = 1e5; - var geoM, m, fracA, fracB, fracM, key; +/** + * Read all features from an OSM source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.OSMXML.prototype.readFeatures; - while (--maxIterations > 0 && fractionStack.length > 0) { - // Pop the a coordinate off the stack - fracA = fractionStack.pop(); - geoA = geoStack.pop(); - a = stack.pop(); - // Add the a coordinate if it has not been added yet - key = fracA.toString(); - if (!goog.object.containsKey(fractions, key)) { - flatCoordinates.push(a[0], a[1]); - goog.object.set(fractions, key, true); - } - // Pop the b coordinate off the stack - fracB = fractionStack.pop(); - geoB = geoStack.pop(); - b = stack.pop(); - // Find the m point between the a and b coordinates - fracM = (fracA + fracB) / 2; - geoM = interpolate(fracM); - m = transform(geoM); - if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1], - b[0], b[1]) < squaredTolerance) { - // If the m point is sufficiently close to the straight line, then we - // discard it. Just use the b coordinate and move on to the next line - // segment. - flatCoordinates.push(b[0], b[1]); - key = fracB.toString(); - goog.asserts.assert(!goog.object.containsKey(fractions, key)); - goog.object.set(fractions, key, true); - } else { - // Otherwise, we need to subdivide the current line segment. Split it - // into two and push the two line segments onto the stack. - fractionStack.push(fracB, fracM, fracM, fracA); - stack.push(b, m, m, a); - geoStack.push(geoB, geoM, geoM, geoA); + +/** + * @inheritDoc + */ +ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + var options = this.getReadOptions(node, opt_options); + if (node.localName == 'osm') { + var state = ol.xml.pushParseAndPop({ + nodes: {}, + features: [] + }, ol.format.OSMXML.PARSERS_, node, [options]); + if (goog.isDef(state.features)) { + return state.features; } } - goog.asserts.assert(maxIterations > 0); - - return flatCoordinates; + return []; }; /** -* Generate a great-circle arcs between two lat/lon points. -* @param {number} lon1 Longitude 1 in degrees. -* @param {number} lat1 Latitude 1 in degrees. -* @param {number} lon2 Longitude 2 in degrees. -* @param {number} lat2 Latitude 2 in degrees. - * @param {ol.proj.Projection} projection Projection. -* @param {number} squaredTolerance Squared tolerance. -* @return {Array.<number>} Flat coordinates. -*/ -ol.geom.flat.geodesic.greatCircleArc = function( - lon1, lat1, lon2, lat2, projection, squaredTolerance) { - - var geoProjection = ol.proj.get('EPSG:4326'); - - var cosLat1 = Math.cos(goog.math.toRadians(lat1)); - var sinLat1 = Math.sin(goog.math.toRadians(lat1)); - var cosLat2 = Math.cos(goog.math.toRadians(lat2)); - var sinLat2 = Math.sin(goog.math.toRadians(lat2)); - var cosDeltaLon = Math.cos(goog.math.toRadians(lon2 - lon1)); - var sinDeltaLon = Math.sin(goog.math.toRadians(lon2 - lon1)); - var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon; + * Read the projection from an OSM source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable + */ +ol.format.OSMXML.prototype.readProjection; - return ol.geom.flat.geodesic.line_( - /** - * @param {number} frac Fraction. - * @return {ol.Coordinate} Coordinate. - */ - function(frac) { - if (1 <= d) { - return [lon2, lat2]; - } - var D = frac * Math.acos(d); - var cosD = Math.cos(D); - var sinD = Math.sin(D); - var y = sinDeltaLon * cosLat2; - var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon; - var theta = Math.atan2(y, x); - var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta)); - var lon = goog.math.toRadians(lon1) + - Math.atan2(Math.sin(theta) * sinD * cosLat1, - cosD - sinLat1 * Math.sin(lat)); - return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; - }, ol.proj.getTransform(geoProjection, projection), squaredTolerance); -}; +goog.provide('ol.format.XLink'); /** - * Generate a meridian (line at constant longitude). - * @param {number} lon Longitude. - * @param {number} lat1 Latitude 1. - * @param {number} lat2 Latitude 2. - * @param {ol.proj.Projection} projection Projection. - * @param {number} squaredTolerance Squared tolerance. - * @return {Array.<number>} Flat coordinates. + * @const + * @type {string} */ -ol.geom.flat.geodesic.meridian = - function(lon, lat1, lat2, projection, squaredTolerance) { - var epsg4326Projection = ol.proj.get('EPSG:4326'); - return ol.geom.flat.geodesic.line_( - /** - * @param {number} frac Fraction. - * @return {ol.Coordinate} Coordinate. - */ - function(frac) { - return [lon, lat1 + ((lat2 - lat1) * frac)]; - }, - ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); -}; +ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; /** - * Generate a parallel (line at constant latitude). - * @param {number} lat Latitude. - * @param {number} lon1 Longitude 1. - * @param {number} lon2 Longitude 2. - * @param {ol.proj.Projection} projection Projection. - * @param {number} squaredTolerance Squared tolerance. - * @return {Array.<number>} Flat coordinates. + * @param {Node} node Node. + * @return {boolean|undefined} Boolean. */ -ol.geom.flat.geodesic.parallel = - function(lat, lon1, lon2, projection, squaredTolerance) { - var epsg4326Projection = ol.proj.get('EPSG:4326'); - return ol.geom.flat.geodesic.line_( - /** - * @param {number} frac Fraction. - * @return {ol.Coordinate} Coordinate. - */ - function(frac) { - return [lon1 + ((lon2 - lon1) * frac), lat]; - }, - ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +ol.format.XLink.readHref = function(node) { + return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href'); }; -goog.provide('ol.Graticule'); +goog.provide('ol.format.XML'); goog.require('goog.asserts'); -goog.require('goog.math'); -goog.require('ol.extent'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.flat.geodesic'); -goog.require('ol.proj'); -goog.require('ol.render.EventType'); -goog.require('ol.style.Stroke'); +goog.require('ol.xml'); /** + * @classdesc + * Generic format for reading non-feature XML data + * * @constructor - * @param {olx.GraticuleOptions=} opt_options Options. - * @api */ -ol.Graticule = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @type {ol.Map} - * @private - */ - this.map_ = null; +ol.format.XML = function() { +}; - /** - * @type {ol.proj.Projection} - * @private - */ - this.projection_ = null; - /** - * @type {number} - * @private - */ - this.maxLat_ = Infinity; +/** + * @param {Document|Node|string} source Source. + * @return {Object} + */ +ol.format.XML.prototype.read = function(source) { + if (ol.xml.isDocument(source)) { + return this.readFromDocument(/** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readFromNode(/** @type {Node} */ (source)); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readFromDocument(doc); + } else { + goog.asserts.fail(); + return null; + } +}; - /** - * @type {number} - * @private - */ - this.maxLon_ = Infinity; - /** - * @type {number} - * @private - */ - this.minLat_ = -Infinity; +/** + * @param {Document} doc Document. + * @return {Object} + */ +ol.format.XML.prototype.readFromDocument = goog.abstractMethod; - /** - * @type {number} - * @private - */ - this.minLon_ = -Infinity; - /** - * @type {number} - * @private - */ - this.targetSize_ = goog.isDef(options.targetSize) ? - options.targetSize : 100; +/** + * @param {Node} node Node. + * @return {Object} + */ +ol.format.XML.prototype.readFromNode = goog.abstractMethod; - /** - * @type {number} - * @private - */ - this.maxLines_ = goog.isDef(options.maxLines) ? options.maxLines : 100; - goog.asserts.assert(this.maxLines_ > 0); +goog.provide('ol.format.OWS'); - /** - * @type {Array.<ol.geom.LineString>} - * @private - */ - this.meridians_ = []; +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); - /** - * @type {Array.<ol.geom.LineString>} - * @private - */ - this.parallels_ = []; - /** - * @type {ol.style.Stroke} - * @private - */ - this.strokeStyle_ = goog.isDef(options.strokeStyle) ? - options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_; - /** - * @type {ol.TransformFunction|undefined} - * @private - */ - this.fromLonLatTransform_ = undefined; +/** + * @constructor + * @extends {ol.format.XML} + */ +ol.format.OWS = function() { + goog.base(this); +}; +goog.inherits(ol.format.OWS, ol.format.XML); - /** - * @type {ol.TransformFunction|undefined} - * @private - */ - this.toLonLatTransform_ = undefined; - /** - * @type {ol.Coordinate} - * @private - */ - this.projectionCenterLonLat_ = null; +/** + * @param {Document} doc Document. + * @return {Object} OWS object. + */ +ol.format.OWS.prototype.readFromDocument = function(doc) { + goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + return this.readFromNode(n); + } + } + return null; +}; - this.setMap(goog.isDef(options.map) ? options.map : null); + +/** + * @param {Node} node Node. + * @return {Object} OWS object. + */ +ol.format.OWS.prototype.readFromNode = function(node) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + var owsObject = ol.xml.pushParseAndPop({}, + ol.format.OWS.PARSERS_, node, []); + return goog.isDef(owsObject) ? owsObject : null; }; /** - * @type {ol.style.Stroke} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private - * @const + * @return {Object|undefined} */ -ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ - color: 'rgba(0,0,0,0.2)' -}); +ol.format.OWS.readAddress_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Address', + 'localName should be Address'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.ADDRESS_PARSERS_, node, objectStack); +}; /** - * TODO can be configurable - * @type {Array.<number>} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Object|undefined} */ -ol.Graticule.intervals_ = [90, 45, 30, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, - 0.01, 0.005, 0.002, 0.001]; +ol.format.OWS.readAllowedValues_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'AllowedValues', + 'localName should be AllowedValues'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack); +}; /** - * @param {number} lon Longitude. - * @param {number} squaredTolerance Squared tolerance. - * @param {ol.Extent} extent Extent. - * @param {number} index Index. - * @return {number} Index. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Object|undefined} */ -ol.Graticule.prototype.addMeridian_ = - function(lon, squaredTolerance, extent, index) { - var lineString = this.getMeridian_(lon, squaredTolerance, index); - if (ol.extent.intersects(lineString.getExtent(), extent)) { - this.meridians_[index++] = lineString; +ol.format.OWS.readConstraint_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Constraint', + 'localName should be Constraint'); + var name = node.getAttribute('name'); + if (!goog.isDef(name)) { + return undefined; } - return index; + return ol.xml.pushParseAndPop({'name': name}, + ol.format.OWS.CONSTRAINT_PARSERS_, node, + objectStack); }; /** - * @param {number} lat Latitude. - * @param {number} squaredTolerance Squared tolerance. - * @param {ol.Extent} extent Extent. - * @param {number} index Index. - * @return {number} Index. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Object|undefined} */ -ol.Graticule.prototype.addParallel_ = - function(lat, squaredTolerance, extent, index) { - var lineString = this.getParallel_(lat, squaredTolerance, index); - if (ol.extent.intersects(lineString.getExtent(), extent)) { - this.parallels_[index++] = lineString; - } - return index; +ol.format.OWS.readContactInfo_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ContactInfo', + 'localName should be ContactInfo'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack); }; /** - * @param {ol.Extent} extent Extent. - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} squaredTolerance Squared tolerance. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Object|undefined} */ -ol.Graticule.prototype.createGraticule_ = - function(extent, center, resolution, squaredTolerance) { +ol.format.OWS.readDcp_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'DCP', 'localName should be DCP'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.DCP_PARSERS_, node, objectStack); +}; - var interval = this.getInterval_(resolution); - if (interval == -1) { - this.meridians_.length = this.parallels_.length = 0; - return; - } - var centerLonLat = this.toLonLatTransform_(center); - var centerLon = centerLonLat[0]; - var centerLat = centerLonLat[1]; - var maxLines = this.maxLines_; - var cnt, idx, lat, lon; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readGet_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Get', 'localName should be Get'); + var href = ol.format.XLink.readHref(node); + if (!goog.isDef(href)) { + return undefined; + } + return ol.xml.pushParseAndPop({'href': href}, + ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack); +}; - // Create meridians - centerLon = Math.floor(centerLon / interval) * interval; - lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_); +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readHttp_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'HTTP', 'localName should be HTTP'); + return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_, + node, objectStack); +}; - idx = this.addMeridian_(lon, squaredTolerance, extent, 0); - cnt = 0; - while (lon != this.minLon_ && cnt++ < maxLines) { - lon = Math.max(lon - interval, this.minLon_); - idx = this.addMeridian_(lon, squaredTolerance, extent, idx); +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readOperation_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Operation', + 'localName should be Operation'); + var name = node.getAttribute('name'); + var value = ol.xml.pushParseAndPop({}, + ol.format.OWS.OPERATION_PARSERS_, node, objectStack); + if (!goog.isDef(value)) { + return undefined; } + var object = /** @type {Object} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(object), 'object should be an Object'); + object[name] = value; - lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_); +}; - cnt = 0; - while (lon != this.maxLon_ && cnt++ < maxLines) { - lon = Math.min(lon + interval, this.maxLon_); - idx = this.addMeridian_(lon, squaredTolerance, extent, idx); - } - this.meridians_.length = idx; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readOperationsMetadata_ = function(node, + objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'OperationsMetadata', + 'localName should be OperationsMetadata'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node, + objectStack); +}; - // Create parallels - centerLat = Math.floor(centerLat / interval) * interval; - lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_); +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readPhone_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Phone', 'localName should be Phone'); + return ol.xml.pushParseAndPop({}, + ol.format.OWS.PHONE_PARSERS_, node, objectStack); +}; - idx = this.addParallel_(lat, squaredTolerance, extent, 0); - cnt = 0; - while (lat != this.minLat_ && cnt++ < maxLines) { - lat = Math.max(lat - interval, this.minLat_); - idx = this.addParallel_(lat, squaredTolerance, extent, idx); - } +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readServiceIdentification_ = function(node, + objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ServiceIdentification', + 'localName should be ServiceIdentification'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node, + objectStack); +}; - lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_); - cnt = 0; - while (lat != this.maxLat_ && cnt++ < maxLines) { - lat = Math.min(lat + interval, this.maxLat_); - idx = this.addParallel_(lat, squaredTolerance, extent, idx); - } +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readServiceContact_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ServiceContact', + 'localName should be ServiceContact'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node, + objectStack); +}; - this.parallels_.length = idx; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} + */ +ol.format.OWS.readServiceProvider_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ServiceProvider', + 'localName should be ServiceProvider'); + return ol.xml.pushParseAndPop( + {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node, + objectStack); }; /** - * @param {number} resolution Resolution. - * @return {number} The interval in degrees. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {string|undefined} */ -ol.Graticule.prototype.getInterval_ = function(resolution) { - var centerLon = this.projectionCenterLonLat_[0]; - var centerLat = this.projectionCenterLonLat_[1]; - var interval = -1; - var i, ii, delta, dist; - var target = Math.pow(this.targetSize_ * resolution, 2); - /** @type {Array.<number>} **/ - var p1 = []; - /** @type {Array.<number>} **/ - var p2 = []; - for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) { - delta = ol.Graticule.intervals_[i] / 2; - p1[0] = centerLon - delta; - p1[1] = centerLat - delta; - p2[0] = centerLon + delta; - p2[1] = centerLat + delta; - this.fromLonLatTransform_(p1, p1); - this.fromLonLatTransform_(p2, p2); - dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2); - if (dist <= target) { - break; - } - interval = ol.Graticule.intervals_[i]; - } - return interval; +ol.format.OWS.readValue_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Value', 'localName should be Value'); + return ol.format.XSD.readString(node); }; /** - * @return {ol.Map} The map. - * @api + * @const + * @type {Array.<string>} + * @private */ -ol.Graticule.prototype.getMap = function() { - return this.map_; -}; +ol.format.OWS.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/ows/1.1' +]; /** - * @param {number} lon Longitude. - * @param {number} squaredTolerance Squared tolerance. - * @return {ol.geom.LineString} The meridian line string. - * @param {number} index Index. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.Graticule.prototype.getMeridian_ = function(lon, squaredTolerance, index) { - goog.asserts.assert(lon >= this.minLon_); - goog.asserts.assert(lon <= this.maxLon_); - var flatCoordinates = ol.geom.flat.geodesic.meridian(lon, - this.minLat_, this.maxLat_, this.projection_, squaredTolerance); - goog.asserts.assert(flatCoordinates.length > 0); - var lineString = goog.isDef(this.meridians_[index]) ? - this.meridians_[index] : new ol.geom.LineString(null); - lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); - return lineString; -}; +ol.format.OWS.PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'ServiceIdentification': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceIdentification_), + 'ServiceProvider': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceProvider_), + 'OperationsMetadata': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readOperationsMetadata_) + }); /** - * @return {Array.<ol.geom.LineString>} The meridians. - * @api + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.Graticule.prototype.getMeridians = function() { - return this.meridians_; -}; +ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'DeliveryPoint': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'AdministrativeArea': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ElectronicMailAddress': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); /** - * @param {number} lat Latitude. - * @param {number} squaredTolerance Squared tolerance. - * @return {ol.geom.LineString} The parallel line string. - * @param {number} index Index. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.Graticule.prototype.getParallel_ = function(lat, squaredTolerance, index) { - goog.asserts.assert(lat >= this.minLat_); - goog.asserts.assert(lat <= this.maxLat_); - var flatCoordinates = ol.geom.flat.geodesic.parallel(lat, - this.minLon_, this.maxLon_, this.projection_, squaredTolerance); - goog.asserts.assert(flatCoordinates.length > 0); - var lineString = goog.isDef(this.parallels_[index]) ? - this.parallels_[index] : new ol.geom.LineString(null); - lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); - return lineString; -}; +ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_) + }); /** - * @return {Array.<ol.geom.LineString>} The parallels. - * @api + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.Graticule.prototype.getParallels = function() { - return this.parallels_; -}; +ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'AllowedValues': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readAllowedValues_) + }); /** - * @param {ol.render.Event} e Event. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.Graticule.prototype.handlePostCompose_ = function(e) { - var vectorContext = e.vectorContext; - var frameState = e.frameState; - var extent = frameState.extent; - var viewState = frameState.viewState; - var center = viewState.center; - var projection = viewState.projection; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var squaredTolerance = - resolution * resolution / (4 * pixelRatio * pixelRatio); +ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_), + 'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_) + }); - var updateProjectionInfo = goog.isNull(this.projection_) || - !ol.proj.equivalent(this.projection_, projection); - if (updateProjectionInfo) { - this.updateProjectionInfo_(projection); - } +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.DCP_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_) + }); - this.createGraticule_(extent, center, resolution, squaredTolerance); - // Draw the lines - vectorContext.setFillStrokeStyle(null, this.strokeStyle_); - var i, l, line; - for (i = 0, l = this.meridians_.length; i < l; ++i) { - line = this.meridians_[i]; - vectorContext.drawLineStringGeometry(line, null); - } - for (i = 0, l = this.parallels_.length; i < l; ++i) { - line = this.parallels_[i]; - vectorContext.drawLineStringGeometry(line, null); - } -}; +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_), + 'Post': undefined // TODO + }); /** - * @param {ol.proj.Projection} projection Projection. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -ol.Graticule.prototype.updateProjectionInfo_ = function(projection) { - goog.asserts.assert(!goog.isNull(projection)); +ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_) + }); - var extent = projection.getExtent(); - var worldExtent = projection.getWorldExtent(); - var maxLat = worldExtent[3]; - var maxLon = worldExtent[2]; - var minLat = worldExtent[1]; - var minLon = worldExtent[0]; - goog.asserts.assert(!goog.isNull(extent)); - goog.asserts.assert(goog.isDef(maxLat)); - goog.asserts.assert(goog.isDef(maxLon)); - goog.asserts.assert(goog.isDef(minLat)); - goog.asserts.assert(goog.isDef(minLon)); +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Operation': ol.format.OWS.readOperation_ + }); - this.maxLat_ = maxLat; - this.maxLon_ = maxLon; - this.minLat_ = minLat; - this.minLon_ = minLon; - var epsg4326Projection = ol.proj.get('EPSG:4326'); +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); - this.fromLonLatTransform_ = ol.proj.getTransform( - epsg4326Projection, projection); - this.toLonLatTransform_ = ol.proj.getTransform( - projection, epsg4326Projection); +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Constraint': ol.xml.makeObjectPropertyPusher( + ol.format.OWS.readConstraint_) + }); - this.projectionCenterLonLat_ = this.toLonLatTransform_( - ol.extent.getCenter(extent)); - this.projection_ = projection; -}; +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.SERVICE_CONTACT_PARSERS_ = + ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'IndividualName': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ContactInfo': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readContactInfo_) + }); /** - * @param {ol.Map} map Map. - * @api + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.Graticule.prototype.setMap = function(map) { - if (!goog.isNull(this.map_)) { - this.map_.un(ol.render.EventType.POSTCOMPOSE, - this.handlePostCompose_, this); - this.map_.render(); +ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ = + ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ServiceTypeVersion': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.OWS.SERVICE_PROVIDER_PARSERS_ = + ol.xml.makeParsersNS( + ol.format.OWS.NAMESPACE_URIS_, { + 'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref), + 'ServiceContact': ol.xml.makeObjectPropertySetter( + ol.format.OWS.readServiceContact_) + }); + +goog.provide('ol.geom.flat.flip'); + +goog.require('goog.asserts'); + + +/** + * @param {Array.<number>} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {Array.<number>=} opt_dest Destination. + * @param {number=} opt_destOffset Destination offset. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.flip.flipXY = + function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) { + var dest, destOffset; + if (goog.isDef(opt_dest)) { + dest = opt_dest; + destOffset = goog.isDef(opt_destOffset) ? opt_destOffset : 0; + } else { + goog.asserts.assert(!goog.isDef(opt_destOffset), + 'opt_destOffSet should be defined'); + dest = []; + destOffset = 0; } - if (!goog.isNull(map)) { - map.on(ol.render.EventType.POSTCOMPOSE, - this.handlePostCompose_, this); - map.render(); + var j, k; + for (j = offset; j < end; ) { + var x = flatCoordinates[j++]; + dest[destOffset++] = flatCoordinates[j++]; + dest[destOffset++] = x; + for (k = 2; k < stride; ++k) { + dest[destOffset++] = flatCoordinates[j++]; + } } - this.map_ = map; + dest.length = destOffset; + return dest; }; -goog.provide('ol.Image'); +goog.provide('ol.format.Polyline'); -goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('ol.ImageBase'); -goog.require('ol.ImageState'); -goog.require('ol.extent'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.geom.flat.flip'); +goog.require('ol.geom.flat.inflate'); +goog.require('ol.proj'); /** + * @classdesc + * Feature format for reading and writing data in the Encoded + * Polyline Algorithm Format. + * * @constructor - * @extends {ol.ImageBase} - * @param {ol.Extent} extent Extent. - * @param {number|undefined} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {Array.<ol.Attribution>} attributions Attributions. - * @param {string} src Image source URI. - * @param {?string} crossOrigin Cross origin. - * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @extends {ol.format.TextFeature} + * @param {olx.format.PolylineOptions=} opt_options + * Optional configuration object. + * @api stable */ -ol.Image = function(extent, resolution, pixelRatio, attributions, src, - crossOrigin, imageLoadFunction) { - - goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE, - attributions); +ol.format.Polyline = function(opt_options) { - /** - * @private - * @type {string} - */ - this.src_ = src; + var options = goog.isDef(opt_options) ? opt_options : {}; - /** - * @private - * @type {Image} - */ - this.image_ = new Image(); - if (!goog.isNull(crossOrigin)) { - this.image_.crossOrigin = crossOrigin; - } + goog.base(this); /** - * @private - * @type {Object.<number, Image>} + * @inheritDoc */ - this.imageByContext_ = {}; + this.defaultDataProjection = ol.proj.get('EPSG:4326'); /** * @private - * @type {Array.<number>} - */ - this.imageListenerKeys_ = null; - - /** - * @protected - * @type {ol.ImageState} + * @type {number} */ - this.state = ol.ImageState.IDLE; + this.factor_ = goog.isDef(options.factor) ? options.factor : 1e5; /** * @private - * @type {ol.ImageLoadFunctionType} + * @type {ol.geom.GeometryLayout} */ - this.imageLoadFunction_ = imageLoadFunction; - + this.geometryLayout_ = goog.isDef(options.geometryLayout) ? + options.geometryLayout : ol.geom.GeometryLayout.XY; }; -goog.inherits(ol.Image, ol.ImageBase); +goog.inherits(ol.format.Polyline, ol.format.TextFeature); /** - * @param {Object=} opt_context Object. - * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + * Encode a list of n-dimensional points and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of n-dimensional points. + * @param {number} stride The number of dimension of the points in the list. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * Default is `1e5`. + * @return {string} The encoded string. * @api */ -ol.Image.prototype.getImage = function(opt_context) { - if (goog.isDef(opt_context)) { - var image; - var key = goog.getUid(opt_context); - if (key in this.imageByContext_) { - return this.imageByContext_[key]; - } else if (goog.object.isEmpty(this.imageByContext_)) { - image = this.image_; - } else { - image = /** @type {Image} */ (this.image_.cloneNode(false)); - } - this.imageByContext_[key] = image; - return image; - } else { - return this.image_; +ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) { + var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; + var d; + + var lastNumbers = new Array(stride); + for (d = 0; d < stride; ++d) { + lastNumbers[d] = 0; } -}; + var i, ii; + for (i = 0, ii = numbers.length; i < ii;) { + for (d = 0; d < stride; ++d, ++i) { + var num = numbers[i]; + var delta = num - lastNumbers[d]; + lastNumbers[d] = num; + + numbers[i] = delta; + } + } -/** - * Tracks loading or read errors. - * - * @private - */ -ol.Image.prototype.handleImageError_ = function() { - this.state = ol.ImageState.ERROR; - this.unlistenImage_(); - this.changed(); + return ol.format.Polyline.encodeFloats(numbers, factor); }; /** - * Tracks successful image load. + * Decode a list of n-dimensional points from an encoded string * - * @private + * @param {string} encoded An encoded string. + * @param {number} stride The number of dimension of the points in the + * encoded string. + * @param {number=} opt_factor The factor by which the resulting numbers will + * be divided. Default is `1e5`. + * @return {Array.<number>} A list of n-dimensional points. + * @api */ -ol.Image.prototype.handleImageLoad_ = function() { - if (!goog.isDef(this.resolution)) { - this.resolution = ol.extent.getHeight(this.extent) / this.image_.height; - } - this.state = ol.ImageState.LOADED; - this.unlistenImage_(); - this.changed(); -}; - +ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) { + var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; + var d; -/** - * Load not yet loaded URI. - */ -ol.Image.prototype.load = function() { - if (this.state == ol.ImageState.IDLE) { - this.state = ol.ImageState.LOADING; - goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); - this.imageListenerKeys_ = [ - goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, - this.handleImageError_, false, this), - goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, - this.handleImageLoad_, false, this) - ]; - this.imageLoadFunction_(this, this.src_); + /** @type {Array.<number>} */ + var lastNumbers = new Array(stride); + for (d = 0; d < stride; ++d) { + lastNumbers[d] = 0; } -}; + var numbers = ol.format.Polyline.decodeFloats(encoded, factor); -/** - * Discards event handlers which listen for load completion or errors. - * - * @private - */ -ol.Image.prototype.unlistenImage_ = function() { - goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); - goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); - this.imageListenerKeys_ = null; -}; - -goog.provide('ol.ImageCanvas'); + var i, ii; + for (i = 0, ii = numbers.length; i < ii;) { + for (d = 0; d < stride; ++d, ++i) { + lastNumbers[d] += numbers[i]; -goog.require('ol.ImageBase'); -goog.require('ol.ImageState'); + numbers[i] = lastNumbers[d]; + } + } + return numbers; +}; /** - * @constructor - * @extends {ol.ImageBase} - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {Array.<ol.Attribution>} attributions Attributions. - * @param {HTMLCanvasElement} canvas Canvas. + * Encode a list of floating point numbers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.<number>} numbers A list of floating point numbers. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * Default is `1e5`. + * @return {string} The encoded string. + * @api */ -ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions, - canvas) { - - goog.base(this, extent, resolution, pixelRatio, ol.ImageState.LOADED, - attributions); - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = canvas; +ol.format.Polyline.encodeFloats = function(numbers, opt_factor) { + var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + numbers[i] = Math.round(numbers[i] * factor); + } + return ol.format.Polyline.encodeSignedIntegers(numbers); }; -goog.inherits(ol.ImageCanvas, ol.ImageBase); /** - * @inheritDoc + * Decode a list of floating point numbers from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number=} opt_factor The factor by which the result will be divided. + * Default is `1e5`. + * @return {Array.<number>} A list of floating point numbers. + * @api */ -ol.ImageCanvas.prototype.getImage = function(opt_context) { - return this.canvas_; +ol.format.Polyline.decodeFloats = function(encoded, opt_factor) { + var factor = goog.isDef(opt_factor) ? opt_factor : 1e5; + var numbers = ol.format.Polyline.decodeSignedIntegers(encoded); + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + numbers[i] /= factor; + } + return numbers; }; -goog.provide('ol.ImageLoadFunctionType'); - /** - * A function that takes an {@link ol.Image} for the image and a `{string}` for - * the src as arguments. It is supposed to make it so the underlying image - * {@link ol.Image#getImage} is assigned the content specified by the src. If - * not specified, the default is - * - * function(image, src) { - * image.getImage().src = src; - * } + * Encode a list of signed integers and return an encoded string * - * Providing a custom `imageLoadFunction` can be useful to load images with - * post requests or - in general - through XHR requests, where the src of the - * image element would be set to a data URI when the content is loaded. + * Attention: This function will modify the passed array! * - * @typedef {function(ol.Image, string)} - * @api + * @param {Array.<number>} numbers A list of signed integers. + * @return {string} The encoded string. */ -ol.ImageLoadFunctionType; - -goog.provide('ol.TileLoadFunctionType'); +ol.format.Polyline.encodeSignedIntegers = function(numbers) { + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + var num = numbers[i]; + numbers[i] = (num < 0) ? ~(num << 1) : (num << 1); + } + return ol.format.Polyline.encodeUnsignedIntegers(numbers); +}; /** - * A function that takes an {@link ol.ImageTile} for the image tile and a - * `{string}` for the src as arguments. + * Decode a list of signed integers from an encoded string * - * @typedef {function(ol.ImageTile, string)} - * @api + * @param {string} encoded An encoded string. + * @return {Array.<number>} A list of signed integers. */ -ol.TileLoadFunctionType; - -goog.provide('ol.ImageTile'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('ol.Tile'); -goog.require('ol.TileCoord'); -goog.require('ol.TileLoadFunctionType'); -goog.require('ol.TileState'); - +ol.format.Polyline.decodeSignedIntegers = function(encoded) { + var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded); + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + var num = numbers[i]; + numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); + } + return numbers; +}; /** - * @constructor - * @extends {ol.Tile} - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.TileState} state State. - * @param {string} src Image source URI. - * @param {?string} crossOrigin Cross origin. - * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * Encode a list of unsigned integers and return an encoded string + * + * @param {Array.<number>} numbers A list of unsigned integers. + * @return {string} The encoded string. */ -ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) { - - goog.base(this, tileCoord, state); - - /** - * Image URI - * - * @private - * @type {string} - */ - this.src_ = src; - - /** - * @private - * @type {Image} - */ - this.image_ = new Image(); - if (!goog.isNull(crossOrigin)) { - this.image_.crossOrigin = crossOrigin; +ol.format.Polyline.encodeUnsignedIntegers = function(numbers) { + var encoded = ''; + var i, ii; + for (i = 0, ii = numbers.length; i < ii; ++i) { + encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]); } - - /** - * @private - * @type {Object.<number, Image>} - */ - this.imageByContext_ = {}; - - /** - * @private - * @type {Array.<number>} - */ - this.imageListenerKeys_ = null; - - /** - * @private - * @type {ol.TileLoadFunctionType} - */ - this.tileLoadFunction_ = tileLoadFunction; - + return encoded; }; -goog.inherits(ol.ImageTile, ol.Tile); /** - * @inheritDoc - * @api + * Decode a list of unsigned integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.<number>} A list of unsigned integers. */ -ol.ImageTile.prototype.getImage = function(opt_context) { - if (goog.isDef(opt_context)) { - var image; - var key = goog.getUid(opt_context); - if (key in this.imageByContext_) { - return this.imageByContext_[key]; - } else if (goog.object.isEmpty(this.imageByContext_)) { - image = this.image_; +ol.format.Polyline.decodeUnsignedIntegers = function(encoded) { + var numbers = []; + var current = 0; + var shift = 0; + var i, ii; + for (i = 0, ii = encoded.length; i < ii; ++i) { + var b = encoded.charCodeAt(i) - 63; + current |= (b & 0x1f) << shift; + if (b < 0x20) { + numbers.push(current); + current = 0; + shift = 0; } else { - image = /** @type {Image} */ (this.image_.cloneNode(false)); + shift += 5; } - this.imageByContext_[key] = image; - return image; - } else { - return this.image_; } + return numbers; }; /** - * @inheritDoc + * Encode one single unsigned integer and return an encoded string + * + * @param {number} num Unsigned integer that should be encoded. + * @return {string} The encoded string. */ -ol.ImageTile.prototype.getKey = function() { - return this.src_; +ol.format.Polyline.encodeUnsignedInteger = function(num) { + var value, encoded = ''; + while (num >= 0x20) { + value = (0x20 | (num & 0x1f)) + 63; + encoded += String.fromCharCode(value); + num >>= 5; + } + value = num + 63; + encoded += String.fromCharCode(value); + return encoded; }; /** - * Tracks loading or read errors. + * Read the feature from the Polyline source. The coordinates are assumed to be + * in two dimensions and in latitude, longitude order. * - * @private + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable */ -ol.ImageTile.prototype.handleImageError_ = function() { - this.state = ol.TileState.ERROR; - this.unlistenImage_(); - this.changed(); +ol.format.Polyline.prototype.readFeature; + + +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) { + var geometry = this.readGeometryFromText(text, opt_options); + return new ol.Feature(geometry); }; /** - * Tracks successful image load. + * Read the feature from the source. As Polyline sources contain a single + * feature, this will return the feature in an array. * - * @private + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable */ -ol.ImageTile.prototype.handleImageLoad_ = function() { - if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { - if (!goog.isDef(this.image_.naturalWidth)) { - this.image_.naturalWidth = this.image_.width; - this.image_.naturalHeight = this.image_.height; - } - } - - if (this.image_.naturalWidth && this.image_.naturalHeight) { - this.state = ol.TileState.LOADED; - } else { - this.state = ol.TileState.EMPTY; - } - this.unlistenImage_(); - this.changed(); -}; +ol.format.Polyline.prototype.readFeatures; /** - * Load not yet loaded URI. + * @inheritDoc */ -ol.ImageTile.prototype.load = function() { - if (this.state == ol.TileState.IDLE) { - this.state = ol.TileState.LOADING; - goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); - this.imageListenerKeys_ = [ - goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, - this.handleImageError_, false, this), - goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, - this.handleImageLoad_, false, this) - ]; - this.tileLoadFunction_(this, this.src_); - } +ol.format.Polyline.prototype.readFeaturesFromText = + function(text, opt_options) { + var feature = this.readFeatureFromText(text, opt_options); + return [feature]; }; /** - * Discards event handlers which listen for load completion or errors. + * Read the geometry from the source. * - * @private + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable */ -ol.ImageTile.prototype.unlistenImage_ = function() { - goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); - goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); - this.imageListenerKeys_ = null; -}; +ol.format.Polyline.prototype.readGeometry; -goog.provide('ol.ImageUrlFunction'); -goog.provide('ol.ImageUrlFunctionType'); -goog.require('ol.Size'); +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.readGeometryFromText = + function(text, opt_options) { + var stride = ol.geom.SimpleGeometry.getStrideForLayout(this.geometryLayout_); + var flatCoordinates = ol.format.Polyline.decodeDeltas( + text, stride, this.factor_); + ol.geom.flat.flip.flipXY( + flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); + var coordinates = ol.geom.flat.inflate.coordinates( + flatCoordinates, 0, flatCoordinates.length, stride); + + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions( + new ol.geom.LineString(coordinates, this.geometryLayout_), false, + this.adaptOptions(opt_options))); +}; /** - * @typedef {function(this:ol.source.Image, ol.Extent, ol.Size, - * ol.proj.Projection): (string|undefined)} + * Read the projection from a Polyline source. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @return {ol.proj.Projection} Projection. + * @api stable */ -ol.ImageUrlFunctionType; +ol.format.Polyline.prototype.readProjection; /** - * @param {string} baseUrl Base URL (may have query data). - * @param {Object.<string,*>} params to encode in the URL. - * @param {function(string, Object.<string,*>, ol.Extent, ol.Size, - * ol.proj.Projection): (string|undefined)} paramsFunction params function. - * @return {ol.ImageUrlFunctionType} Image URL function. + * @inheritDoc */ -ol.ImageUrlFunction.createFromParamsFunction = - function(baseUrl, params, paramsFunction) { - return ( - /** - * @this {ol.source.Image} - * @param {ol.Extent} extent Extent. - * @param {ol.Size} size Size. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} URL. - */ - function(extent, size, projection) { - return paramsFunction(baseUrl, params, extent, size, projection); - }); +ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) { + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + return this.writeGeometryText(geometry, opt_options); + } else { + goog.asserts.fail('geometry needs to be defined'); + return ''; + } }; /** - * @this {ol.source.Image} - * @param {ol.Extent} extent Extent. - * @param {ol.Size} size Size. - * @return {string|undefined} Image URL. + * @inheritDoc */ -ol.ImageUrlFunction.nullImageUrlFunction = - function(extent, size) { - return undefined; +ol.format.Polyline.prototype.writeFeaturesText = + function(features, opt_options) { + goog.asserts.assert(features.length == 1, + 'features array should have 1 item'); + return this.writeFeatureText(features[0], opt_options); }; -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Provides a files drag and drop event detector. It works on - * HTML5 browsers. + * Write a single geometry in Polyline format. * - * @see ../demos/filedrophandler.html + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} Geometry. + * @api stable */ +ol.format.Polyline.prototype.writeGeometry; -goog.provide('goog.events.FileDropHandler'); -goog.provide('goog.events.FileDropHandler.EventType'); -goog.require('goog.array'); -goog.require('goog.dom'); -goog.require('goog.events.BrowserEvent'); -goog.require('goog.events.EventHandler'); -goog.require('goog.events.EventTarget'); -goog.require('goog.events.EventType'); -goog.require('goog.log'); -goog.require('goog.log.Level'); +/** + * @inheritDoc + */ +ol.format.Polyline.prototype.writeGeometryText = + function(geometry, opt_options) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + geometry = /** @type {ol.geom.LineString} */ + (ol.format.Feature.transformWithOptions( + geometry, true, this.adaptOptions(opt_options))); + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + ol.geom.flat.flip.flipXY( + flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates); + return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_); +}; + +goog.provide('ol.format.TopoJSON'); + +goog.require('goog.asserts'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.JSONFeature'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj'); /** - * A files drag and drop event detector. Gets an {@code element} as parameter - * and fires {@code goog.events.FileDropHandler.EventType.DROP} event when files - * are dropped in the {@code element}. + * @classdesc + * Feature format for reading and writing data in the TopoJSON format. * - * @param {Element|Document} element The element or document to listen on. - * @param {boolean=} opt_preventDropOutside Whether to prevent a drop on the - * area outside the {@code element}. Default false. * @constructor - * @extends {goog.events.EventTarget} - * @final + * @extends {ol.format.JSONFeature} + * @param {olx.format.TopoJSONOptions=} opt_options Options. + * @api stable */ -goog.events.FileDropHandler = function(element, opt_preventDropOutside) { - goog.events.EventTarget.call(this); - - /** - * Handler for drag/drop events. - * @type {!goog.events.EventHandler<!goog.events.FileDropHandler>} - * @private - */ - this.eventHandler_ = new goog.events.EventHandler(this); +ol.format.TopoJSON = function(opt_options) { - var doc = element; - if (opt_preventDropOutside) { - doc = goog.dom.getOwnerDocument(element); - } + var options = goog.isDef(opt_options) ? opt_options : {}; - // Add dragenter listener to the owner document of the element. - this.eventHandler_.listen(doc, - goog.events.EventType.DRAGENTER, - this.onDocDragEnter_); + goog.base(this); - // Add dragover listener to the owner document of the element only if the - // document is not the element itself. - if (doc != element) { - this.eventHandler_.listen(doc, - goog.events.EventType.DRAGOVER, - this.onDocDragOver_); - } + /** + * @inheritDoc + */ + this.defaultDataProjection = ol.proj.get( + goog.isDefAndNotNull(options.defaultDataProjection) ? + options.defaultDataProjection : 'EPSG:4326'); - // Add dragover and drop listeners to the element. - this.eventHandler_.listen(element, - goog.events.EventType.DRAGOVER, - this.onElemDragOver_); - this.eventHandler_.listen(element, - goog.events.EventType.DROP, - this.onElemDrop_); }; -goog.inherits(goog.events.FileDropHandler, goog.events.EventTarget); +goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature); /** - * Whether the drag event contains files. It is initialized only in the - * dragenter event. It is used in all the drag events to prevent default actions - * only if the drag contains files. Preventing default actions is necessary to - * go from dragenter to dragover and from dragover to drop. However we do not - * always want to prevent default actions, e.g. when the user drags text or - * links on a text area we should not prevent the browser default action that - * inserts the text in the text area. It is also necessary to stop propagation - * when handling drag events on the element to prevent them from propagating - * to the document. + * @const {Array.<string>} * @private - * @type {boolean} */ -goog.events.FileDropHandler.prototype.dndContainsFiles_ = false; +ol.format.TopoJSON.EXTENSIONS_ = ['.topojson']; /** - * A logger, used to help us debug the algorithm. - * @type {goog.log.Logger} + * Concatenate arcs into a coordinate array. + * @param {Array.<number>} indices Indices of arcs to concatenate. Negative + * values indicate arcs need to be reversed. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already + * transformed). + * @return {Array.<ol.Coordinate>} Coordinates array. * @private */ -goog.events.FileDropHandler.prototype.logger_ = - goog.log.getLogger('goog.events.FileDropHandler'); - - -/** - * The types of events fired by this class. - * @enum {string} - */ -goog.events.FileDropHandler.EventType = { - DROP: goog.events.EventType.DROP -}; - - -/** @override */ -goog.events.FileDropHandler.prototype.disposeInternal = function() { - goog.events.FileDropHandler.superClass_.disposeInternal.call(this); - this.eventHandler_.dispose(); +ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) { + /** @type {Array.<ol.Coordinate>} */ + var coordinates = []; + var index, arc; + var i, ii; + var j, jj; + for (i = 0, ii = indices.length; i < ii; ++i) { + index = indices[i]; + if (i > 0) { + // splicing together arcs, discard last point + coordinates.pop(); + } + if (index >= 0) { + // forward arc + arc = arcs[index]; + } else { + // reverse arc + arc = arcs[~index].slice().reverse(); + } + coordinates.push.apply(coordinates, arc); + } + // provide fresh copies of coordinate arrays + for (j = 0, jj = coordinates.length; j < jj; ++j) { + coordinates[j] = coordinates[j].slice(); + } + return coordinates; }; /** - * Dispatches the DROP event. - * @param {goog.events.BrowserEvent} e The underlying browser event. + * Create a point from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @return {ol.geom.Point} Geometry. * @private */ -goog.events.FileDropHandler.prototype.dispatch_ = function(e) { - goog.log.fine(this.logger_, 'Firing DROP event...'); - var event = new goog.events.BrowserEvent(e.getBrowserEvent()); - event.type = goog.events.FileDropHandler.EventType.DROP; - this.dispatchEvent(event); +ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) { + var coordinates = object.coordinates; + if (!goog.isNull(scale) && !goog.isNull(translate)) { + ol.format.TopoJSON.transformVertex_(coordinates, scale, translate); + } + return new ol.geom.Point(coordinates); }; /** - * Handles dragenter on the document. - * @param {goog.events.BrowserEvent} e The dragenter event. + * Create a multi-point from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @return {ol.geom.MultiPoint} Geometry. * @private */ -goog.events.FileDropHandler.prototype.onDocDragEnter_ = function(e) { - goog.log.log(this.logger_, goog.log.Level.FINER, - '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); - var dt = e.getBrowserEvent().dataTransfer; - // Check whether the drag event contains files. - this.dndContainsFiles_ = !!(dt && - ((dt.types && - (goog.array.contains(dt.types, 'Files') || - goog.array.contains(dt.types, 'public.file-url'))) || - (dt.files && dt.files.length > 0))); - // If it does - if (this.dndContainsFiles_) { - // Prevent default actions. - e.preventDefault(); +ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale, + translate) { + var coordinates = object.coordinates; + var i, ii; + if (!goog.isNull(scale) && !goog.isNull(translate)) { + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate); + } } - goog.log.log(this.logger_, goog.log.Level.FINER, - 'dndContainsFiles_: ' + this.dndContainsFiles_); + return new ol.geom.MultiPoint(coordinates); }; /** - * Handles dragging something over the document. - * @param {goog.events.BrowserEvent} e The dragover event. + * Create a linestring from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.LineString} Geometry. * @private */ -goog.events.FileDropHandler.prototype.onDocDragOver_ = function(e) { - goog.log.log(this.logger_, goog.log.Level.FINEST, - '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); - if (this.dndContainsFiles_) { - // Prevent default actions. - e.preventDefault(); - // Disable the drop on the document outside the drop zone. - var dt = e.getBrowserEvent().dataTransfer; - dt.dropEffect = 'none'; - } +ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) { + var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs); + return new ol.geom.LineString(coordinates); }; /** - * Handles dragging something over the element (drop zone). - * @param {goog.events.BrowserEvent} e The dragover event. + * Create a multi-linestring from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.MultiLineString} Geometry. * @private */ -goog.events.FileDropHandler.prototype.onElemDragOver_ = function(e) { - goog.log.log(this.logger_, goog.log.Level.FINEST, - '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); - if (this.dndContainsFiles_) { - // Prevent default actions and stop the event from propagating further to - // the document. Both lines are needed! (See comment above). - e.preventDefault(); - e.stopPropagation(); - // Allow the drop on the drop zone. - var dt = e.getBrowserEvent().dataTransfer; - dt.effectAllowed = 'all'; - dt.dropEffect = 'copy'; +ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) { + var coordinates = []; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); } + return new ol.geom.MultiLineString(coordinates); }; /** - * Handles dropping something onto the element (drop zone). - * @param {goog.events.BrowserEvent} e The drop event. + * Create a polygon from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.Polygon} Geometry. * @private */ -goog.events.FileDropHandler.prototype.onElemDrop_ = function(e) { - goog.log.log(this.logger_, goog.log.Level.FINER, - '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); - // If the drag and drop event contains files. - if (this.dndContainsFiles_) { - // Prevent default actions and stop the event from propagating further to - // the document. Both lines are needed! (See comment above). - e.preventDefault(); - e.stopPropagation(); - // Dispatch DROP event. - this.dispatch_(e); +ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) { + var coordinates = []; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); } + return new ol.geom.Polygon(coordinates); }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -goog.provide('goog.Thenable'); - - /** - * Provides a more strict interface for Thenables in terms of - * http://promisesaplus.com for interop with {@see goog.Promise}. + * Create a multi-polygon from a TopoJSON geometry object. * - * @interface - * @extends {IThenable<TYPE>} - * @template TYPE + * @param {TopoJSONGeometry} object TopoJSON object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @return {ol.geom.MultiPolygon} Geometry. + * @private */ -goog.Thenable = function() {}; +ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) { + var coordinates = []; + var polyArray, ringCoords, j, jj; + var i, ii; + for (i = 0, ii = object.arcs.length; i < ii; ++i) { + // for each polygon + polyArray = object.arcs[i]; + ringCoords = []; + for (j = 0, jj = polyArray.length; j < jj; ++j) { + // for each ring + ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs); + } + coordinates[i] = ringCoords; + } + return new ol.geom.MultiPolygon(coordinates); +}; /** - * Adds callbacks that will operate on the result of the Thenable, returning a - * new child Promise. - * - * If the Thenable is fulfilled, the {@code onFulfilled} callback will be - * invoked with the fulfillment value as argument, and the child Promise will - * be fulfilled with the return value of the callback. If the callback throws - * an exception, the child Promise will be rejected with the thrown value - * instead. - * - * If the Thenable is rejected, the {@code onRejected} callback will be invoked - * with the rejection reason as argument, and the child Promise will be rejected - * with the return value of the callback or thrown value. - * - * @param {?(function(this:THIS, TYPE): - * (RESULT|IThenable<RESULT>|Thenable))=} opt_onFulfilled A - * function that will be invoked with the fulfillment value if the Promise - * is fullfilled. - * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will - * be invoked with the rejection reason if the Promise is rejected. - * @param {THIS=} opt_context An optional context object that will be the - * execution context for the callbacks. By default, functions are executed - * with the default this. - * @return {!goog.Promise<RESULT>} A new Promise that will receive the result - * of the fulfillment or rejection callback. - * @template RESULT,THIS + * @inheritDoc */ -goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected, - opt_context) {}; +ol.format.TopoJSON.prototype.getExtensions = function() { + return ol.format.TopoJSON.EXTENSIONS_; +}; /** - * An expando property to indicate that an object implements - * {@code goog.Thenable}. - * - * {@see addImplementation}. + * Create features from a TopoJSON GeometryCollection object. * - * @const - */ -goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable'; - - -/** - * Marks a given class (constructor) as an implementation of Thenable, so - * that we can query that fact at runtime. The class must have already - * implemented the interface. - * Exports a 'then' method on the constructor prototype, so that the objects - * also implement the extern {@see goog.Thenable} interface for interop with - * other Promise implementations. - * @param {function(new:goog.Thenable,...[?])} ctor The class constructor. The - * corresponding class must have already implemented the interface. + * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry + * object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Array of features. + * @private */ -goog.Thenable.addImplementation = function(ctor) { - goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then); - if (COMPILED) { - ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true; - } else { - // Avoids dictionary access in uncompiled mode. - ctor.prototype.$goog_Thenable = true; +ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function( + collection, arcs, scale, translate, opt_options) { + var geometries = collection.geometries; + var features = []; + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + features[i] = ol.format.TopoJSON.readFeatureFromGeometry_( + geometries[i], arcs, scale, translate, opt_options); } + return features; }; /** - * @param {*} object - * @return {boolean} Whether a given instance implements {@code goog.Thenable}. - * The class/superclass of the instance must call {@code addImplementation}. + * Create a feature from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON geometry object. + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @private */ -goog.Thenable.isImplementedBy = function(object) { - if (!object) { - return false; +ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs, + scale, translate, opt_options) { + var geometry; + var type = object.type; + var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type]; + goog.asserts.assert(goog.isDef(geometryReader), + 'geometryReader should be defined'); + if ((type === 'Point') || (type === 'MultiPoint')) { + geometry = geometryReader(object, scale, translate); + } else { + geometry = geometryReader(object, arcs); } - try { - if (COMPILED) { - return !!object[goog.Thenable.IMPLEMENTED_BY_PROP]; - } - return !!object.$goog_Thenable; - } catch (e) { - // Property access seems to be forbidden. - return false; + var feature = new ol.Feature(); + feature.setGeometry(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, opt_options))); + if (goog.isDef(object.id)) { + feature.setId(object.id); } -}; - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + if (goog.isDef(object.properties)) { + feature.setProperties(object.properties); + } + return feature; +}; + /** - * @fileoverview Simple notifiers for the Closure testing framework. + * Read all features from a TopoJSON source. * - * @author johnlenz@google.com (John Lenz) + * @function + * @param {Document|Node|Object|string} source Source. + * @return {Array.<ol.Feature>} Features. + * @api stable */ - -goog.provide('goog.testing.watchers'); - - -/** @private {!Array<function()>} */ -goog.testing.watchers.resetWatchers_ = []; +ol.format.TopoJSON.prototype.readFeatures; /** - * Fires clock reset watching functions. + * @inheritDoc */ -goog.testing.watchers.signalClockReset = function() { - var watchers = goog.testing.watchers.resetWatchers_; - for (var i = 0; i < watchers.length; i++) { - goog.testing.watchers.resetWatchers_[i](); +ol.format.TopoJSON.prototype.readFeaturesFromObject = function( + object, opt_options) { + if (object.type == 'Topology') { + var topoJSONTopology = /** @type {TopoJSONTopology} */ (object); + var transform, scale = null, translate = null; + if (goog.isDef(topoJSONTopology.transform)) { + transform = /** @type {TopoJSONTransform} */ + (topoJSONTopology.transform); + scale = transform.scale; + translate = transform.translate; + } + var arcs = topoJSONTopology.arcs; + if (goog.isDef(transform)) { + ol.format.TopoJSON.transformArcs_(arcs, scale, translate); + } + /** @type {Array.<ol.Feature>} */ + var features = []; + var topoJSONFeatures = goog.object.getValues(topoJSONTopology.objects); + var i, ii; + var feature; + for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) { + if (topoJSONFeatures[i].type === 'GeometryCollection') { + feature = /** @type {TopoJSONGeometryCollection} */ + (topoJSONFeatures[i]); + features.push.apply(features, + ol.format.TopoJSON.readFeaturesFromGeometryCollection_( + feature, arcs, scale, translate, opt_options)); + } else { + feature = /** @type {TopoJSONGeometry} */ + (topoJSONFeatures[i]); + features.push(ol.format.TopoJSON.readFeatureFromGeometry_( + feature, arcs, scale, translate, opt_options)); + } + } + return features; + } else { + return []; } }; /** - * Enqueues a function to be called when the clock used for setTimeout is reset. - * @param {function()} fn + * Apply a linear transform to array of arcs. The provided array of arcs is + * modified in place. + * + * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private */ -goog.testing.watchers.watchClockReset = function(fn) { - goog.testing.watchers.resetWatchers_.push(fn); +ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) { + var i, ii; + for (i = 0, ii = arcs.length; i < ii; ++i) { + ol.format.TopoJSON.transformArc_(arcs[i], scale, translate); + } }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -goog.provide('goog.async.run'); - -goog.require('goog.async.nextTick'); -goog.require('goog.async.throwException'); -goog.require('goog.testing.watchers'); - - /** - * Fires the provided callback just before the current callstack unwinds, or as - * soon as possible after the current JS execution context. - * @param {function(this:THIS)} callback - * @param {THIS=} opt_context Object to use as the "this value" when calling - * the provided function. - * @template THIS + * Apply a linear transform to an arc. The provided arc is modified in place. + * + * @param {Array.<ol.Coordinate>} arc Arc. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. + * @private */ -goog.async.run = function(callback, opt_context) { - if (!goog.async.run.schedule_) { - goog.async.run.initializeRunner_(); - } - if (!goog.async.run.workQueueScheduled_) { - // Nothing is currently scheduled, schedule it now. - goog.async.run.schedule_(); - goog.async.run.workQueueScheduled_ = true; +ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) { + var x = 0; + var y = 0; + var vertex; + var i, ii; + for (i = 0, ii = arc.length; i < ii; ++i) { + vertex = arc[i]; + x += vertex[0]; + y += vertex[1]; + vertex[0] = x; + vertex[1] = y; + ol.format.TopoJSON.transformVertex_(vertex, scale, translate); } - - goog.async.run.workQueue_.push( - new goog.async.run.WorkItem_(callback, opt_context)); }; /** - * Initializes the function to use to process the work queue. + * Apply a linear transform to a vertex. The provided vertex is modified in + * place. + * + * @param {ol.Coordinate} vertex Vertex. + * @param {Array.<number>} scale Scale for each dimension. + * @param {Array.<number>} translate Translation for each dimension. * @private */ -goog.async.run.initializeRunner_ = function() { - // If native Promises are available in the browser, just schedule the callback - // on a fulfilled promise, which is specified to be async, but as fast as - // possible. - if (goog.global.Promise && goog.global.Promise.resolve) { - var promise = goog.global.Promise.resolve(); - goog.async.run.schedule_ = function() { - promise.then(goog.async.run.processWorkQueue); - }; - } else { - goog.async.run.schedule_ = function() { - goog.async.nextTick(goog.async.run.processWorkQueue); - }; - } +ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) { + vertex[0] = vertex[0] * scale[0] + translate[0]; + vertex[1] = vertex[1] * scale[1] + translate[1]; }; /** - * Forces goog.async.run to use nextTick instead of Promise. + * Read the projection from a TopoJSON source. * - * This should only be done in unit tests. It's useful because MockClock - * replaces nextTick, but not the browser Promise implementation, so it allows - * Promise-based code to be tested with MockClock. + * @function + * @param {Document|Node|Object|string} object Source. + * @return {ol.proj.Projection} Projection. + * @api stable */ -goog.async.run.forceNextTick = function() { - goog.async.run.schedule_ = function() { - goog.async.nextTick(goog.async.run.processWorkQueue); - }; +ol.format.TopoJSON.prototype.readProjection = function(object) { + return this.defaultDataProjection; }; /** - * The function used to schedule work asynchronousely. - * @private {function()} + * @const + * @private + * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>} */ -goog.async.run.schedule_; +ol.format.TopoJSON.GEOMETRY_READERS_ = { + 'Point': ol.format.TopoJSON.readPointGeometry_, + 'LineString': ol.format.TopoJSON.readLineStringGeometry_, + 'Polygon': ol.format.TopoJSON.readPolygonGeometry_, + 'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_, + 'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_, + 'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_ +}; +goog.provide('ol.format.WFS'); -/** @private {boolean} */ -goog.async.run.workQueueScheduled_ = false; +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('ol.format.GML3'); +goog.require('ol.format.GMLBase'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.format.XSD'); +goog.require('ol.geom.Geometry'); +goog.require('ol.proj'); +goog.require('ol.xml'); -/** @private {!Array<!goog.async.run.WorkItem_>} */ -goog.async.run.workQueue_ = []; +/** + * @classdesc + * Feature format for reading and writing data in the WFS format. + * By default, supports WFS version 1.1.0. You can pass a GML format + * as option if you want to read a WFS that contains GML2 (WFS 1.0.0). + * Also see {@link ol.format.GMLBase} which is used by this format. + * + * @constructor + * @param {olx.format.WFSOptions=} opt_options + * Optional configuration object. + * @extends {ol.format.XMLFeature} + * @api stable + */ +ol.format.WFS = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; -if (goog.DEBUG) { /** - * Reset the event queue. * @private + * @type {Array.<string>|string|undefined} */ - goog.async.run.resetQueue_ = function() { - goog.async.run.workQueueScheduled_ = false; - goog.async.run.workQueue_ = []; - }; + this.featureType_ = options.featureType; - // If there is a clock implemenation in use for testing - // and it is reset, reset the queue. - goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_); -} + /** + * @private + * @type {Object.<string, string>|string|undefined} + */ + this.featureNS_ = options.featureNS; + /** + * @private + * @type {ol.format.GMLBase} + */ + this.gmlFormat_ = goog.isDef(options.gmlFormat) ? + options.gmlFormat : new ol.format.GML3(); -/** - * Run any pending goog.async.run work items. This function is not intended - * for general use, but for use by entry point handlers to run items ahead of - * goog.async.nextTick. - */ -goog.async.run.processWorkQueue = function() { - // NOTE: additional work queue items may be pushed while processing. - while (goog.async.run.workQueue_.length) { - // Don't let the work queue grow indefinitely. - var workItems = goog.async.run.workQueue_; - goog.async.run.workQueue_ = []; - for (var i = 0; i < workItems.length; i++) { - var workItem = workItems[i]; - try { - workItem.fn.call(workItem.scope); - } catch (e) { - goog.async.throwException(e); - } - } - } + /** + * @private + * @type {string} + */ + this.schemaLocation_ = goog.isDef(options.schemaLocation) ? + options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION; - // There are no more work items, reset the work queue. - goog.async.run.workQueueScheduled_ = false; + goog.base(this); }; - +goog.inherits(ol.format.WFS, ol.format.XMLFeature); /** - * @constructor - * @final - * @struct - * @private - * - * @param {function()} fn - * @param {Object|null|undefined} scope + * @const + * @type {string} */ -goog.async.run.WorkItem_ = function(fn, scope) { - /** @const */ this.fn = fn; - /** @const */ this.scope = scope; -}; - -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -goog.provide('goog.promise.Resolver'); - +ol.format.WFS.FEATURE_PREFIX = 'feature'; /** - * Resolver interface for promises. The resolver is a convenience interface that - * bundles the promise and its associated resolve and reject functions together, - * for cases where the resolver needs to be persisted internally. - * - * @interface - * @template TYPE + * @const + * @type {string} */ -goog.promise.Resolver = function() {}; +ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/'; /** - * The promise that created this resolver. - * @type {!goog.Promise<TYPE>} + * Number of features; bounds/extent. + * @typedef {{numberOfFeatures: number, + * bounds: ol.Extent}} + * @api stable */ -goog.promise.Resolver.prototype.promise; +ol.format.WFS.FeatureCollectionMetadata; /** - * Resolves this resolver with the specified value. - * @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)} + * Total deleted; total inserted; total updated; array of insert ids. + * @typedef {{totalDeleted: number, + * totalInserted: number, + * totalUpdated: number, + * insertIds: Array.<string>}} + * @api stable */ -goog.promise.Resolver.prototype.resolve; +ol.format.WFS.TransactionResponse; /** - * Rejects this resolver with the specified reason. - * @type {function(*): void} + * @const + * @type {string} */ -goog.promise.Resolver.prototype.reject; +ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' + + 'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd'; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -goog.provide('goog.Promise'); +/** + * Read all features from a WFS FeatureCollection. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable + */ +ol.format.WFS.prototype.readFeatures; -goog.require('goog.Thenable'); -goog.require('goog.asserts'); -goog.require('goog.async.run'); -goog.require('goog.async.throwException'); -goog.require('goog.debug.Error'); -goog.require('goog.promise.Resolver'); +/** + * @inheritDoc + */ +ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) { + var context = { + 'featureType': this.featureType_, + 'featureNS': this.featureNS_ + }; + goog.object.extend(context, this.getReadOptions(node, + goog.isDef(opt_options) ? opt_options : {})); + var objectStack = [context]; + this.gmlFormat_.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][ + 'featureMember'] = + ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal); + var features = ol.xml.pushParseAndPop([], + this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, + objectStack, this.gmlFormat_); + if (!goog.isDef(features)) { + features = []; + } + return features; +}; /** - * Promises provide a result that may be resolved asynchronously. A Promise may - * be resolved by being fulfilled or rejected with a value, which will be known - * as the fulfillment value or the rejection reason. Whether fulfilled or - * rejected, the Promise result is immutable once it is set. - * - * Promises may represent results of any type, including undefined. Rejection - * reasons are typically Errors, but may also be of any type. Closure Promises - * allow for optional type annotations that enforce that fulfillment values are - * of the appropriate types at compile time. - * - * The result of a Promise is accessible by calling {@code then} and registering - * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise - * resolves, the relevant callbacks are invoked with the fulfillment value or - * rejection reason as argument. Callbacks are always invoked in the order they - * were registered, even when additional {@code then} calls are made from inside - * another callback. A callback is always run asynchronously sometime after the - * scope containing the registering {@code then} invocation has returned. - * - * If a Promise is resolved with another Promise, the first Promise will block - * until the second is resolved, and then assumes the same result as the second - * Promise. This allows Promises to depend on the results of other Promises, - * linking together multiple asynchronous operations. - * - * This implementation is compatible with the Promises/A+ specification and - * passes that specification's conformance test suite. A Closure Promise may be - * resolved with a Promise instance (or sufficiently compatible Promise-like - * object) created by other Promise implementations. From the specification, - * Promise-like objects are known as "Thenables". - * - * @see http://promisesaplus.com/ + * Read transaction response of the source. * - * @param {function( - * this:RESOLVER_CONTEXT, - * function((TYPE|IThenable<TYPE>|Thenable)=), - * function(*)): void} resolver - * Initialization function that is invoked immediately with {@code resolve} - * and {@code reject} functions as arguments. The Promise is resolved or - * rejected with the first argument passed to either function. - * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the - * resolver function. If unspecified, the resolver function will be executed - * in the default scope. - * @constructor - * @struct - * @final - * @implements {goog.Thenable<TYPE>} - * @template TYPE,RESOLVER_CONTEXT + * @param {Document|Node|Object|string} source Source. + * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response. + * @api stable */ -goog.Promise = function(resolver, opt_context) { - /** - * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or - * BLOCKED. - * @private {goog.Promise.State_} - */ - this.state_ = goog.Promise.State_.PENDING; - - /** - * The resolved result of the Promise. Immutable once set with either a - * fulfillment value or rejection reason. - * @private {*} - */ - this.result_ = undefined; - - /** - * For Promises created by calling {@code then()}, the originating parent. - * @private {goog.Promise} - */ - this.parent_ = null; - - /** - * The list of {@code onFulfilled} and {@code onRejected} callbacks added to - * this Promise by calls to {@code then()}. - * @private {Array<goog.Promise.CallbackEntry_>} - */ - this.callbackEntries_ = null; +ol.format.WFS.prototype.readTransactionResponse = function(source) { + if (ol.xml.isDocument(source)) { + return this.readTransactionResponseFromDocument( + /** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readTransactionResponseFromNode(/** @type {Node} */ (source)); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readTransactionResponseFromDocument(doc); + } else { + goog.asserts.fail('Unknown source type'); + return undefined; + } +}; - /** - * Whether the Promise is in the queue of Promises to execute. - * @private {boolean} - */ - this.executing_ = false; - if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { - /** - * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater - * than 0 milliseconds. The ID is set when the Promise is rejected, and - * cleared only if an {@code onRejected} callback is invoked for the - * Promise (or one of its descendants) before the delay is exceeded. - * - * If the rejection is not handled before the timeout completes, the - * rejection reason is passed to the unhandled rejection handler. - * @private {number} - */ - this.unhandledRejectionId_ = 0; - } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { - /** - * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a - * boolean that is set if the Promise is rejected, and reset to false if an - * {@code onRejected} callback is invoked for the Promise (or one of its - * descendants). If the rejection is not handled before the next timestep, - * the rejection reason is passed to the unhandled rejection handler. - * @private {boolean} - */ - this.hadUnhandledRejection_ = false; +/** + * Read feature collection metadata of the source. + * + * @param {Document|Node|Object|string} source Source. + * @return {ol.format.WFS.FeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + * @api stable + */ +ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) { + if (ol.xml.isDocument(source)) { + return this.readFeatureCollectionMetadataFromDocument( + /** @type {Document} */ (source)); + } else if (ol.xml.isNode(source)) { + return this.readFeatureCollectionMetadataFromNode( + /** @type {Node} */ (source)); + } else if (goog.isString(source)) { + var doc = ol.xml.parse(source); + return this.readFeatureCollectionMetadataFromDocument(doc); + } else { + goog.asserts.fail('Unknown source type'); + return undefined; } +}; - if (goog.Promise.LONG_STACK_TRACES) { - /** - * A list of stack trace frames pointing to the locations where this Promise - * was created or had callbacks added to it. Saved to add additional context - * to stack traces when an exception is thrown. - * @private {!Array<string>} - */ - this.stack_ = []; - this.addStackTrace_(new Error('created')); - /** - * Index of the most recently executed stack frame entry. - * @private {number} - */ - this.currentStep_ = 0; +/** + * @param {Document} doc Document. + * @return {ol.format.WFS.FeatureCollectionMetadata|undefined} + * FeatureCollection metadata. + */ +ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = + function(doc) { + goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + return this.readFeatureCollectionMetadataFromNode(n); + } } + return undefined; +}; - try { - var self = this; - resolver.call( - opt_context, - function(value) { - self.resolve_(goog.Promise.State_.FULFILLED, value); - }, - function(reason) { - if (goog.DEBUG && - !(reason instanceof goog.Promise.CancellationError)) { - try { - // Promise was rejected. Step up one call frame to see why. - if (reason instanceof Error) { - throw reason; - } else { - throw new Error('Promise rejected.'); - } - } catch (e) { - // Only thrown so browser dev tools can catch rejections of - // promises when the option to break on caught exceptions is - // activated. - } - } - self.resolve_(goog.Promise.State_.REJECTED, reason); - }); - } catch (e) { - this.resolve_(goog.Promise.State_.REJECTED, e); + +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = { + 'http://www.opengis.net/gml': { + 'boundedBy': ol.xml.makeObjectPropertySetter( + ol.format.GMLBase.prototype.readGeometryElement, 'bounds') } }; /** - * @define {boolean} Whether traces of {@code then} calls should be included in - * exceptions thrown + * @param {Node} node Node. + * @return {ol.format.WFS.FeatureCollectionMetadata|undefined} + * FeatureCollection metadata. */ -goog.define('goog.Promise.LONG_STACK_TRACES', false); +ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'FeatureCollection', + 'localName should be FeatureCollection'); + var result = {}; + var value = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('numberOfFeatures')); + result['numberOfFeatures'] = value; + return ol.xml.pushParseAndPop( + /** @type {ol.format.WFS.FeatureCollectionMetadata} */ (result), + ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_); +}; /** - * @define {number} The delay in milliseconds before a rejected Promise's reason - * is passed to the rejection handler. By default, the rejection handler - * rethrows the rejection reason so that it appears in the developer console or - * {@code window.onerror} handler. - * - * Rejections are rethrown as quickly as possible by default. A negative value - * disables rejection handling entirely. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0); +ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'totalInserted': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'totalUpdated': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'totalDeleted': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + } +}; /** - * The possible internal states for a Promise. These states are not directly - * observable to external callers. - * @enum {number} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Transaction Summary. * @private */ -goog.Promise.State_ = { - /** The Promise is waiting for resolution. */ - PENDING: 0, +ol.format.WFS.readTransactionSummary_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack); +}; - /** The Promise is blocked waiting for the result of another Thenable. */ - BLOCKED: 1, - /** The Promise has been resolved with a fulfillment value. */ - FULFILLED: 2, +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.WFS.OGC_FID_PARSERS_ = { + 'http://www.opengis.net/ogc': { + 'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) { + return node.getAttribute('fid'); + }) + } +}; - /** The Promise has been resolved with a rejection reason. */ - REJECTED: 3 + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.WFS.fidParser_ = function(node, objectStack) { + ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack); }; /** - * Typedef for entries in the callback chain. Each call to {@code then}, - * {@code thenCatch}, or {@code thenAlways} creates an entry containing the - * functions that may be invoked once the Promise is resolved. - * - * @typedef {{ - * child: goog.Promise, - * onFulfilled: function(*), - * onRejected: function(*) - * }} + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -goog.Promise.CallbackEntry_; +ol.format.WFS.INSERT_RESULTS_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'Feature': ol.format.WFS.fidParser_ + } +}; /** - * @param {(TYPE|goog.Thenable<TYPE>|Thenable)=} opt_value - * @return {!goog.Promise<TYPE>} A new Promise that is immediately resolved - * with the given value. - * @template TYPE + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<string>|undefined} Insert results. + * @private */ -goog.Promise.resolve = function(opt_value) { - return new goog.Promise(function(resolve, reject) { - resolve(opt_value); - }); +ol.format.WFS.readInsertResults_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack); }; /** - * @param {*=} opt_reason - * @return {!goog.Promise} A new Promise that is immediately rejected with the - * given reason. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.Promise.reject = function(opt_reason) { - return new goog.Promise(function(resolve, reject) { - reject(opt_reason); - }); +ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = { + 'http://www.opengis.net/wfs': { + 'TransactionSummary': ol.xml.makeObjectPropertySetter( + ol.format.WFS.readTransactionSummary_, 'transactionSummary'), + 'InsertResults': ol.xml.makeObjectPropertySetter( + ol.format.WFS.readInsertResults_, 'insertIds') + } }; /** - * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises - * @return {!goog.Promise<TYPE>} A Promise that receives the result of the - * first Promise (or Promise-like) input to complete. - * @template TYPE + * @param {Document} doc Document. + * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response. */ -goog.Promise.race = function(promises) { - return new goog.Promise(function(resolve, reject) { - if (!promises.length) { - resolve(undefined); - } - for (var i = 0, promise; promise = promises[i]; i++) { - promise.then(resolve, reject); +ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) { + goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + return this.readTransactionResponseFromNode(n); } - }); + } + return undefined; }; /** - * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises - * @return {!goog.Promise<!Array<TYPE>>} A Promise that receives a list of - * every fulfilled value once every input Promise (or Promise-like) is - * successfully fulfilled, or is rejected by the first rejection result. - * @template TYPE + * @param {Node} node Node. + * @return {ol.format.WFS.TransactionResponse|undefined} Transaction response. */ -goog.Promise.all = function(promises) { - return new goog.Promise(function(resolve, reject) { - var toFulfill = promises.length; - var values = []; +ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'TransactionResponse', + 'localName should be TransactionResponse'); + return ol.xml.pushParseAndPop( + /** @type {ol.format.WFS.TransactionResponse} */({}), + ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []); +}; - if (!toFulfill) { - resolve(values); - return; - } - var onFulfill = function(index, value) { - toFulfill--; - values[index] = value; - if (toFulfill == 0) { - resolve(values); - } - }; +/** + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @private + */ +ol.format.WFS.QUERY_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode) + } +}; - var onReject = function(reason) { - reject(reason); - }; - for (var i = 0, promise; promise = promises[i]; i++) { - promise.then(goog.partial(onFulfill, i), onReject); - } - }); +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeFeature_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featureType = context['featureType']; + var featureNS = context['featureNS']; + var child = ol.xml.createElementNS(featureNS, featureType); + node.appendChild(child); + ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack); }; /** - * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises - * @return {!goog.Promise<TYPE>} A Promise that receives the value of the first - * input to be fulfilled, or is rejected with a list of every rejection - * reason if all inputs are rejected. - * @template TYPE + * @param {Node} node Node. + * @param {number|string} fid Feature identifier. + * @param {Array.<*>} objectStack Node stack. + * @private */ -goog.Promise.firstFulfilled = function(promises) { - return new goog.Promise(function(resolve, reject) { - var toReject = promises.length; - var reasons = []; +ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) { + var filter = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter'); + var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'FeatureId'); + filter.appendChild(child); + child.setAttribute('fid', fid); + node.appendChild(filter); +}; - if (!toReject) { - resolve(undefined); - return; - } - var onFulfill = function(value) { - resolve(value); - }; +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeDelete_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featureType = context['featureType']; + var featurePrefix = context['featurePrefix']; + featurePrefix = goog.isDef(featurePrefix) ? featurePrefix : + ol.format.WFS.FEATURE_PREFIX; + var featureNS = context['featureNS']; + node.setAttribute('typeName', featurePrefix + ':' + featureType); + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + var fid = feature.getId(); + if (goog.isDef(fid)) { + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } +}; - var onReject = function(index, reason) { - toReject--; - reasons[index] = reason; - if (toReject == 0) { - reject(reasons); - } - }; - for (var i = 0, promise; promise = promises[i]; i++) { - promise.then(onFulfill, goog.partial(onReject, i)); +/** + * @param {Node} node Node. + * @param {ol.Feature} feature Feature. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featureType = context['featureType']; + var featurePrefix = context['featurePrefix']; + featurePrefix = goog.isDef(featurePrefix) ? featurePrefix : + ol.format.WFS.FEATURE_PREFIX; + var featureNS = context['featureNS']; + node.setAttribute('typeName', featurePrefix + ':' + featureType); + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + var fid = feature.getId(); + if (goog.isDef(fid)) { + var keys = feature.getKeys(); + var values = []; + for (var i = 0, ii = keys.length; i < ii; i++) { + var value = feature.get(keys[i]); + if (goog.isDef(value)) { + values.push({name: keys[i], value: value}); + } } - }); + ol.xml.pushSerializeAndPop({node: node, srsName: + context['srsName']}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Property'), values, + objectStack); + ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack); + } }; /** - * @return {!goog.promise.Resolver<TYPE>} Resolver wrapping the promise and its - * resolve / reject functions. Resolving or rejecting the resolver - * resolves or rejects the promise. - * @template TYPE + * @param {Node} node Node. + * @param {Object} pair Property name and value. + * @param {Array.<*>} objectStack Node stack. + * @private */ -goog.Promise.withResolver = function() { - var resolve, reject; - var promise = new goog.Promise(function(rs, rj) { - resolve = rs; - reject = rj; - }); - return new goog.Promise.Resolver_(promise, resolve, reject); +ol.format.WFS.writeProperty_ = function(node, pair, objectStack) { + var name = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Name'); + node.appendChild(name); + ol.format.XSD.writeStringTextNode(name, pair.name); + if (goog.isDefAndNotNull(pair.value)) { + var value = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Value'); + node.appendChild(value); + if (pair.value instanceof ol.geom.Geometry) { + ol.format.GML3.prototype.writeGeometryElement(value, + pair.value, objectStack); + } else { + ol.format.XSD.writeStringTextNode(value, pair.value); + } + } }; /** - * Adds callbacks that will operate on the result of the Promise, returning a - * new child Promise. - * - * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked - * with the fulfillment value as argument, and the child Promise will be - * fulfilled with the return value of the callback. If the callback throws an - * exception, the child Promise will be rejected with the thrown value instead. - * - * If the Promise is rejected, the {@code onRejected} callback will be invoked - * with the rejection reason as argument, and the child Promise will be resolved - * with the return value or rejected with the thrown value of the callback. - * - * @override + * @param {Node} node Node. + * @param {{vendorId: string, safeToIgnore: boolean, value: string}} + * nativeElement The native element. + * @param {Array.<*>} objectStack Node stack. + * @private */ -goog.Promise.prototype.then = function( - opt_onFulfilled, opt_onRejected, opt_context) { - - if (opt_onFulfilled != null) { - goog.asserts.assertFunction(opt_onFulfilled, - 'opt_onFulfilled should be a function.'); +ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) { + if (goog.isDef(nativeElement.vendorId)) { + node.setAttribute('vendorId', nativeElement.vendorId); } - if (opt_onRejected != null) { - goog.asserts.assertFunction(opt_onRejected, - 'opt_onRejected should be a function. Did you pass opt_context ' + - 'as the second argument instead of the third?'); + if (goog.isDef(nativeElement.safeToIgnore)) { + node.setAttribute('safeToIgnore', nativeElement.safeToIgnore); } - - if (goog.Promise.LONG_STACK_TRACES) { - this.addStackTrace_(new Error('then')); + if (goog.isDef(nativeElement.value)) { + ol.format.XSD.writeStringTextNode(node, nativeElement.value); } - - return this.addChildPromise_( - goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null, - goog.isFunction(opt_onRejected) ? opt_onRejected : null, - opt_context); }; -goog.Thenable.addImplementation(goog.Promise); /** - * Adds a callback that will be invoked whether the Promise is fulfilled or - * rejected. The callback receives no argument, and no new child Promise is - * created. This is useful for ensuring that cleanup takes place after certain - * asynchronous operations. Callbacks added with {@code thenAlways} will be - * executed in the same order with other calls to {@code then}, - * {@code thenAlways}, or {@code thenCatch}. - * - * Since it does not produce a new child Promise, cancellation propagation is - * not prevented by adding callbacks with {@code thenAlways}. A Promise that has - * a cleanup handler added with {@code thenAlways} will be canceled if all of - * its children created by {@code then} (or {@code thenCatch}) are canceled. - * Additionally, since any rejections are not passed to the callback, it does - * not stop the unhandled rejection handler from running. - * - * @param {function(this:THIS): void} onResolved A function that will be invoked - * when the Promise is resolved. - * @param {THIS=} opt_context An optional context object that will be the - * execution context for the callbacks. By default, functions are executed - * in the global scope. - * @return {!goog.Promise<TYPE>} This Promise, for chaining additional calls. - * @template THIS + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} + * @private */ -goog.Promise.prototype.thenAlways = function(onResolved, opt_context) { - if (goog.Promise.LONG_STACK_TRACES) { - this.addStackTrace_(new Error('thenAlways')); +ol.format.WFS.TRANSACTION_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_), + 'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_), + 'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_), + 'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_), + 'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_) } +}; - var callback = function() { - try { - // Ensure that no arguments are passed to onResolved. - onResolved.call(opt_context); - } catch (err) { - goog.Promise.handleRejection_.call(null, err); - } - }; - this.addCallbackEntry_({ - child: null, - onRejected: callback, - onFulfilled: callback - }); - return this; +/** + * @param {Node} node Node. + * @param {string} featureType Feature type. + * @param {Array.<*>} objectStack Node stack. + * @private + */ +ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var featurePrefix = context['featurePrefix']; + var featureNS = context['featureNS']; + var propertyNames = context['propertyNames']; + var srsName = context['srsName']; + var prefix = goog.isDef(featurePrefix) ? featurePrefix + ':' : ''; + node.setAttribute('typeName', prefix + featureType); + if (goog.isDef(srsName)) { + node.setAttribute('srsName', srsName); + } + if (goog.isDef(featureNS)) { + ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix, + featureNS); + } + var item = goog.object.clone(context); + item.node = node; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.QUERY_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames, + objectStack); + var bbox = context['bbox']; + if (goog.isDef(bbox)) { + var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter'); + ol.format.WFS.writeOgcBBOX_(child, bbox, objectStack); + node.appendChild(child); + } }; /** - * Adds a callback that will be invoked only if the Promise is rejected. This - * is equivalent to {@code then(null, onRejected)}. - * - * @param {!function(this:THIS, *): *} onRejected A function that will be - * invoked with the rejection reason if the Promise is rejected. - * @param {THIS=} opt_context An optional context object that will be the - * execution context for the callbacks. By default, functions are executed - * in the global scope. - * @return {!goog.Promise} A new Promise that will receive the result of the - * callback. - * @template THIS + * @param {Node} node Node. + * @param {string} value PropertyName value. + * @param {Array.<*>} objectStack Node stack. + * @private */ -goog.Promise.prototype.thenCatch = function(onRejected, opt_context) { - if (goog.Promise.LONG_STACK_TRACES) { - this.addStackTrace_(new Error('thenCatch')); - } - return this.addChildPromise_(null, onRejected, opt_context); +ol.format.WFS.writeOgcPropertyName_ = function(node, value, objectStack) { + var property = ol.xml.createElementNS('http://www.opengis.net/ogc', + 'PropertyName'); + ol.format.XSD.writeStringTextNode(property, value); + node.appendChild(property); }; /** - * Cancels the Promise if it is still pending by rejecting it with a cancel - * Error. No action is performed if the Promise is already resolved. - * - * All child Promises of the canceled Promise will be rejected with the same - * cancel error, as with normal Promise rejection. If the Promise to be canceled - * is the only child of a pending Promise, the parent Promise will also be - * canceled. Cancellation may propagate upward through multiple generations. - * - * @param {string=} opt_message An optional debugging message for describing the - * cancellation reason. + * @param {Node} node Node. + * @param {ol.Extent} bbox Bounding box. + * @param {Array.<*>} objectStack Node stack. + * @private */ -goog.Promise.prototype.cancel = function(opt_message) { - if (this.state_ == goog.Promise.State_.PENDING) { - goog.async.run(function() { - var err = new goog.Promise.CancellationError(opt_message); - this.cancelInternal_(err); - }, this); - } +ol.format.WFS.writeOgcBBOX_ = function(node, bbox, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var geometryName = context['geometryName']; + var bboxNode = ol.xml.createElementNS('http://www.opengis.net/ogc', 'BBOX'); + node.appendChild(bboxNode); + ol.format.WFS.writeOgcPropertyName_(bboxNode, geometryName, objectStack); + ol.format.GML3.prototype.writeGeometryElement(bboxNode, bbox, objectStack); }; /** - * Cancels this Promise with the given error. - * - * @param {!Error} err The cancellation error. + * @type {Object.<string, Object.<string, ol.xml.Serializer>>} * @private */ -goog.Promise.prototype.cancelInternal_ = function(err) { - if (this.state_ == goog.Promise.State_.PENDING) { - if (this.parent_) { - // Cancel the Promise and remove it from the parent's child list. - this.parent_.cancelChild_(this, err); - } else { - this.resolve_(goog.Promise.State_.REJECTED, err); - } +ol.format.WFS.GETFEATURE_SERIALIZERS_ = { + 'http://www.opengis.net/wfs': { + 'Query': ol.xml.makeChildAppender( + ol.format.WFS.writeQuery_) } }; /** - * Cancels a child Promise from the list of callback entries. If the Promise has - * not already been resolved, reject it with a cancel error. If there are no - * other children in the list of callback entries, propagate the cancellation - * by canceling this Promise as well. - * - * @param {!goog.Promise} childPromise The Promise to cancel. - * @param {!Error} err The cancel error to use for rejecting the Promise. + * @param {Node} node Node. + * @param {Array.<{string}>} featureTypes Feature types. + * @param {Array.<*>} objectStack Node stack. * @private */ -goog.Promise.prototype.cancelChild_ = function(childPromise, err) { - if (!this.callbackEntries_) { - return; - } - var childCount = 0; - var childIndex = -1; +ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) { + var context = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(context), 'context should be an Object'); + var item = goog.object.clone(context); + item.node = node; + ol.xml.pushSerializeAndPop(item, + ol.format.WFS.GETFEATURE_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Query'), featureTypes, + objectStack); +}; - // Find the callback entry for the childPromise, and count whether there are - // additional child Promises. - for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) { - var child = entry.child; - if (child) { - childCount++; - if (child == childPromise) { - childIndex = i; - } - if (childIndex >= 0 && childCount > 1) { - break; - } - } - } - // If the child Promise was the only child, cancel this Promise as well. - // Otherwise, reject only the child Promise with the cancel error. - if (childIndex >= 0) { - if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) { - this.cancelInternal_(err); - } else { - var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0]; - this.executeCallback_( - callbackEntry, goog.Promise.State_.REJECTED, err); +/** + * Encode format as WFS `GetFeature` and return the Node. + * + * @param {olx.format.WFSWriteGetFeatureOptions} options Options. + * @return {Node} Result. + * @api stable + */ +ol.format.WFS.prototype.writeGetFeature = function(options) { + var node = ol.xml.createElementNS('http://www.opengis.net/wfs', + 'GetFeature'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + if (goog.isDef(options)) { + if (goog.isDef(options.handle)) { + node.setAttribute('handle', options.handle); + } + if (goog.isDef(options.outputFormat)) { + node.setAttribute('outputFormat', options.outputFormat); + } + if (goog.isDef(options.maxFeatures)) { + node.setAttribute('maxFeatures', options.maxFeatures); + } + if (goog.isDef(options.resultType)) { + node.setAttribute('resultType', options.resultType); + } + if (goog.isDef(options.startIndex)) { + node.setAttribute('startIndex', options.startIndex); + } + if (goog.isDef(options.count)) { + node.setAttribute('count', options.count); } } + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + var context = { + node: node, + srsName: options.srsName, + featureNS: goog.isDef(options.featureNS) ? + options.featureNS : this.featureNS_, + featurePrefix: options.featurePrefix, + geometryName: options.geometryName, + bbox: options.bbox, + propertyNames: goog.isDef(options.propertyNames) ? + options.propertyNames : [] + }; + goog.asserts.assert(goog.isArray(options.featureTypes), + 'options.featureTypes should be an array'); + ol.format.WFS.writeGetFeature_(node, options.featureTypes, [context]); + return node; }; /** - * Adds a callback entry to the current Promise, and schedules callback - * execution if the Promise has already been resolved. + * Encode format as WFS `Transaction` and return the Node. * - * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing - * {@code onFulfilled} and {@code onRejected} callbacks to execute after - * the Promise is resolved. - * @private + * @param {Array.<ol.Feature>} inserts The features to insert. + * @param {Array.<ol.Feature>} updates The features to update. + * @param {Array.<ol.Feature>} deletes The features to delete. + * @param {olx.format.WFSWriteTransactionOptions} options Write options. + * @return {Node} Result. + * @api stable */ -goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) { - if ((!this.callbackEntries_ || !this.callbackEntries_.length) && - (this.state_ == goog.Promise.State_.FULFILLED || - this.state_ == goog.Promise.State_.REJECTED)) { - this.scheduleCallbacks_(); +ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes, + options) { + var objectStack = []; + var node = ol.xml.createElementNS('http://www.opengis.net/wfs', + 'Transaction'); + node.setAttribute('service', 'WFS'); + node.setAttribute('version', '1.1.0'); + var baseObj, obj; + if (goog.isDef(options)) { + baseObj = goog.isDef(options.gmlOptions) ? options.gmlOptions : {}; + if (goog.isDef(options.handle)) { + node.setAttribute('handle', options.handle); + } } - if (!this.callbackEntries_) { - this.callbackEntries_ = []; + ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation', this.schemaLocation_); + if (goog.isDefAndNotNull(inserts)) { + obj = {node: node, featureNS: options.featureNS, + featureType: options.featureType, featurePrefix: options.featurePrefix}; + goog.object.extend(obj, baseObj); + ol.xml.pushSerializeAndPop(obj, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Insert'), inserts, + objectStack); } - this.callbackEntries_.push(callbackEntry); + if (goog.isDefAndNotNull(updates)) { + obj = {node: node, featureNS: options.featureNS, + featureType: options.featureType, featurePrefix: options.featurePrefix}; + goog.object.extend(obj, baseObj); + ol.xml.pushSerializeAndPop(obj, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Update'), updates, + objectStack); + } + if (goog.isDefAndNotNull(deletes)) { + ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, + featureType: options.featureType, featurePrefix: options.featurePrefix}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Delete'), deletes, + objectStack); + } + if (goog.isDef(options.nativeElements)) { + ol.xml.pushSerializeAndPop({node: node, featureNS: options.featureNS, + featureType: options.featureType, featurePrefix: options.featurePrefix}, + ol.format.WFS.TRANSACTION_SERIALIZERS_, + ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements, + objectStack); + } + return node; }; /** - * Creates a child Promise and adds it to the callback entry list. The result of - * the child Promise is determined by the state of the parent Promise and the - * result of the {@code onFulfilled} or {@code onRejected} callbacks as - * specified in the Promise resolution procedure. - * - * @see http://promisesaplus.com/#the__method + * Read the projection from a WFS source. * - * @param {?function(this:THIS, TYPE): - * (RESULT|goog.Promise<RESULT>|Thenable)} onFulfilled A callback that - * will be invoked if the Promise is fullfilled, or null. - * @param {?function(this:THIS, *): *} onRejected A callback that will be - * invoked if the Promise is rejected, or null. - * @param {THIS=} opt_context An optional execution context for the callbacks. - * in the default calling context. - * @return {!goog.Promise} The child Promise. - * @template RESULT,THIS - * @private + * @function + * @param {Document|Node|Object|string} source Source. + * @return {?ol.proj.Projection} Projection. + * @api stable */ -goog.Promise.prototype.addChildPromise_ = function( - onFulfilled, onRejected, opt_context) { - - var callbackEntry = { - child: null, - onFulfilled: null, - onRejected: null - }; - - callbackEntry.child = new goog.Promise(function(resolve, reject) { - // Invoke onFulfilled, or resolve with the parent's value if absent. - callbackEntry.onFulfilled = onFulfilled ? function(value) { - try { - var result = onFulfilled.call(opt_context, value); - resolve(result); - } catch (err) { - reject(err); - } - } : resolve; - - // Invoke onRejected, or reject with the parent's reason if absent. - callbackEntry.onRejected = onRejected ? function(reason) { - try { - var result = onRejected.call(opt_context, reason); - if (!goog.isDef(result) && - reason instanceof goog.Promise.CancellationError) { - // Propagate cancellation to children if no other result is returned. - reject(reason); - } else { - resolve(result); - } - } catch (err) { - reject(err); - } - } : reject; - }); - - callbackEntry.child.parent_ = this; - this.addCallbackEntry_( - /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry)); - return callbackEntry.child; -}; +ol.format.WFS.prototype.readProjection; /** - * Unblocks the Promise and fulfills it with the given value. - * - * @param {TYPE} value - * @private + * @inheritDoc */ -goog.Promise.prototype.unblockAndFulfill_ = function(value) { - goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED); - this.state_ = goog.Promise.State_.PENDING; - this.resolve_(goog.Promise.State_.FULFILLED, value); +ol.format.WFS.prototype.readProjectionFromDocument = function(doc) { + goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT, + 'doc.nodeType should be a DOCUMENT'); + for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + return this.readProjectionFromNode(n); + } + } + return null; }; /** - * Unblocks the Promise and rejects it with the given rejection reason. - * - * @param {*} reason - * @private + * @inheritDoc */ -goog.Promise.prototype.unblockAndReject_ = function(reason) { - goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED); - this.state_ = goog.Promise.State_.PENDING; - this.resolve_(goog.Promise.State_.REJECTED, reason); +ol.format.WFS.prototype.readProjectionFromNode = function(node) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'FeatureCollection', + 'localName should be FeatureCollection'); + + if (goog.isDefAndNotNull(node.firstElementChild) && + goog.isDefAndNotNull(node.firstElementChild.firstElementChild)) { + node = node.firstElementChild.firstElementChild; + for (var n = node.firstElementChild; !goog.isNull(n); + n = n.nextElementSibling) { + if (!(n.childNodes.length === 0 || + (n.childNodes.length === 1 && + n.firstChild.nodeType === 3))) { + var objectStack = [{}]; + this.gmlFormat_.readGeometryElement(n, objectStack); + return ol.proj.get(objectStack.pop().srsName); + } + } + } + + return null; }; +goog.provide('ol.format.WKT'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.Feature'); +goog.require('ol.format.Feature'); +goog.require('ol.format.TextFeature'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); + + /** - * Attempts to resolve a Promise with a given resolution state and value. This - * is a no-op if the given Promise has already been resolved. - * - * If the given result is a Thenable (such as another Promise), the Promise will - * be resolved with the same state and result as the Thenable once it is itself - * resolved. - * - * If the given result is not a Thenable, the Promise will be fulfilled or - * rejected with that result based on the given state. - * - * @see http://promisesaplus.com/#the_promise_resolution_procedure + * @classdesc + * Geometry format for reading and writing data in the `WellKnownText` (WKT) + * format. * - * @param {goog.Promise.State_} state - * @param {*} x The result to apply to the Promise. - * @private + * @constructor + * @extends {ol.format.TextFeature} + * @param {olx.format.WKTOptions=} opt_options Options. + * @api stable */ -goog.Promise.prototype.resolve_ = function(state, x) { - if (this.state_ != goog.Promise.State_.PENDING) { - return; - } - - if (this == x) { - state = goog.Promise.State_.REJECTED; - x = new TypeError('Promise cannot resolve to itself'); +ol.format.WKT = function(opt_options) { - } else if (goog.Thenable.isImplementedBy(x)) { - x = /** @type {!goog.Thenable} */ (x); - this.state_ = goog.Promise.State_.BLOCKED; - x.then(this.unblockAndFulfill_, this.unblockAndReject_, this); - return; + var options = goog.isDef(opt_options) ? opt_options : {}; - } else if (goog.isObject(x)) { - try { - var then = x['then']; - if (goog.isFunction(then)) { - this.tryThen_(x, then); - return; - } - } catch (e) { - state = goog.Promise.State_.REJECTED; - x = e; - } - } + goog.base(this); - this.result_ = x; - this.state_ = state; - this.scheduleCallbacks_(); + /** + * Split GeometryCollection into multiple features. + * @type {boolean} + * @private + */ + this.splitCollection_ = goog.isDef(options.splitCollection) ? + options.splitCollection : false; - if (state == goog.Promise.State_.REJECTED && - !(x instanceof goog.Promise.CancellationError)) { - goog.Promise.addUnhandledRejection_(this, x); - } }; +goog.inherits(ol.format.WKT, ol.format.TextFeature); /** - * Attempts to call the {@code then} method on an object in the hopes that it is - * a Promise-compatible instance. This allows interoperation between different - * Promise implementations, however a non-compliant object may cause a Promise - * to hang indefinitely. If the {@code then} method throws an exception, the - * dependent Promise will be rejected with the thrown value. - * - * @see http://promisesaplus.com/#point-70 - * - * @param {Thenable} thenable An object with a {@code then} method that may be - * compatible with the Promise/A+ specification. - * @param {!Function} then The {@code then} method of the Thenable object. - * @private + * @const + * @type {string} */ -goog.Promise.prototype.tryThen_ = function(thenable, then) { - this.state_ = goog.Promise.State_.BLOCKED; - var promise = this; - var called = false; - - var resolve = function(value) { - if (!called) { - called = true; - promise.unblockAndFulfill_(value); - } - }; +ol.format.WKT.EMPTY = 'EMPTY'; - var reject = function(reason) { - if (!called) { - called = true; - promise.unblockAndReject_(reason); - } - }; - try { - then.call(thenable, resolve, reject); - } catch (e) { - reject(e); +/** + * @param {ol.geom.Point} geom Point geometry. + * @return {string} Coordinates part of Point as WKT. + * @private + */ +ol.format.WKT.encodePointGeometry_ = function(geom) { + var coordinates = geom.getCoordinates(); + if (goog.array.isEmpty(coordinates)) { + return ''; } + return coordinates[0] + ' ' + coordinates[1]; }; /** - * Executes the pending callbacks of a resolved Promise after a timeout. - * - * Section 2.2.4 of the Promises/A+ specification requires that Promise - * callbacks must only be invoked from a call stack that only contains Promise - * implementation code, which we accomplish by invoking callback execution after - * a timeout. If {@code startExecution_} is called multiple times for the same - * Promise, the callback chain will be evaluated only once. Additional callbacks - * may be added during the evaluation phase, and will be executed in the same - * event loop. - * - * All Promises added to the waiting list during the same browser event loop - * will be executed in one batch to avoid using a separate timeout per Promise. - * + * @param {ol.geom.MultiPoint} geom MultiPoint geometry. + * @return {string} Coordinates part of MultiPoint as WKT. * @private */ -goog.Promise.prototype.scheduleCallbacks_ = function() { - if (!this.executing_) { - this.executing_ = true; - goog.async.run(this.executeCallbacks_, this); +ol.format.WKT.encodeMultiPointGeometry_ = function(geom) { + var array = []; + var components = geom.getPoints(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodePointGeometry_(components[i]) + ')'); } + return array.join(','); }; /** - * Executes all pending callbacks for this Promise. - * + * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry. + * @return {string} Coordinates part of GeometryCollection as WKT. * @private */ -goog.Promise.prototype.executeCallbacks_ = function() { - while (this.callbackEntries_ && this.callbackEntries_.length) { - var entries = this.callbackEntries_; - this.callbackEntries_ = []; - - for (var i = 0; i < entries.length; i++) { - if (goog.Promise.LONG_STACK_TRACES) { - this.currentStep_++; - } - this.executeCallback_(entries[i], this.state_, this.result_); - } +ol.format.WKT.encodeGeometryCollectionGeometry_ = function(geom) { + var array = []; + var geoms = geom.getGeometries(); + for (var i = 0, ii = geoms.length; i < ii; ++i) { + array.push(ol.format.WKT.encode_(geoms[i])); } - this.executing_ = false; + return array.join(','); }; /** - * Executes a pending callback for this Promise. Invokes an {@code onFulfilled} - * or {@code onRejected} callback based on the resolved state of the Promise. - * - * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the - * onFulfilled and/or onRejected callbacks for this step. - * @param {goog.Promise.State_} state The resolution status of the Promise, - * either FULFILLED or REJECTED. - * @param {*} result The resolved result of the Promise. + * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry. + * @return {string} Coordinates part of LineString as WKT. * @private */ -goog.Promise.prototype.executeCallback_ = function( - callbackEntry, state, result) { - if (state == goog.Promise.State_.FULFILLED) { - callbackEntry.onFulfilled(result); - } else { - if (callbackEntry.child) { - this.removeUnhandledRejection_(); - } - callbackEntry.onRejected(result); +ol.format.WKT.encodeLineStringGeometry_ = function(geom) { + var coordinates = geom.getCoordinates(); + var array = []; + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + array.push(coordinates[i][0] + ' ' + coordinates[i][1]); } + return array.join(','); }; /** - * Records a stack trace entry for functions that call {@code then} or the - * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}. - * - * @param {!Error} err An Error object created by the calling function for - * providing a stack trace. + * @param {ol.geom.MultiLineString} geom MultiLineString geometry. + * @return {string} Coordinates part of MultiLineString as WKT. * @private */ -goog.Promise.prototype.addStackTrace_ = function(err) { - if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) { - // Extract the third line of the stack trace, which is the entry for the - // user function that called into Promise code. - var trace = err.stack.split('\n', 4)[3]; - var message = err.message; - - // Pad the message to align the traces. - message += Array(11 - message.length).join(' '); - this.stack_.push(message + trace); +ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) { + var array = []; + var components = geom.getLineStrings(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodeLineStringGeometry_( + components[i]) + ')'); } + return array.join(','); }; /** - * Adds extra stack trace information to an exception for the list of - * asynchronous {@code then} calls that have been run for this Promise. Stack - * trace information is recorded in {@see #addStackTrace_}, and appended to - * rethrown errors when {@code LONG_STACK_TRACES} is enabled. - * - * @param {*} err An unhandled exception captured during callback execution. + * @param {ol.geom.Polygon} geom Polygon geometry. + * @return {string} Coordinates part of Polygon as WKT. * @private */ -goog.Promise.prototype.appendLongStack_ = function(err) { - if (goog.Promise.LONG_STACK_TRACES && - err && goog.isString(err.stack) && this.stack_.length) { - var longTrace = ['Promise trace:']; - - for (var promise = this; promise; promise = promise.parent_) { - for (var i = this.currentStep_; i >= 0; i--) { - longTrace.push(promise.stack_[i]); - } - longTrace.push('Value: ' + - '[' + (promise.state_ == goog.Promise.State_.REJECTED ? - 'REJECTED' : 'FULFILLED') + '] ' + - '<' + String(promise.result_) + '>'); - } - err.stack += '\n\n' + longTrace.join('\n'); +ol.format.WKT.encodePolygonGeometry_ = function(geom) { + var array = []; + var rings = geom.getLinearRings(); + for (var i = 0, ii = rings.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodeLineStringGeometry_( + rings[i]) + ')'); } + return array.join(','); }; /** - * Marks this rejected Promise as having being handled. Also marks any parent - * Promises in the rejected state as handled. The rejection handler will no - * longer be invoked for this Promise (if it has not been called already). - * + * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry. + * @return {string} Coordinates part of MultiPolygon as WKT. * @private */ -goog.Promise.prototype.removeUnhandledRejection_ = function() { - if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { - for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) { - goog.global.clearTimeout(p.unhandledRejectionId_); - p.unhandledRejectionId_ = 0; - } - } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { - for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) { - p.hadUnhandledRejection_ = false; - } +ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) { + var array = []; + var components = geom.getPolygons(); + for (var i = 0, ii = components.length; i < ii; ++i) { + array.push('(' + ol.format.WKT.encodePolygonGeometry_( + components[i]) + ')'); } + return array.join(','); }; /** - * Marks this rejected Promise as unhandled. If no {@code onRejected} callback - * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY} - * expires, the reason will be passed to the unhandled rejection handler. The - * handler typically rethrows the rejection reason so that it becomes visible in - * the developer console. - * - * @param {!goog.Promise} promise The rejected Promise. - * @param {*} reason The Promise rejection reason. + * Encode a geometry as WKT. + * @param {ol.geom.Geometry} geom The geometry to encode. + * @return {string} WKT string for the geometry. * @private */ -goog.Promise.addUnhandledRejection_ = function(promise, reason) { - if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) { - promise.unhandledRejectionId_ = goog.global.setTimeout(function() { - promise.appendLongStack_(reason); - goog.Promise.handleRejection_.call(null, reason); - }, goog.Promise.UNHANDLED_REJECTION_DELAY); - - } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) { - promise.hadUnhandledRejection_ = true; - goog.async.run(function() { - if (promise.hadUnhandledRejection_) { - promise.appendLongStack_(reason); - goog.Promise.handleRejection_.call(null, reason); - } - }); +ol.format.WKT.encode_ = function(geom) { + var type = geom.getType(); + var geometryEncoder = ol.format.WKT.GeometryEncoder_[type]; + goog.asserts.assert(goog.isDef(geometryEncoder), + 'geometryEncoder should be defined'); + var enc = geometryEncoder(geom); + type = type.toUpperCase(); + if (enc.length === 0) { + return type + ' ' + ol.format.WKT.EMPTY; } + return type + '(' + enc + ')'; }; /** - * A method that is invoked with the rejection reasons for Promises that are - * rejected but have no {@code onRejected} callbacks registered yet. - * @type {function(*)} + * @const + * @type {Object.<string, function(ol.geom.Geometry): string>} * @private */ -goog.Promise.handleRejection_ = goog.async.throwException; +ol.format.WKT.GeometryEncoder_ = { + 'Point': ol.format.WKT.encodePointGeometry_, + 'LineString': ol.format.WKT.encodeLineStringGeometry_, + 'Polygon': ol.format.WKT.encodePolygonGeometry_, + 'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_, + 'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_, + 'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_, + 'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_ +}; /** - * Sets a handler that will be called with reasons from unhandled rejected - * Promises. If the rejected Promise (or one of its descendants) has an - * {@code onRejected} callback registered, the rejection will be considered - * handled, and the rejection handler will not be called. - * - * By default, unhandled rejections are rethrown so that the error may be - * captured by the developer console or a {@code window.onerror} handler. - * - * @param {function(*)} handler A function that will be called with reasons from - * rejected Promises. Defaults to {@code goog.async.throwException}. + * Parse a WKT string. + * @param {string} wkt WKT string. + * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined} + * The geometry created. + * @private */ -goog.Promise.setUnhandledRejectionHandler = function(handler) { - goog.Promise.handleRejection_ = handler; +ol.format.WKT.prototype.parse_ = function(wkt) { + var lexer = new ol.format.WKT.Lexer(wkt); + var parser = new ol.format.WKT.Parser(lexer); + return parser.parse(); }; - /** - * Error used as a rejection reason for canceled Promises. + * Read a feature from a WKT source. * - * @param {string=} opt_message - * @constructor - * @extends {goog.debug.Error} - * @final + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + * @api stable */ -goog.Promise.CancellationError = function(opt_message) { - goog.Promise.CancellationError.base(this, 'constructor', opt_message); -}; -goog.inherits(goog.Promise.CancellationError, goog.debug.Error); - +ol.format.WKT.prototype.readFeature; -/** @override */ -goog.Promise.CancellationError.prototype.name = 'cancel'; +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) { + var geom = this.readGeometryFromText(text, opt_options); + if (goog.isDef(geom)) { + var feature = new ol.Feature(); + feature.setGeometry(geom); + return feature; + } + return null; +}; /** - * Internal implementation of the resolver interface. + * Read all features from a WKT source. * - * @param {!goog.Promise<TYPE>} promise - * @param {function((TYPE|goog.Promise<TYPE>|Thenable)=)} resolve - * @param {function(*): void} reject - * @implements {goog.promise.Resolver<TYPE>} - * @final @struct - * @constructor - * @private - * @template TYPE + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {Array.<ol.Feature>} Features. + * @api stable */ -goog.Promise.Resolver_ = function(promise, resolve, reject) { - /** @const */ - this.promise = promise; - - /** @const */ - this.resolve = resolve; - - /** @const */ - this.reject = reject; -}; +ol.format.WKT.prototype.readFeatures; -// Copyright 2007 Bob Ippolito. All Rights Reserved. -// Modifications Copyright 2009 The Closure Library Authors. All Rights -// Reserved. /** - * @license Portions of this code are from MochiKit, received by - * The Closure Authors under the MIT license. All other code is Copyright - * 2005-2009 The Closure Authors. All Rights Reserved. + * @inheritDoc */ +ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) { + var geometries = []; + var geometry = this.readGeometryFromText(text, opt_options); + if (this.splitCollection_ && + geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) { + geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry)) + .getGeometriesArray(); + } else { + geometries = [geometry]; + } + var feature, features = []; + for (var i = 0, ii = geometries.length; i < ii; ++i) { + feature = new ol.Feature(); + feature.setGeometry(geometries[i]); + features.push(feature); + } + return features; +}; + /** - * @fileoverview Classes for tracking asynchronous operations and handling the - * results. The Deferred object here is patterned after the Deferred object in - * the Twisted python networking framework. - * - * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html - * - * Based on the Dojo code which in turn is based on the MochiKit code. + * Read a single geometry from a WKT source. * - * @author arv@google.com (Erik Arvidsson) - * @author brenneman@google.com (Shawn Brenneman) + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.geom.Geometry} Geometry. + * @api stable */ +ol.format.WKT.prototype.readGeometry; -goog.provide('goog.async.Deferred'); -goog.provide('goog.async.Deferred.AlreadyCalledError'); -goog.provide('goog.async.Deferred.CanceledError'); - -goog.require('goog.Promise'); -goog.require('goog.Thenable'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.debug.Error'); +/** + * @inheritDoc + */ +ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) { + var geometry = this.parse_(text); + if (goog.isDef(geometry)) { + return /** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, false, opt_options)); + } else { + return null; + } +}; /** - * A Deferred represents the result of an asynchronous operation. A Deferred - * instance has no result when it is created, and is "fired" (given an initial - * result) by calling {@code callback} or {@code errback}. - * - * Once fired, the result is passed through a sequence of callback functions - * registered with {@code addCallback} or {@code addErrback}. The functions may - * mutate the result before it is passed to the next function in the sequence. - * - * Callbacks and errbacks may be added at any time, including after the Deferred - * has been "fired". If there are no pending actions in the execution sequence - * of a fired Deferred, any new callback functions will be called with the last - * computed result. Adding a callback function is the only way to access the - * result of the Deferred. - * - * If a Deferred operation is canceled, an optional user-provided cancellation - * function is invoked which may perform any special cleanup, followed by firing - * the Deferred's errback sequence with a {@code CanceledError}. If the - * Deferred has already fired, cancellation is ignored. - * - * Deferreds may be templated to a specific type they produce using generics - * with syntax such as: - * <code> - * /** @type {goog.async.Deferred<string>} */ - * var d = new goog.async.Deferred(); - * // Compiler can infer that foo is a string. - * d.addCallback(function(foo) {...}); - * d.callback('string'); // Checked to be passed a string - * </code> - * Since deferreds are often used to produce different values across a chain, - * the type information is not propagated across chains, but rather only - * associated with specifically cast objects. + * Encode a feature as a WKT string. * - * @param {Function=} opt_onCancelFunction A function that will be called if the - * Deferred is canceled. If provided, this function runs before the - * Deferred is fired with a {@code CanceledError}. - * @param {Object=} opt_defaultScope The default object context to call - * callbacks and errbacks in. - * @constructor - * @implements {goog.Thenable<VALUE>} - * @template VALUE + * @function + * @param {ol.Feature} feature Feature. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} WKT string. + * @api stable */ -goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) { - /** - * Entries in the sequence are arrays containing a callback, an errback, and - * an optional scope. The callback or errback in an entry may be null. - * @type {!Array<!Array>} - * @private - */ - this.sequence_ = []; - - /** - * Optional function that will be called if the Deferred is canceled. - * @type {Function|undefined} - * @private - */ - this.onCancelFunction_ = opt_onCancelFunction; - - /** - * The default scope to execute callbacks and errbacks in. - * @type {Object} - * @private - */ - this.defaultScope_ = opt_defaultScope || null; - - /** - * Whether the Deferred has been fired. - * @type {boolean} - * @private - */ - this.fired_ = false; - - /** - * Whether the last result in the execution sequence was an error. - * @type {boolean} - * @private - */ - this.hadError_ = false; - - /** - * The current Deferred result, updated as callbacks and errbacks are - * executed. - * @type {*} - * @private - */ - this.result_ = undefined; - - /** - * Whether the Deferred is blocked waiting on another Deferred to fire. If a - * callback or errback returns a Deferred as a result, the execution sequence - * is blocked until that Deferred result becomes available. - * @type {boolean} - * @private - */ - this.blocked_ = false; - - /** - * Whether this Deferred is blocking execution of another Deferred. If this - * instance was returned as a result in another Deferred's execution - * sequence,that other Deferred becomes blocked until this instance's - * execution sequence completes. No additional callbacks may be added to a - * Deferred once it is blocking another instance. - * @type {boolean} - * @private - */ - this.blocking_ = false; - - /** - * Whether the Deferred has been canceled without having a custom cancel - * function. - * @type {boolean} - * @private - */ - this.silentlyCanceled_ = false; - - /** - * If an error is thrown during Deferred execution with no errback to catch - * it, the error is rethrown after a timeout. Reporting the error after a - * timeout allows execution to continue in the calling context (empty when - * no error is scheduled). - * @type {number} - * @private - */ - this.unhandledErrorId_ = 0; - - /** - * If this Deferred was created by branch(), this will be the "parent" - * Deferred. - * @type {goog.async.Deferred} - * @private - */ - this.parent_ = null; +ol.format.WKT.prototype.writeFeature; - /** - * The number of Deferred objects that have been branched off this one. This - * will be decremented whenever a branch is fired or canceled. - * @type {number} - * @private - */ - this.branches_ = 0; - if (goog.async.Deferred.LONG_STACK_TRACES) { - /** - * Holds the stack trace at time of deferred creation if the JS engine - * provides the Error.captureStackTrace API. - * @private {?string} - */ - this.constructorStack_ = null; - if (Error.captureStackTrace) { - var target = { stack: '' }; - Error.captureStackTrace(target, goog.async.Deferred); - // Check if Error.captureStackTrace worked. It fails in gjstest. - if (typeof target.stack == 'string') { - // Remove first line and force stringify to prevent memory leak due to - // holding on to actual stack frames. - this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, ''); - } - } +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) { + var geometry = feature.getGeometry(); + if (goog.isDef(geometry)) { + return this.writeGeometryText(geometry, opt_options); } + return ''; }; /** - * @define {boolean} Whether unhandled errors should always get rethrown to the - * global scope. Defaults to the value of goog.DEBUG. + * Encode an array of features as a WKT string. + * + * @function + * @param {Array.<ol.Feature>} features Features. + * @param {olx.format.WriteOptions=} opt_options Write options. + * @return {string} WKT string. + * @api stable */ -goog.define('goog.async.Deferred.STRICT_ERRORS', false); +ol.format.WKT.prototype.writeFeatures; /** - * @define {boolean} Whether to attempt to make stack traces long. Defaults to - * the value of goog.DEBUG. + * @inheritDoc */ -goog.define('goog.async.Deferred.LONG_STACK_TRACES', false); +ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) { + if (features.length == 1) { + return this.writeFeatureText(features[0], opt_options); + } + var geometries = []; + for (var i = 0, ii = features.length; i < ii; ++i) { + geometries.push(features[i].getGeometry()); + } + var collection = new ol.geom.GeometryCollection(geometries); + return this.writeGeometryText(collection, opt_options); +}; /** - * Cancels a Deferred that has not yet been fired, or is blocked on another - * deferred operation. If this Deferred is waiting for a blocking Deferred to - * fire, the blocking Deferred will also be canceled. - * - * If this Deferred was created by calling branch() on a parent Deferred with - * opt_propagateCancel set to true, the parent may also be canceled. If - * opt_deepCancel is set, cancel() will be called on the parent (as well as any - * other ancestors if the parent is also a branch). If one or more branches were - * created with opt_propagateCancel set to true, the parent will be canceled if - * cancel() is called on all of those branches. + * Write a single geometry as a WKT string. * - * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even - * if cancel() hasn't been called on some of the parent's branches. Has no - * effect on a branch without opt_propagateCancel set to true. + * @function + * @param {ol.geom.Geometry} geometry Geometry. + * @return {string} WKT string. + * @api stable */ -goog.async.Deferred.prototype.cancel = function(opt_deepCancel) { - if (!this.hasFired()) { - if (this.parent_) { - // Get rid of the parent reference before potentially running the parent's - // canceler function to ensure that this cancellation isn't - // double-counted. - var parent = this.parent_; - delete this.parent_; - if (opt_deepCancel) { - parent.cancel(opt_deepCancel); - } else { - parent.branchCancel_(); - } - } +ol.format.WKT.prototype.writeGeometry; - if (this.onCancelFunction_) { - // Call in user-specified scope. - this.onCancelFunction_.call(this.defaultScope_, this); - } else { - this.silentlyCanceled_ = true; - } - if (!this.hasFired()) { - this.errback(new goog.async.Deferred.CanceledError(this)); - } - } else if (this.result_ instanceof goog.async.Deferred) { - this.result_.cancel(); - } + +/** + * @inheritDoc + */ +ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) { + return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ ( + ol.format.Feature.transformWithOptions(geometry, true, opt_options))); }; /** - * Handle a single branch being canceled. Once all branches are canceled, this - * Deferred will be canceled as well. - * - * @private + * @typedef {{type: number, value: (number|string|undefined), position: number}} */ -goog.async.Deferred.prototype.branchCancel_ = function() { - this.branches_--; - if (this.branches_ <= 0) { - this.cancel(); - } +ol.format.WKT.Token; + + +/** + * @const + * @enum {number} + */ +ol.format.WKT.TokenType = { + TEXT: 1, + LEFT_PAREN: 2, + RIGHT_PAREN: 3, + NUMBER: 4, + COMMA: 5, + EOF: 6 }; + /** - * Called after a blocking Deferred fires. Unblocks this Deferred and resumes - * its execution sequence. - * - * @param {boolean} isSuccess Whether the result is a success or an error. - * @param {*} res The result of the blocking Deferred. - * @private + * Class to tokenize a WKT string. + * @param {string} wkt WKT string. + * @constructor + * @protected */ -goog.async.Deferred.prototype.continue_ = function(isSuccess, res) { - this.blocked_ = false; - this.updateResult_(isSuccess, res); +ol.format.WKT.Lexer = function(wkt) { + + /** + * @type {string} + */ + this.wkt = wkt; + + /** + * @type {number} + * @private + */ + this.index_ = -1; }; /** - * Updates the current result based on the success or failure of the last action - * in the execution sequence. - * - * @param {boolean} isSuccess Whether the new result is a success or an error. - * @param {*} res The result. + * @param {string} c Character. + * @return {boolean} Whether the character is alphabetic. * @private */ -goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) { - this.fired_ = true; - this.result_ = res; - this.hadError_ = !isSuccess; - this.fire_(); +ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) { + return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; }; /** - * Verifies that the Deferred has not yet been fired. - * + * @param {string} c Character. + * @param {boolean=} opt_decimal Whether the string number + * contains a dot, i.e. is a decimal number. + * @return {boolean} Whether the character is numeric. * @private - * @throws {Error} If this has already been fired. */ -goog.async.Deferred.prototype.check_ = function() { - if (this.hasFired()) { - if (!this.silentlyCanceled_) { - throw new goog.async.Deferred.AlreadyCalledError(this); - } - this.silentlyCanceled_ = false; - } +ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) { + var decimal = goog.isDef(opt_decimal) ? opt_decimal : false; + return c >= '0' && c <= '9' || c == '.' && !decimal; }; /** - * Fire the execution sequence for this Deferred by passing the starting result - * to the first registered callback. - * @param {VALUE=} opt_result The starting result. + * @param {string} c Character. + * @return {boolean} Whether the character is whitespace. + * @private */ -goog.async.Deferred.prototype.callback = function(opt_result) { - this.check_(); - this.assertNotDeferred_(opt_result); - this.updateResult_(true /* isSuccess */, opt_result); +ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; }; /** - * Fire the execution sequence for this Deferred by passing the starting error - * result to the first registered errback. - * @param {*=} opt_result The starting error. + * @return {string} Next string character. + * @private */ -goog.async.Deferred.prototype.errback = function(opt_result) { - this.check_(); - this.assertNotDeferred_(opt_result); - this.makeStackTraceLong_(opt_result); - this.updateResult_(false /* isSuccess */, opt_result); +ol.format.WKT.Lexer.prototype.nextChar_ = function() { + return this.wkt.charAt(++this.index_); }; /** - * Attempt to make the error's stack trace be long in that it contains the - * stack trace from the point where the deferred was created on top of the - * current stack trace to give additional context. - * @param {*} error - * @private + * Fetch and return the next token. + * @return {!ol.format.WKT.Token} Next string token. */ -goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) { - if (!goog.async.Deferred.LONG_STACK_TRACES) { - return; - } - if (this.constructorStack_ && goog.isObject(error) && error.stack && - // Stack looks like it was system generated. See - // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi - (/^[^\n]+(\n [^\n]+)+/).test(error.stack)) { - error.stack = error.stack + '\nDEFERRED OPERATION:\n' + - this.constructorStack_; +ol.format.WKT.Lexer.prototype.nextToken = function() { + var c = this.nextChar_(); + var token = {position: this.index_, value: c}; + + if (c == '(') { + token.type = ol.format.WKT.TokenType.LEFT_PAREN; + } else if (c == ',') { + token.type = ol.format.WKT.TokenType.COMMA; + } else if (c == ')') { + token.type = ol.format.WKT.TokenType.RIGHT_PAREN; + } else if (this.isNumeric_(c) || c == '-') { + token.type = ol.format.WKT.TokenType.NUMBER; + token.value = this.readNumber_(); + } else if (this.isAlpha_(c)) { + token.type = ol.format.WKT.TokenType.TEXT; + token.value = this.readText_(); + } else if (this.isWhiteSpace_(c)) { + return this.nextToken(); + } else if (c === '') { + token.type = ol.format.WKT.TokenType.EOF; + } else { + throw new Error('Unexpected character: ' + c); } + + return token; }; /** - * Asserts that an object is not a Deferred. - * @param {*} obj The object to test. - * @throws {Error} Throws an exception if the object is a Deferred. + * @return {number} Numeric token value. * @private */ -goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) { - goog.asserts.assert( - !(obj instanceof goog.async.Deferred), - 'An execution sequence may not be initiated with a blocking Deferred.'); +ol.format.WKT.Lexer.prototype.readNumber_ = function() { + var c, index = this.index_; + var decimal = false; + var scientificNotation = false; + do { + if (c == '.') { + decimal = true; + } else if (c == 'e' || c == 'E') { + scientificNotation = true; + } + c = this.nextChar_(); + } while ( + this.isNumeric_(c, decimal) || + // if we haven't detected a scientific number before, 'e' or 'E' + // hint that we should continue to read + !scientificNotation && (c == 'e' || c == 'E') || + // once we know that we have a scientific number, both '-' and '+' + // are allowed + scientificNotation && (c == '-' || c == '+') + ); + return parseFloat(this.wkt.substring(index, this.index_--)); }; /** - * Register a callback function to be called with a successful result. If no - * value is returned by the callback function, the result value is unchanged. If - * a new value is returned, it becomes the Deferred result and will be passed to - * the next callback in the execution sequence. - * - * If the function throws an error, the error becomes the new result and will be - * passed to the next errback in the execution chain. - * - * If the function returns a Deferred, the execution sequence will be blocked - * until that Deferred fires. Its result will be passed to the next callback (or - * errback if it is an error result) in this Deferred's execution sequence. - * - * @param {!function(this:T,VALUE):?} cb The function to be called with a - * successful result. - * @param {T=} opt_scope An optional scope to call the callback in. - * @return {!goog.async.Deferred} This Deferred. - * @template T + * @return {string} String token value. + * @private */ -goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) { - return this.addCallbacks(cb, null, opt_scope); +ol.format.WKT.Lexer.prototype.readText_ = function() { + var c, index = this.index_; + do { + c = this.nextChar_(); + } while (this.isAlpha_(c)); + return this.wkt.substring(index, this.index_--).toUpperCase(); }; + /** - * Register a callback function to be called with an error result. If no value - * is returned by the function, the error result is unchanged. If a new error - * value is returned or thrown, that error becomes the Deferred result and will - * be passed to the next errback in the execution sequence. - * - * If the errback function handles the error by returning a non-error value, - * that result will be passed to the next normal callback in the sequence. - * - * If the function returns a Deferred, the execution sequence will be blocked - * until that Deferred fires. Its result will be passed to the next callback (or - * errback if it is an error result) in this Deferred's execution sequence. - * - * @param {!function(this:T,?):?} eb The function to be called on an - * unsuccessful result. - * @param {T=} opt_scope An optional scope to call the errback in. - * @return {!goog.async.Deferred<VALUE>} This Deferred. - * @template T + * Class to parse the tokens from the WKT string. + * @param {ol.format.WKT.Lexer} lexer + * @constructor + * @protected */ -goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) { - return this.addCallbacks(null, eb, opt_scope); +ol.format.WKT.Parser = function(lexer) { + + /** + * @type {ol.format.WKT.Lexer} + * @private + */ + this.lexer_ = lexer; + + /** + * @type {ol.format.WKT.Token} + * @private + */ + this.token_; + + /** + * @type {number} + * @private + */ + this.dimension_ = 2; }; /** - * Registers one function as both a callback and errback. - * - * @param {!function(this:T,?):?} f The function to be called on any result. - * @param {T=} opt_scope An optional scope to call the function in. - * @return {!goog.async.Deferred} This Deferred. - * @template T + * Fetch the next token form the lexer and replace the active token. + * @private */ -goog.async.Deferred.prototype.addBoth = function(f, opt_scope) { - return this.addCallbacks(f, f, opt_scope); +ol.format.WKT.Parser.prototype.consume_ = function() { + this.token_ = this.lexer_.nextToken(); }; /** - * Registers a callback function and an errback function at the same position - * in the execution sequence. Only one of these functions will execute, - * depending on the error state during the execution sequence. - * - * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If - * the callback is invoked, the errback will be skipped, and vice versa. - * - * @param {(function(this:T,VALUE):?)|null} cb The function to be called on a - * successful result. - * @param {(function(this:T,?):?)|null} eb The function to be called on an - * unsuccessful result. - * @param {T=} opt_scope An optional scope to call the functions in. - * @return {!goog.async.Deferred} This Deferred. - * @template T + * If the given type matches the current token, consume it. + * @param {ol.format.WKT.TokenType.<number>} type Token type. + * @return {boolean} Whether the token matches the given type. */ -goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) { - goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used'); - this.sequence_.push([cb, eb, opt_scope]); - if (this.hasFired()) { - this.fire_(); +ol.format.WKT.Parser.prototype.match = function(type) { + var isMatch = this.token_.type == type; + if (isMatch) { + this.consume_(); } - return this; + return isMatch; }; /** - * Implements {@see goog.Thenable} for seamless integration with - * {@see goog.Promise}. - * Deferred results are mutable and may represent multiple values over - * their lifetime. Calling {@code then} on a Deferred returns a Promise - * with the result of the Deferred at that point in its callback chain. - * Note that if the Deferred result is never mutated, and only - * {@code then} calls are made, the Deferred will behave like a Promise. - * - * @override + * Try to parse the tokens provided by the lexer. + * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry. */ -goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected, - opt_context) { - var resolve, reject; - var promise = new goog.Promise(function(res, rej) { - // Copying resolvers to outer scope, so that they are available when the - // deferred callback fires (which may be synchronous). - resolve = res; - reject = rej; - }); - this.addCallbacks(resolve, function(reason) { - if (reason instanceof goog.async.Deferred.CanceledError) { - promise.cancel(); - } else { - reject(reason); - } - }); - return promise.then(opt_onFulfilled, opt_onRejected, opt_context); +ol.format.WKT.Parser.prototype.parse = function() { + this.consume_(); + var geometry = this.parseGeometry_(); + goog.asserts.assert(this.token_.type == ol.format.WKT.TokenType.EOF, + 'token type should be end of file'); + return geometry; }; -goog.Thenable.addImplementation(goog.async.Deferred); /** - * Links another Deferred to the end of this Deferred's execution sequence. The - * result of this execution sequence will be passed as the starting result for - * the chained Deferred, invoking either its first callback or errback. - * - * @param {!goog.async.Deferred} otherDeferred The Deferred to chain. - * @return {!goog.async.Deferred} This Deferred. + * @return {!(ol.geom.Geometry|ol.geom.GeometryCollection)} The geometry. + * @private */ -goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) { - this.addCallbacks( - otherDeferred.callback, otherDeferred.errback, otherDeferred); - return this; +ol.format.WKT.Parser.prototype.parseGeometry_ = function() { + var token = this.token_; + if (this.match(ol.format.WKT.TokenType.TEXT)) { + var geomType = token.value; + if (geomType == ol.geom.GeometryType.GEOMETRY_COLLECTION.toUpperCase()) { + var geometries = this.parseGeometryCollectionText_(); + return new ol.geom.GeometryCollection(geometries); + } else { + var parser = ol.format.WKT.Parser.GeometryParser_[geomType]; + var ctor = ol.format.WKT.Parser.GeometryConstructor_[geomType]; + if (!goog.isDef(parser) || !goog.isDef(ctor)) { + throw new Error('Invalid geometry type: ' + geomType); + } + var coordinates = parser.call(this); + return new ctor(coordinates); + } + } + throw new Error(this.formatErrorMessage_()); }; /** - * Makes this Deferred wait for another Deferred's execution sequence to - * complete before continuing. - * - * This is equivalent to adding a callback that returns {@code otherDeferred}, - * but doesn't prevent additional callbacks from being added to - * {@code otherDeferred}. - * - * @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred - * to wait for. - * @return {!goog.async.Deferred} This Deferred. + * @return {!Array.<ol.geom.Geometry>} A collection of geometries. + * @private */ -goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) { - if (!(otherDeferred instanceof goog.async.Deferred)) { - // The Thenable case. - return this.addCallback(function() { - return otherDeferred; - }); +ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var geometries = []; + do { + geometries.push(this.parseGeometry_()); + } while (this.match(ol.format.WKT.TokenType.COMMA)); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return geometries; + } + } else if (this.isEmptyGeometry_()) { + return []; } - return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred)); + throw new Error(this.formatErrorMessage_()); }; /** - * Creates a branch off this Deferred's execution sequence, and returns it as a - * new Deferred. The branched Deferred's starting result will be shared with the - * parent at the point of the branch, even if further callbacks are added to the - * parent. - * - * All branches at the same stage in the execution sequence will receive the - * same starting value. - * - * @param {boolean=} opt_propagateCancel If cancel() is called on every child - * branch created with opt_propagateCancel, the parent will be canceled as - * well. - * @return {!goog.async.Deferred<VALUE>} A Deferred that will be started with - * the computed result from this stage in the execution sequence. + * @return {Array.<number>} All values in a point. + * @private */ -goog.async.Deferred.prototype.branch = function(opt_propagateCancel) { - var d = new goog.async.Deferred(); - this.chainDeferred(d); - if (opt_propagateCancel) { - d.parent_ = this; - this.branches_++; +ol.format.WKT.Parser.prototype.parsePointText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePoint_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return null; } - return d; + throw new Error(this.formatErrorMessage_()); }; /** - * @return {boolean} Whether the execution sequence has been started on this - * Deferred by invoking {@code callback} or {@code errback}. + * @return {!Array.<!Array.<number>>} All points in a linestring. + * @private */ -goog.async.Deferred.prototype.hasFired = function() { - return this.fired_; +ol.format.WKT.Parser.prototype.parseLineStringText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePointList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); }; /** - * @param {*} res The latest result in the execution sequence. - * @return {boolean} Whether the current result is an error that should cause - * the next errback to fire. May be overridden by subclasses to handle - * special error types. - * @protected + * @return {!Array.<!Array.<number>>} All points in a polygon. + * @private */ -goog.async.Deferred.prototype.isError = function(res) { - return res instanceof Error; +ol.format.WKT.Parser.prototype.parsePolygonText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parseLineStringTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); }; /** - * @return {boolean} Whether an errback exists in the remaining sequence. + * @return {!Array.<!Array.<number>>} All points in a multipoint. * @private */ -goog.async.Deferred.prototype.hasErrback_ = function() { - return goog.array.some(this.sequence_, function(sequenceRow) { - // The errback is the second element in the array. - return goog.isFunction(sequenceRow[1]); - }); +ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates; + if (this.token_.type == ol.format.WKT.TokenType.LEFT_PAREN) { + coordinates = this.parsePointTextList_(); + } else { + coordinates = this.parsePointList_(); + } + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); }; /** - * Exhausts the execution sequence while a result is available. The result may - * be modified by callbacks or errbacks, and execution will block if the - * returned result is an incomplete Deferred. - * + * @return {!Array.<!Array.<number>>} All linestring points + * in a multilinestring. * @private */ -goog.async.Deferred.prototype.fire_ = function() { - if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) { - // It is possible to add errbacks after the Deferred has fired. If a new - // errback is added immediately after the Deferred encountered an unhandled - // error, but before that error is rethrown, the error is unscheduled. - goog.async.Deferred.unscheduleError_(this.unhandledErrorId_); - this.unhandledErrorId_ = 0; - } - - if (this.parent_) { - this.parent_.branches_--; - delete this.parent_; - } - - var res = this.result_; - var unhandledException = false; - var isNewlyBlocked = false; - - while (this.sequence_.length && !this.blocked_) { - var sequenceEntry = this.sequence_.shift(); - - var callback = sequenceEntry[0]; - var errback = sequenceEntry[1]; - var scope = sequenceEntry[2]; - - var f = this.hadError_ ? errback : callback; - if (f) { - /** @preserveTry */ - try { - var ret = f.call(scope || this.defaultScope_, res); - - // If no result, then use previous result. - if (goog.isDef(ret)) { - // Bubble up the error as long as the return value hasn't changed. - this.hadError_ = this.hadError_ && (ret == res || this.isError(ret)); - this.result_ = res = ret; - } - - if (goog.Thenable.isImplementedBy(res)) { - isNewlyBlocked = true; - this.blocked_ = true; - } +ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parseLineStringTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; + } + } else if (this.isEmptyGeometry_()) { + return []; + } + throw new Error(this.formatErrorMessage_()); +}; - } catch (ex) { - res = ex; - this.hadError_ = true; - this.makeStackTraceLong_(res); - if (!this.hasErrback_()) { - // If an error is thrown with no additional errbacks in the queue, - // prepare to rethrow the error. - unhandledException = true; - } - } +/** + * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon. + * @private + */ +ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() { + if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) { + var coordinates = this.parsePolygonTextList_(); + if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) { + return coordinates; } + } else if (this.isEmptyGeometry_()) { + return []; } + throw new Error(this.formatErrorMessage_()); +}; - this.result_ = res; - - if (isNewlyBlocked) { - var onCallback = goog.bind(this.continue_, this, true /* isSuccess */); - var onErrback = goog.bind(this.continue_, this, false /* isSuccess */); - if (res instanceof goog.async.Deferred) { - res.addCallbacks(onCallback, onErrback); - res.blocking_ = true; +/** + * @return {!Array.<number>} A point. + * @private + */ +ol.format.WKT.Parser.prototype.parsePoint_ = function() { + var coordinates = []; + for (var i = 0; i < this.dimension_; ++i) { + var token = this.token_; + if (this.match(ol.format.WKT.TokenType.NUMBER)) { + coordinates.push(token.value); } else { - res.then(onCallback, onErrback); + break; } - } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) && - !(res instanceof goog.async.Deferred.CanceledError)) { - this.hadError_ = true; - unhandledException = true; } - - if (unhandledException) { - // Rethrow the unhandled error after a timeout. Execution will continue, but - // the error will be seen by global handlers and the user. The throw will - // be canceled if another errback is appended before the timeout executes. - // The error's original stack trace is preserved where available. - this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res); + if (coordinates.length == this.dimension_) { + return coordinates; } + throw new Error(this.formatErrorMessage_()); }; /** - * Creates a Deferred that has an initial result. - * - * @param {*=} opt_result The result. - * @return {!goog.async.Deferred} The new Deferred. + * @return {!Array.<!Array.<number>>} An array of points. + * @private */ -goog.async.Deferred.succeed = function(opt_result) { - var d = new goog.async.Deferred(); - d.callback(opt_result); - return d; +ol.format.WKT.Parser.prototype.parsePointList_ = function() { + var coordinates = [this.parsePoint_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePoint_()); + } + return coordinates; }; /** - * Creates a Deferred that fires when the given promise resolves. - * Use only during migration to Promises. - * - * @param {!goog.Promise<T>} promise - * @return {!goog.async.Deferred<T>} The new Deferred. - * @template T + * @return {!Array.<!Array.<number>>} An array of points. + * @private */ -goog.async.Deferred.fromPromise = function(promise) { - var d = new goog.async.Deferred(); - d.callback(); - d.addCallback(function() { - return promise; - }); - return d; +ol.format.WKT.Parser.prototype.parsePointTextList_ = function() { + var coordinates = [this.parsePointText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePointText_()); + } + return coordinates; }; /** - * Creates a Deferred that has an initial error result. - * - * @param {*} res The error result. - * @return {!goog.async.Deferred} The new Deferred. + * @return {!Array.<!Array.<number>>} An array of points. + * @private */ -goog.async.Deferred.fail = function(res) { - var d = new goog.async.Deferred(); - d.errback(res); - return d; +ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() { + var coordinates = [this.parseLineStringText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parseLineStringText_()); + } + return coordinates; }; /** - * Creates a Deferred that has already been canceled. - * - * @return {!goog.async.Deferred} The new Deferred. + * @return {!Array.<!Array.<number>>} An array of points. + * @private */ -goog.async.Deferred.canceled = function() { - var d = new goog.async.Deferred(); - d.cancel(); - return d; +ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() { + var coordinates = [this.parsePolygonText_()]; + while (this.match(ol.format.WKT.TokenType.COMMA)) { + coordinates.push(this.parsePolygonText_()); + } + return coordinates; }; /** - * Normalizes values that may or may not be Deferreds. - * - * If the input value is a Deferred, the Deferred is branched (so the original - * execution sequence is not modified) and the input callback added to the new - * branch. The branch is returned to the caller. - * - * If the input value is not a Deferred, the callback will be executed - * immediately and an already firing Deferred will be returned to the caller. - * - * In the following (contrived) example, if <code>isImmediate</code> is true - * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay. - * - * <pre> - * var value; - * if (isImmediate) { - * value = 3; - * } else { - * value = new goog.async.Deferred(); - * setTimeout(function() { value.callback(6); }, 2000); - * } - * - * var d = goog.async.Deferred.when(value, alert); - * </pre> - * - * @param {*} value Deferred or normal value to pass to the callback. - * @param {!function(this:T, ?):?} callback The callback to execute. - * @param {T=} opt_scope An optional scope to call the callback in. - * @return {!goog.async.Deferred} A new Deferred that will call the input - * callback with the input value. - * @template T + * @return {boolean} Whether the token implies an empty geometry. + * @private */ -goog.async.Deferred.when = function(value, callback, opt_scope) { - if (value instanceof goog.async.Deferred) { - return value.branch(true).addCallback(callback, opt_scope); - } else { - return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope); +ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() { + var isEmpty = this.token_.type == ol.format.WKT.TokenType.TEXT && + this.token_.value == ol.format.WKT.EMPTY; + if (isEmpty) { + this.consume_(); } + return isEmpty; }; - /** - * An error sub class that is used when a Deferred has already been called. - * @param {!goog.async.Deferred} deferred The Deferred. - * - * @constructor - * @extends {goog.debug.Error} + * Create an error message for an unexpected token error. + * @return {string} Error message. + * @private */ -goog.async.Deferred.AlreadyCalledError = function(deferred) { - goog.debug.Error.call(this); +ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() { + return 'Unexpected `' + this.token_.value + '` at position ' + + this.token_.position + ' in `' + this.lexer_.wkt + '`'; +}; - /** - * The Deferred that raised this error. - * @type {goog.async.Deferred} - */ - this.deferred = deferred; + +/** + * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout.<string>=)} + * @private + */ +ol.format.WKT.Parser.GeometryConstructor_ = { + 'POINT': ol.geom.Point, + 'LINESTRING': ol.geom.LineString, + 'POLYGON': ol.geom.Polygon, + 'MULTIPOINT': ol.geom.MultiPoint, + 'MULTILINESTRING': ol.geom.MultiLineString, + 'MULTIPOLYGON': ol.geom.MultiPolygon }; -goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error); -/** @override */ -goog.async.Deferred.AlreadyCalledError.prototype.message = - 'Deferred has already fired'; +/** + * @enum {(function(): Array)} + * @private + */ +ol.format.WKT.Parser.GeometryParser_ = { + 'POINT': ol.format.WKT.Parser.prototype.parsePointText_, + 'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_, + 'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_, + 'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_, + 'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_, + 'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_ +}; +goog.provide('ol.format.WMSCapabilities'); -/** @override */ -goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError'; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); /** - * An error sub class that is used when a Deferred is canceled. + * @classdesc + * Format for reading WMS capabilities data * - * @param {!goog.async.Deferred} deferred The Deferred object. * @constructor - * @extends {goog.debug.Error} + * @extends {ol.format.XML} + * @api */ -goog.async.Deferred.CanceledError = function(deferred) { - goog.debug.Error.call(this); +ol.format.WMSCapabilities = function() { + + goog.base(this); /** - * The Deferred that raised this error. - * @type {goog.async.Deferred} + * @type {string|undefined} */ - this.deferred = deferred; + this.version = undefined; }; -goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error); - - -/** @override */ -goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled'; - - -/** @override */ -goog.async.Deferred.CanceledError.prototype.name = 'CanceledError'; - +goog.inherits(ol.format.WMSCapabilities, ol.format.XML); /** - * Wrapper around errors that are scheduled to be thrown by failing deferreds - * after a timeout. + * Read a WMS capabilities document. * - * @param {*} error Error from a failing deferred. - * @constructor - * @final - * @private - * @struct + * @function + * @param {Document|Node|string} source The XML source. + * @return {Object} An object representing the WMS capabilities. + * @api */ -goog.async.Deferred.Error_ = function(error) { - /** @const @private {number} */ - this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0); - - /** @const @private {*} */ - this.error_ = error; -}; +ol.format.WMSCapabilities.prototype.read; /** - * Actually throws the error and removes it from the list of pending - * deferred errors. + * @param {Document} doc Document. + * @return {Object} WMS Capability object. */ -goog.async.Deferred.Error_.prototype.throwError = function() { - goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_], - 'Cannot throw an error that is not scheduled.'); - delete goog.async.Deferred.errorMap_[this.id_]; - throw this.error_; +ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) { + goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + return this.readFromNode(n); + } + } + return null; }; /** - * Resets the error throw timer. + * @param {Node} node Node. + * @return {Object} WMS Capability object. */ -goog.async.Deferred.Error_.prototype.resetTimer = function() { - goog.global.clearTimeout(this.id_); +ol.format.WMSCapabilities.prototype.readFromNode = function(node) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'WMS_Capabilities' || + node.localName == 'WMT_MS_Capabilities', + 'localName should be WMS_Capabilities or WMT_MS_Capabilities'); + this.version = goog.string.trim(node.getAttribute('version')); + goog.asserts.assertString(this.version, 'this.version should be a string'); + var wmsCapabilityObject = ol.xml.pushParseAndPop({ + 'version': this.version + }, ol.format.WMSCapabilities.PARSERS_, node, []); + return goog.isDef(wmsCapabilityObject) ? wmsCapabilityObject : null; }; /** - * Map of unhandled errors scheduled to be rethrown in a future timestep. - * @private {!Object<number|string, goog.async.Deferred.Error_>} + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Attribution object. */ -goog.async.Deferred.errorMap_ = {}; +ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Attribution', + 'localName should be Attribution'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack); +}; /** - * Schedules an error to be thrown after a delay. - * @param {*} error Error from a failing deferred. - * @return {number} Id of the error. * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object} Bounding box object. */ -goog.async.Deferred.scheduleError_ = function(error) { - var deferredError = new goog.async.Deferred.Error_(error); - goog.async.Deferred.errorMap_[deferredError.id_] = deferredError; - return deferredError.id_; +ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'BoundingBox', + 'localName should be BoundingBox'); + + var extent = [ + ol.format.XSD.readDecimalString(node.getAttribute('minx')), + ol.format.XSD.readDecimalString(node.getAttribute('miny')), + ol.format.XSD.readDecimalString(node.getAttribute('maxx')), + ol.format.XSD.readDecimalString(node.getAttribute('maxy')) + ]; + + var resolutions = [ + ol.format.XSD.readDecimalString(node.getAttribute('resx')), + ol.format.XSD.readDecimalString(node.getAttribute('resy')) + ]; + + return { + 'crs': node.getAttribute('CRS'), + 'extent': extent, + 'res': resolutions + }; }; /** - * Unschedules an error from being thrown. - * @param {number} id Id of the deferred error to unschedule. * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {ol.Extent|undefined} Bounding box object. */ -goog.async.Deferred.unscheduleError_ = function(id) { - var error = goog.async.Deferred.errorMap_[id]; - if (error) { - error.resetTimer(); - delete goog.async.Deferred.errorMap_[id]; +ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'EX_GeographicBoundingBox', + 'localName should be EX_GeographicBoundingBox'); + var geographicBoundingBox = ol.xml.pushParseAndPop( + {}, + ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_, + node, objectStack); + if (!goog.isDef(geographicBoundingBox)) { + return undefined; } + var westBoundLongitude = /** @type {number|undefined} */ + (geographicBoundingBox['westBoundLongitude']); + var southBoundLatitude = /** @type {number|undefined} */ + (geographicBoundingBox['southBoundLatitude']); + var eastBoundLongitude = /** @type {number|undefined} */ + (geographicBoundingBox['eastBoundLongitude']); + var northBoundLatitude = /** @type {number|undefined} */ + (geographicBoundingBox['northBoundLatitude']); + if (!goog.isDef(westBoundLongitude) || !goog.isDef(southBoundLatitude) || + !goog.isDef(eastBoundLongitude) || !goog.isDef(northBoundLatitude)) { + return undefined; + } + return [ + westBoundLongitude, southBoundLatitude, + eastBoundLongitude, northBoundLatitude + ]; }; /** - * Asserts that there are no pending deferred errors. If there are any - * scheduled errors, one will be thrown immediately to make this function fail. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Capability object. */ -goog.async.Deferred.assertNoErrors = function() { - var map = goog.async.Deferred.errorMap_; - for (var key in map) { - var error = map[key]; - error.resetTimer(); - error.throwError(); - } +ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Capability', + 'localName should be Capability'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack); }; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A wrapper for the HTML5 FileError object. - * + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Service object. */ - -goog.provide('goog.fs.Error'); -goog.provide('goog.fs.Error.ErrorCode'); - -goog.require('goog.debug.Error'); -goog.require('goog.object'); -goog.require('goog.string'); - +ol.format.WMSCapabilities.readService_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Service', + 'localName should be Service'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack); +}; /** - * A filesystem error. Since the filesystem API is asynchronous, stack traces - * are less useful for identifying where errors come from, so this includes a - * large amount of metadata in the message. - * - * @param {!DOMError} error - * @param {string} action The action being undertaken when the error was raised. - * @constructor - * @extends {goog.debug.Error} - * @final + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact information object. */ -goog.fs.Error = function(error, action) { - /** @type {string} */ - this.name; - - /** - * @type {goog.fs.Error.ErrorCode} - * @deprecated Use the 'name' or 'message' field instead. - */ - this.code; - - if (goog.isDef(error.name)) { - this.name = error.name; - // TODO(user): Remove warning suppression after JSCompiler stops - // firing a spurious warning here. - /** @suppress {deprecated} */ - this.code = goog.fs.Error.getCodeFromName_(error.name); - } else { - this.code = error.code; - this.name = goog.fs.Error.getNameFromCode_(error.code); - } - goog.fs.Error.base(this, 'constructor', - goog.string.subs('%s %s', this.name, action)); +ol.format.WMSCapabilities.readContactInformation_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType shpuld be ELEMENT'); + goog.asserts.assert(node.localName == 'ContactInformation', + 'localName should be ContactInformation'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_, + node, objectStack); }; -goog.inherits(goog.fs.Error, goog.debug.Error); /** - * Names of errors that may be thrown by the File API, the File System API, or - * the File Writer API. - * - * @see http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException - * @see http://www.w3.org/TR/file-system-api/#definitions - * @see http://dev.w3.org/2009/dap/file-system/file-writer.html#definitions - * @enum {string} + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact person object. */ -goog.fs.Error.ErrorName = { - ABORT: 'AbortError', - ENCODING: 'EncodingError', - INVALID_MODIFICATION: 'InvalidModificationError', - INVALID_STATE: 'InvalidStateError', - NOT_FOUND: 'NotFoundError', - NOT_READABLE: 'NotReadableError', - NO_MODIFICATION_ALLOWED: 'NoModificationAllowedError', - PATH_EXISTS: 'PathExistsError', - QUOTA_EXCEEDED: 'QuotaExceededError', - SECURITY: 'SecurityError', - SYNTAX: 'SyntaxError', - TYPE_MISMATCH: 'TypeMismatchError' +ol.format.WMSCapabilities.readContactPersonPrimary_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ContactPersonPrimary', + 'localName should be ContactPersonPrimary'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_, + node, objectStack); }; /** - * Error codes for file errors. - * @see http://www.w3.org/TR/file-system-api/#idl-def-FileException - * - * @enum {number} - * @deprecated Use the 'name' or 'message' attribute instead. - */ -goog.fs.Error.ErrorCode = { - NOT_FOUND: 1, - SECURITY: 2, - ABORT: 3, - NOT_READABLE: 4, - ENCODING: 5, - NO_MODIFICATION_ALLOWED: 6, - INVALID_STATE: 7, - SYNTAX: 8, - INVALID_MODIFICATION: 9, - QUOTA_EXCEEDED: 10, - TYPE_MISMATCH: 11, - PATH_EXISTS: 12 + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Object|undefined} Contact address object. + */ +ol.format.WMSCapabilities.readContactAddress_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'ContactAddress', + 'localName should be ContactAddress'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_, + node, objectStack); }; /** - * @param {goog.fs.Error.ErrorCode} code - * @return {string} name + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Array.<string>|undefined} Format array. */ -goog.fs.Error.getNameFromCode_ = function(code) { - var name = goog.object.findKey(goog.fs.Error.NameToCodeMap_, function(c) { - return code == c; - }); - if (!goog.isDef(name)) { - throw new Error('Invalid code: ' + code); - } - return name; +ol.format.WMSCapabilities.readException_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Exception', + 'localName should be Exception'); + return ol.xml.pushParseAndPop( + [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack); }; /** - * Returns the code that corresponds to the given name. - * @param {string} name - * @return {goog.fs.Error.ErrorCode} code + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. * @private + * @return {Object|undefined} Layer object. */ -goog.fs.Error.getCodeFromName_ = function(name) { - return goog.fs.Error.NameToCodeMap_[name]; +ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Layer', 'localName should be Layer'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack); }; /** - * Mapping from error names to values from the ErrorCode enum. - * @see http://www.w3.org/TR/file-system-api/#definitions. - * @private {!Object<string, goog.fs.Error.ErrorCode>} + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Layer object. */ -goog.fs.Error.NameToCodeMap_ = goog.object.create( - goog.fs.Error.ErrorName.ABORT, - goog.fs.Error.ErrorCode.ABORT, - - goog.fs.Error.ErrorName.ENCODING, - goog.fs.Error.ErrorCode.ENCODING, +ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Layer', 'localName should be Layer'); + var parentLayerObject = /** @type {Object.<string,*>} */ + (objectStack[objectStack.length - 1]); - goog.fs.Error.ErrorName.INVALID_MODIFICATION, - goog.fs.Error.ErrorCode.INVALID_MODIFICATION, + var layerObject = /** @type {Object.<string,*>} */ (ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack)); - goog.fs.Error.ErrorName.INVALID_STATE, - goog.fs.Error.ErrorCode.INVALID_STATE, + if (!goog.isDef(layerObject)) { + return undefined; + } + var queryable = + ol.format.XSD.readBooleanString(node.getAttribute('queryable')); + if (!goog.isDef(queryable)) { + queryable = parentLayerObject['queryable']; + } + layerObject['queryable'] = goog.isDef(queryable) ? queryable : false; - goog.fs.Error.ErrorName.NOT_FOUND, - goog.fs.Error.ErrorCode.NOT_FOUND, + var cascaded = ol.format.XSD.readNonNegativeIntegerString( + node.getAttribute('cascaded')); + if (!goog.isDef(cascaded)) { + cascaded = parentLayerObject['cascaded']; + } + layerObject['cascaded'] = cascaded; - goog.fs.Error.ErrorName.NOT_READABLE, - goog.fs.Error.ErrorCode.NOT_READABLE, + var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque')); + if (!goog.isDef(opaque)) { + opaque = parentLayerObject['opaque']; + } + layerObject['opaque'] = goog.isDef(opaque) ? opaque : false; - goog.fs.Error.ErrorName.NO_MODIFICATION_ALLOWED, - goog.fs.Error.ErrorCode.NO_MODIFICATION_ALLOWED, + var noSubsets = + ol.format.XSD.readBooleanString(node.getAttribute('noSubsets')); + if (!goog.isDef(noSubsets)) { + noSubsets = parentLayerObject['noSubsets']; + } + layerObject['noSubsets'] = goog.isDef(noSubsets) ? noSubsets : false; - goog.fs.Error.ErrorName.PATH_EXISTS, - goog.fs.Error.ErrorCode.PATH_EXISTS, + var fixedWidth = + ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth')); + if (!goog.isDef(fixedWidth)) { + fixedWidth = parentLayerObject['fixedWidth']; + } + layerObject['fixedWidth'] = fixedWidth; - goog.fs.Error.ErrorName.QUOTA_EXCEEDED, - goog.fs.Error.ErrorCode.QUOTA_EXCEEDED, + var fixedHeight = + ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight')); + if (!goog.isDef(fixedHeight)) { + fixedHeight = parentLayerObject['fixedHeight']; + } + layerObject['fixedHeight'] = fixedHeight; - goog.fs.Error.ErrorName.SECURITY, - goog.fs.Error.ErrorCode.SECURITY, + // See 7.2.4.8 + var addKeys = ['Style', 'CRS', 'AuthorityURL']; + goog.array.forEach(addKeys, function(key) { + var parentValue = parentLayerObject[key]; + if (goog.isDef(parentValue)) { + var childValue = goog.object.setIfUndefined(layerObject, key, []); + childValue = childValue.concat(parentValue); + layerObject[key] = childValue; + } + }); - goog.fs.Error.ErrorName.SYNTAX, - goog.fs.Error.ErrorCode.SYNTAX, + var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension', + 'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator']; + goog.array.forEach(replaceKeys, function(key) { + var childValue = layerObject[key]; + if (!goog.isDef(childValue)) { + var parentValue = parentLayerObject[key]; + layerObject[key] = parentValue; + } + }); - goog.fs.Error.ErrorName.TYPE_MISMATCH, - goog.fs.Error.ErrorCode.TYPE_MISMATCH); + return layerObject; +}; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A wrapper for the HTML5 File ProgressEvent objects. - * + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object} Dimension object. */ -goog.provide('goog.fs.ProgressEvent'); +ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Dimension', + 'localName should be Dimension'); + var dimensionObject = { + 'name': node.getAttribute('name'), + 'units': node.getAttribute('units'), + 'unitSymbol': node.getAttribute('unitSymbol'), + 'default': node.getAttribute('default'), + 'multipleValues': ol.format.XSD.readBooleanString( + node.getAttribute('multipleValues')), + 'nearestValue': ol.format.XSD.readBooleanString( + node.getAttribute('nearestValue')), + 'current': ol.format.XSD.readBooleanString(node.getAttribute('current')), + 'values': ol.format.XSD.readString(node) + }; + return dimensionObject; +}; -goog.require('goog.events.Event'); +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Online resource object. + */ +ol.format.WMSCapabilities.readFormatOnlineresource_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_, + node, objectStack); +}; /** - * A wrapper for the progress events emitted by the File APIs. - * - * @param {!ProgressEvent} event The underlying event object. - * @param {!Object} target The file access object emitting the event. - * @extends {goog.events.Event} - * @constructor - * @final + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Request object. */ -goog.fs.ProgressEvent = function(event, target) { - goog.fs.ProgressEvent.base(this, 'constructor', event.type, target); - - /** - * The underlying event object. - * @type {!ProgressEvent} - * @private - */ - this.event_ = event; +ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Request', + 'localName should be Request'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack); }; -goog.inherits(goog.fs.ProgressEvent, goog.events.Event); /** - * @return {boolean} Whether or not the total size of the of the file being - * saved is known. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} DCP type object. */ -goog.fs.ProgressEvent.prototype.isLengthComputable = function() { - return this.event_.lengthComputable; +ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'DCPType', + 'localName should be DCPType'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack); }; /** - * @return {number} The number of bytes saved so far. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} HTTP object. */ -goog.fs.ProgressEvent.prototype.getLoaded = function() { - return this.event_.loaded; +ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'HTTP', 'localName should be HTTP'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack); }; /** - * @return {number} The total number of bytes in the file being saved. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Operation type object. */ -goog.fs.ProgressEvent.prototype.getTotal = function() { - return this.event_.total; +ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack); }; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A wrapper for the HTML5 FileReader object. - * + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Online resource object. */ +ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = + function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + var formatOnlineresource = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (goog.isDef(formatOnlineresource)) { + var size = [ + ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')), + ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height')) + ]; + formatOnlineresource['size'] = size; + return formatOnlineresource; + } + return undefined; +}; -goog.provide('goog.fs.FileReader'); -goog.provide('goog.fs.FileReader.EventType'); -goog.provide('goog.fs.FileReader.ReadyState'); - -goog.require('goog.async.Deferred'); -goog.require('goog.events.EventTarget'); -goog.require('goog.fs.Error'); -goog.require('goog.fs.ProgressEvent'); +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Authority URL object. + */ +ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'AuthorityURL', + 'localName should be AuthorityURL'); + var authorityObject = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (goog.isDef(authorityObject)) { + authorityObject['name'] = node.getAttribute('name'); + return authorityObject; + } + return undefined; +}; /** - * An object for monitoring the reading of files. This emits ProgressEvents of - * the types listed in {@link goog.fs.FileReader.EventType}. - * - * @constructor - * @extends {goog.events.EventTarget} - * @final + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Metadata URL object. */ -goog.fs.FileReader = function() { - goog.fs.FileReader.base(this, 'constructor'); +ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'MetadataURL', + 'localName should be MetadataURL'); + var metadataObject = + ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack); + if (goog.isDef(metadataObject)) { + metadataObject['type'] = node.getAttribute('type'); + return metadataObject; + } + return undefined; +}; - /** - * The underlying FileReader object. - * - * @type {!FileReader} - * @private - */ - this.reader_ = new FileReader(); - this.reader_.onloadstart = goog.bind(this.dispatchProgressEvent_, this); - this.reader_.onprogress = goog.bind(this.dispatchProgressEvent_, this); - this.reader_.onload = goog.bind(this.dispatchProgressEvent_, this); - this.reader_.onabort = goog.bind(this.dispatchProgressEvent_, this); - this.reader_.onerror = goog.bind(this.dispatchProgressEvent_, this); - this.reader_.onloadend = goog.bind(this.dispatchProgressEvent_, this); +/** + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Style object. + */ +ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Style', 'localName should be Style'); + return ol.xml.pushParseAndPop( + {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack); }; -goog.inherits(goog.fs.FileReader, goog.events.EventTarget); /** - * Possible states for a FileReader. - * - * @enum {number} + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<string>|undefined} Keyword list. */ -goog.fs.FileReader.ReadyState = { - /** - * The object has been constructed, but there is no pending read. - */ - INIT: 0, - /** - * Data is being read. - */ - LOADING: 1, - /** - * The data has been read from the file, the read was aborted, or an error - * occurred. - */ - DONE: 2 +ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'KeywordList', + 'localName should be KeywordList'); + return ol.xml.pushParseAndPop( + [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack); }; /** - * Events emitted by a FileReader. - * - * @enum {string} + * @const + * @private + * @type {Array.<string>} */ -goog.fs.FileReader.EventType = { - /** - * Emitted when the reading begins. readyState will be LOADING. - */ - LOAD_START: 'loadstart', - /** - * Emitted when progress has been made in reading the file. readyState will be - * LOADING. - */ - PROGRESS: 'progress', - /** - * Emitted when the data has been successfully read. readyState will be - * LOADING. - */ - LOAD: 'load', - /** - * Emitted when the reading has been aborted. readyState will be LOADING. - */ - ABORT: 'abort', - /** - * Emitted when an error is encountered or the reading has been aborted. - * readyState will be LOADING. - */ - ERROR: 'error', - /** - * Emitted when the reading is finished, whether successfully or not. - * readyState will be DONE. - */ - LOAD_END: 'loadend' -}; +ol.format.WMSCapabilities.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/wms' +]; /** - * Abort the reading of the file. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.abort = function() { - try { - this.reader_.abort(); - } catch (e) { - throw new goog.fs.Error(e, 'aborting read'); - } -}; +ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Service': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readService_), + 'Capability': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readCapability_) + }); /** - * @return {goog.fs.FileReader.ReadyState} The current state of the FileReader. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.getReadyState = function() { - return /** @type {goog.fs.FileReader.ReadyState} */ (this.reader_.readyState); -}; +ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Request': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readRequest_), + 'Exception': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readException_), + 'Layer': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readCapabilityLayer_) + }); /** - * @return {*} The result of the file read. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.getResult = function() { - return this.reader_.result; -}; +ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'KeywordList': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readKeywordList_), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref), + 'ContactInformation': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactInformation_), + 'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'AccessConstraints': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'LayerLimit': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MaxHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }); /** - * @return {goog.fs.Error} The error encountered while reading, if any. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.getError = function() { - return this.reader_.error && - new goog.fs.Error(this.reader_.error, 'reading file'); -}; +ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'ContactPersonPrimary': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactPersonPrimary_), + 'ContactPosition': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactAddress': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readContactAddress_), + 'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); /** - * Wrap a progress event emitted by the underlying file reader and re-emit it. - * - * @param {!ProgressEvent} event The underlying event. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -goog.fs.FileReader.prototype.dispatchProgressEvent_ = function(event) { - this.dispatchEvent(new goog.fs.ProgressEvent(event, this)); -}; +ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'ContactPerson': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'ContactOrganization': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); -/** @override */ -goog.fs.FileReader.prototype.disposeInternal = function() { - goog.fs.FileReader.base(this, 'disposeInternal'); - delete this.reader_; -}; +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'StateOrProvince': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + }); + + +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString) + }); /** - * Starts reading a blob as a binary string. - * @param {!Blob} blob The blob to read. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.readAsBinaryString = function(blob) { - this.reader_.readAsBinaryString(blob); -}; +ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'KeywordList': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readKeywordList_), + 'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readEXGeographicBoundingBox_), + 'BoundingBox': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readBoundingBox_), + 'Dimension': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readDimension_), + 'Attribution': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readAttribution_), + 'AuthorityURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readAuthorityURL_), + 'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'MetadataURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readMetadataURL_), + 'DataURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'FeatureListURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'Style': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readStyle_), + 'MinScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'MaxScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'Layer': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readLayer_) + }); /** - * Reads a blob as a binary string. - * @param {!Blob} blob The blob to read. - * @return {!goog.async.Deferred} The deferred Blob contents as a binary string. - * If an error occurs, the errback is called with a {@link goog.fs.Error}. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.readAsBinaryString = function(blob) { - var reader = new goog.fs.FileReader(); - var d = goog.fs.FileReader.createDeferred_(reader); - reader.readAsBinaryString(blob); - return d; -}; +ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref), + 'LogoURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readSizedFormatOnlineresource_) + }); /** - * Starts reading a blob as an array buffer. - * @param {!Blob} blob The blob to read. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.readAsArrayBuffer = function(blob) { - this.reader_.readAsArrayBuffer(blob); -}; +ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ = + ol.xml.makeParsersNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'westBoundLongitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'eastBoundLongitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'southBoundLatitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'northBoundLatitude': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal) + }); /** - * Reads a blob as an array buffer. - * @param {!Blob} blob The blob to read. - * @return {!goog.async.Deferred} The deferred Blob contents as an array buffer. - * If an error occurs, the errback is called with a {@link goog.fs.Error}. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.readAsArrayBuffer = function(blob) { - var reader = new goog.fs.FileReader(); - var d = goog.fs.FileReader.createDeferred_(reader); - reader.readAsArrayBuffer(blob); - return d; -}; +ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'GetCapabilities': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_), + 'GetMap': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_), + 'GetFeatureInfo': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readOperationType_) + }); /** - * Starts reading a blob as text. - * @param {!Blob} blob The blob to read. - * @param {string=} opt_encoding The name of the encoding to use. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.readAsText = function(blob, opt_encoding) { - this.reader_.readAsText(blob, opt_encoding); -}; +ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString), + 'DCPType': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readDCPType_) + }); /** - * Reads a blob as text. - * @param {!Blob} blob The blob to read. - * @param {string=} opt_encoding The name of the encoding to use. - * @return {!goog.async.Deferred} The deferred Blob contents as text. - * If an error occurs, the errback is called with a {@link goog.fs.Error}. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.readAsText = function(blob, opt_encoding) { - var reader = new goog.fs.FileReader(); - var d = goog.fs.FileReader.createDeferred_(reader); - reader.readAsText(blob, opt_encoding); - return d; -}; +ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'HTTP': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readHTTP_) + }); /** - * Starts reading a blob as a data URL. - * @param {!Blob} blob The blob to read. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.prototype.readAsDataUrl = function(blob) { - this.reader_.readAsDataURL(blob); -}; +ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Get': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'Post': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_) + }); /** - * Reads a blob as a data URL. - * @param {!Blob} blob The blob to read. - * @return {!goog.async.Deferred} The deferred Blob contents as a data URL. - * If an error occurs, the errback is called with a {@link goog.fs.Error}. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.fs.FileReader.readAsDataUrl = function(blob) { - var reader = new goog.fs.FileReader(); - var d = goog.fs.FileReader.createDeferred_(reader); - reader.readAsDataUrl(blob); - return d; -}; +ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'LegendURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMSCapabilities.readSizedFormatOnlineresource_), + 'StyleSheetURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_), + 'StyleURL': ol.xml.makeObjectPropertySetter( + ol.format.WMSCapabilities.readFormatOnlineresource_) + }); /** - * Creates a new deferred object for the results of a read method. - * @param {goog.fs.FileReader} reader The reader to create a deferred for. - * @return {!goog.async.Deferred} The deferred results. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} * @private */ -goog.fs.FileReader.createDeferred_ = function(reader) { - var deferred = new goog.async.Deferred(); - reader.listen(goog.fs.FileReader.EventType.LOAD_END, - goog.partial(function(d, r, e) { - var result = r.getResult(); - var error = r.getError(); - if (result != null && !error) { - d.callback(result); - } else { - d.errback(error); - } - r.dispose(); - }, deferred, reader)); - return deferred; -}; +ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'OnlineResource': ol.xml.makeObjectPropertySetter( + ol.format.XLink.readHref) + }); -// FIXME should handle all geo-referenced data, not just vector data -goog.provide('ol.interaction.DragAndDrop'); -goog.provide('ol.interaction.DragAndDropEvent'); +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMSCapabilities.NAMESPACE_URIS_, { + 'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString) + }); +goog.provide('ol.format.WMSGetFeatureInfo'); + +goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('goog.events.FileDropHandler'); -goog.require('goog.events.FileDropHandler.EventType'); -goog.require('goog.fs.FileReader'); -goog.require('goog.functions'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.proj'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('ol.format.GML'); +goog.require('ol.format.GML2'); +goog.require('ol.format.XMLFeature'); +goog.require('ol.xml'); /** * @classdesc - * Handles input of vector data by drag and drop. + * Format for reading WMSGetFeatureInfo format. It uses + * {@link ol.format.GML2} to read features. * * @constructor - * @extends {ol.interaction.Interaction} - * @fires ol.interaction.DragAndDropEvent - * @param {olx.interaction.DragAndDropOptions=} opt_options Options. - * @api stable + * @extends {ol.format.XMLFeature} + * @api */ -ol.interaction.DragAndDrop = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, { - handleEvent: ol.interaction.DragAndDrop.handleEvent - }); - - /** - * @private - * @type {Array.<function(new: ol.format.Feature)>} - */ - this.formatConstructors_ = goog.isDef(options.formatConstructors) ? - options.formatConstructors : []; +ol.format.WMSGetFeatureInfo = function() { /** * @private - * @type {ol.proj.Projection} + * @type {string} */ - this.projection_ = goog.isDef(options.projection) ? - ol.proj.get(options.projection) : null; + this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver'; - /** - * @private - * @type {goog.events.FileDropHandler} - */ - this.fileDropHandler_ = null; /** * @private - * @type {goog.events.Key|undefined} + * @type {ol.format.GML2} */ - this.dropListenKey_ = undefined; + this.gmlFormat_ = new ol.format.GML2(); + goog.base(this); }; -goog.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction); +goog.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature); /** - * @inheritDoc + * @const + * @type {string} + * @private */ -ol.interaction.DragAndDrop.prototype.disposeInternal = function() { - if (goog.isDef(this.dropListenKey_)) { - goog.events.unlistenByKey(this.dropListenKey_); - } - goog.base(this, 'disposeInternal'); -}; +ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature'; /** - * @param {goog.events.BrowserEvent} event Event. + * @const + * @type {string} * @private */ -ol.interaction.DragAndDrop.prototype.handleDrop_ = function(event) { - var files = event.getBrowserEvent().dataTransfer.files; - var i, ii, file; - for (i = 0, ii = files.length; i < ii; ++i) { - file = files[i]; - // The empty string param is a workaround for - // https://code.google.com/p/closure-library/issues/detail?id=524 - var reader = goog.fs.FileReader.readAsText(file, ''); - reader.addCallback(goog.partial(this.handleResult_, file), this); - } -}; +ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer'; /** - * @param {File} file File. - * @param {string} result Result. + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Array.<ol.Feature>} Features. * @private */ -ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, result) { - var map = this.getMap(); - goog.asserts.assert(!goog.isNull(map)); - var projection = this.projection_; - if (goog.isNull(projection)) { - var view = map.getView(); - goog.asserts.assert(!goog.isNull(view)); - projection = view.getProjection(); - goog.asserts.assert(goog.isDef(projection)); - } - var formatConstructors = this.formatConstructors_; +ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = + function(node, objectStack) { + + node.namespaceURI = this.featureNS_; + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + var localName = ol.xml.getLocalName(node); + /** @type {Array.<ol.Feature>} */ var features = []; - var i, ii; - for (i = 0, ii = formatConstructors.length; i < ii; ++i) { - var formatConstructor = formatConstructors[i]; - var format = new formatConstructor(); - var readFeatures = this.tryReadFeatures_(format, result); - if (!goog.isNull(readFeatures)) { - var featureProjection = format.readProjection(result); - var transform = ol.proj.getTransform(featureProjection, projection); - var j, jj; - for (j = 0, jj = readFeatures.length; j < jj; ++j) { - var feature = readFeatures[j]; - var geometry = feature.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - geometry.applyTransform(transform); - } - features.push(feature); - } - } + if (node.childNodes.length === 0) { + return features; } - this.dispatchEvent( - new ol.interaction.DragAndDropEvent( - ol.interaction.DragAndDropEventType.ADD_FEATURES, this, file, - features, projection)); -}; + if (localName == 'msGMLOutput') { + goog.array.forEach(node.childNodes, function(layer) { + if (layer.nodeType !== goog.dom.NodeType.ELEMENT) { + return; + } + var context = objectStack[0]; + goog.asserts.assert(goog.isObject(context), + 'context should be an Object'); + goog.asserts.assert(layer.localName.indexOf( + ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0, + 'localName of layer node should match layerIdentifier'); -/** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.DragAndDrop} - * @api - */ -ol.interaction.DragAndDrop.handleEvent = goog.functions.TRUE; + var featureType = goog.string.remove(layer.localName, + ol.format.WMSGetFeatureInfo.layerIdentifier_) + + ol.format.WMSGetFeatureInfo.featureIdentifier_; + context['featureType'] = featureType; + context['featureNS'] = this.featureNS_; -/** - * @inheritDoc - */ -ol.interaction.DragAndDrop.prototype.setMap = function(map) { - if (goog.isDef(this.dropListenKey_)) { - goog.events.unlistenByKey(this.dropListenKey_); - this.dropListenKey_ = undefined; - } - if (!goog.isNull(this.fileDropHandler_)) { - goog.dispose(this.fileDropHandler_); - this.fileDropHandler_ = null; + var parsers = {}; + parsers[featureType] = ol.xml.makeArrayPusher( + this.gmlFormat_.readFeatureElement, this.gmlFormat_); + var parsersNS = ol.xml.makeParsersNS( + [context['featureNS'], null], parsers); + layer.namespaceURI = this.featureNS_; + var layerFeatures = ol.xml.pushParseAndPop( + [], parsersNS, layer, objectStack, this.gmlFormat_); + if (goog.isDef(layerFeatures)) { + goog.array.extend(features, layerFeatures); + } + }, this); } - goog.asserts.assert(!goog.isDef(this.dropListenKey_)); - goog.base(this, 'setMap', map); - if (!goog.isNull(map)) { - this.fileDropHandler_ = new goog.events.FileDropHandler(map.getViewport()); - this.dropListenKey_ = goog.events.listen( - this.fileDropHandler_, goog.events.FileDropHandler.EventType.DROP, - this.handleDrop_, false, this); + if (localName == 'FeatureCollection') { + var gmlFeatures = ol.xml.pushParseAndPop([], + this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node, + [{}], this.gmlFormat_); + if (goog.isDef(gmlFeatures)) { + features = gmlFeatures; + } } + return features; }; /** - * @param {ol.format.Feature} format Format. - * @param {string} text Text. - * @private + * Read all features from a WMSGetFeatureInfo response. + * + * @function + * @param {Document|Node|Object|string} source Source. + * @param {olx.format.ReadOptions=} opt_options Options. * @return {Array.<ol.Feature>} Features. + * @api stable */ -ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text) { - try { - return format.readFeatures(text); - } catch (e) { - return null; - } -}; - - -/** - * @enum {string} - */ -ol.interaction.DragAndDropEventType = { - /** - * Triggered when features are added - * @event ol.interaction.DragAndDropEvent#addfeatures - * @api stable - */ - ADD_FEATURES: 'addfeatures' -}; - +ol.format.WMSGetFeatureInfo.prototype.readFeatures; /** - * @classdesc - * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances - * of this type. - * - * @constructor - * @extends {goog.events.Event} - * @implements {oli.interaction.DragAndDropEvent} - * @param {ol.interaction.DragAndDropEventType} type Type. - * @param {Object} target Target. - * @param {File} file File. - * @param {Array.<ol.Feature>=} opt_features Features. - * @param {ol.proj.Projection=} opt_projection Projection. + * @inheritDoc */ -ol.interaction.DragAndDropEvent = - function(type, target, file, opt_features, opt_projection) { - - goog.base(this, type, target); - - /** - * @type {Array.<ol.Feature>|undefined} - * @api stable - */ - this.features = opt_features; - - /** - * @type {File} - * @api stable - */ - this.file = file; - - /** - * @type {ol.proj.Projection|undefined} - * @api - */ - this.projection = opt_projection; - +ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = + function(node, opt_options) { + var options = { + 'featureType': this.featureType, + 'featureNS': this.featureNS + }; + if (goog.isDef(opt_options)) { + goog.object.extend(options, this.getReadOptions(node, opt_options)); + } + return this.readFeatures_(node, [options]); }; -goog.inherits(ol.interaction.DragAndDropEvent, goog.events.Event); - -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Defines a 2-element vector class that can be used for - * coordinate math, useful for animation systems and point manipulation. - * - * Vec2 objects inherit from goog.math.Coordinate and may be used wherever a - * Coordinate is required. Where appropriate, Vec2 functions accept both Vec2 - * and Coordinate objects as input. - * - * @author brenneman@google.com (Shawn Brenneman) - */ -goog.provide('goog.math.Vec2'); +goog.provide('ol.format.WMTSCapabilities'); -goog.require('goog.math'); -goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); +goog.require('goog.dom.NodeType'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('ol.extent'); +goog.require('ol.format.OWS'); +goog.require('ol.format.XLink'); +goog.require('ol.format.XML'); +goog.require('ol.format.XSD'); +goog.require('ol.xml'); /** - * Class for a two-dimensional vector object and assorted functions useful for - * manipulating points. + * @classdesc + * Format for reading WMTS capabilities data. * - * @param {number} x The x coordinate for the vector. - * @param {number} y The y coordinate for the vector. * @constructor - * @extends {goog.math.Coordinate} + * @extends {ol.format.XML} + * @api */ -goog.math.Vec2 = function(x, y) { - /** - * X-value - * @type {number} - */ - this.x = x; +ol.format.WMTSCapabilities = function() { + goog.base(this); /** - * Y-value - * @type {number} + * @type {ol.format.OWS} + * @private */ - this.y = y; + this.owsParser_ = new ol.format.OWS(); }; -goog.inherits(goog.math.Vec2, goog.math.Coordinate); +goog.inherits(ol.format.WMTSCapabilities, ol.format.XML); /** - * @return {!goog.math.Vec2} A random unit-length vector. + * Read a WMTS capabilities document. + * + * @function + * @param {Document|Node|string} source The XML source. + * @return {Object} An object representing the WMTS capabilities. + * @api */ -goog.math.Vec2.randomUnit = function() { - var angle = Math.random() * Math.PI * 2; - return new goog.math.Vec2(Math.cos(angle), Math.sin(angle)); -}; +ol.format.WMTSCapabilities.prototype.read; /** - * @return {!goog.math.Vec2} A random vector inside the unit-disc. + * @param {Document} doc Document. + * @return {Object} WMTS Capability object. */ -goog.math.Vec2.random = function() { - var mag = Math.sqrt(Math.random()); - var angle = Math.random() * Math.PI * 2; - - return new goog.math.Vec2(Math.cos(angle) * mag, Math.sin(angle) * mag); +ol.format.WMTSCapabilities.prototype.readFromDocument = function(doc) { + goog.asserts.assert(doc.nodeType == goog.dom.NodeType.DOCUMENT, + 'doc.nodeType should be DOCUMENT'); + for (var n = doc.firstChild; !goog.isNull(n); n = n.nextSibling) { + if (n.nodeType == goog.dom.NodeType.ELEMENT) { + return this.readFromNode(n); + } + } + return null; }; /** - * Returns a new Vec2 object from a given coordinate. - * @param {!goog.math.Coordinate} a The coordinate. - * @return {!goog.math.Vec2} A new vector object. + * @param {Node} node Node. + * @return {Object} WMTS Capability object. */ -goog.math.Vec2.fromCoordinate = function(a) { - return new goog.math.Vec2(a.x, a.y); +ol.format.WMTSCapabilities.prototype.readFromNode = function(node) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Capabilities', + 'localName should be Capabilities'); + this.version = goog.string.trim(node.getAttribute('version')); + goog.asserts.assertString(this.version, 'this.version should be a string'); + var WMTSCapabilityObject = this.owsParser_.readFromNode(node); + if (!goog.isDef(WMTSCapabilityObject)) { + return null; + } + goog.object.set(WMTSCapabilityObject, 'version', this.version); + WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject, + ol.format.WMTSCapabilities.PARSERS_, node, []); + return goog.isDef(WMTSCapabilityObject) ? WMTSCapabilityObject : null; }; /** - * @return {!goog.math.Vec2} A new vector with the same coordinates as this one. - * @override + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Attribution object. */ -goog.math.Vec2.prototype.clone = function() { - return new goog.math.Vec2(this.x, this.y); +ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Contents', + 'localName should be Contents'); + + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack); }; /** - * Returns the magnitude of the vector measured from the origin. - * @return {number} The length of the vector. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Layers object. */ -goog.math.Vec2.prototype.magnitude = function() { - return Math.sqrt(this.x * this.x + this.y * this.y); +ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT, + 'node.nodeType should be ELEMENT'); + goog.asserts.assert(node.localName == 'Layer', 'localName should be Layer'); + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack); }; /** - * Returns the squared magnitude of the vector measured from the origin. - * NOTE(brenneman): Leaving out the square root is not a significant - * optimization in JavaScript. - * @return {number} The length of the vector, squared. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Tile Matrix Set object. */ -goog.math.Vec2.prototype.squaredMagnitude = function() { - return this.x * this.x + this.y * this.y; +ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack); }; /** - * @return {!goog.math.Vec2} This coordinate after scaling. - * @override + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Style object. */ -goog.math.Vec2.prototype.scale = - /** @type {function(number, number=):!goog.math.Vec2} */ - (goog.math.Coordinate.prototype.scale); +ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) { + var style = ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack); + if (!goog.isDef(style)) { + return undefined; + } + var isDefault = node.getAttribute('isDefault') === 'true'; + style['isDefault'] = isDefault; + return style; + +}; /** - * Reverses the sign of the vector. Equivalent to scaling the vector by -1. - * @return {!goog.math.Vec2} The inverted vector. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Tile Matrix Set Link object. */ -goog.math.Vec2.prototype.invert = function() { - this.x = -this.x; - this.y = -this.y; - return this; +ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node, + objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack); }; /** - * Normalizes the current vector to have a magnitude of 1. - * @return {!goog.math.Vec2} The normalized vector. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Resource URL object. */ -goog.math.Vec2.prototype.normalize = function() { - return this.scale(1 / this.magnitude()); +ol.format.WMTSCapabilities.readResourceUrl_ = function(node, objectStack) { + var format = node.getAttribute('format'); + var template = node.getAttribute('template'); + var resourceType = node.getAttribute('resourceType'); + var resource = {}; + if (goog.isDef(format)) { + resource['format'] = format; + } + if (goog.isDef(template)) { + resource['template'] = template; + } + if (goog.isDef(resourceType)) { + resource['resourceType'] = resourceType; + } + return resource; }; /** - * Adds another vector to this vector in-place. - * @param {!goog.math.Coordinate} b The vector to add. - * @return {!goog.math.Vec2} This vector with {@code b} added. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} WGS84 BBox object. */ -goog.math.Vec2.prototype.add = function(b) { - this.x += b.x; - this.y += b.y; - return this; +ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) { + var coordinates = ol.xml.pushParseAndPop([], + ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack); + if (coordinates.length != 2) { + return undefined; + } + return ol.extent.boundingExtent(coordinates); }; /** - * Subtracts another vector from this vector in-place. - * @param {!goog.math.Coordinate} b The vector to subtract. - * @return {!goog.math.Vec2} This vector with {@code b} subtracted. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Legend object. */ -goog.math.Vec2.prototype.subtract = function(b) { - this.x -= b.x; - this.y -= b.y; - return this; +ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) { + var legend = {}; + legend['format'] = node.getAttribute('format'); + legend['href'] = ol.format.XLink.readHref(node); + return legend; }; /** - * Rotates this vector in-place by a given angle, specified in radians. - * @param {number} angle The angle, in radians. - * @return {!goog.math.Vec2} This vector rotated {@code angle} radians. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} Coordinates object. */ -goog.math.Vec2.prototype.rotate = function(angle) { - var cos = Math.cos(angle); - var sin = Math.sin(angle); - var newX = this.x * cos - this.y * sin; - var newY = this.y * cos + this.x * sin; - this.x = newX; - this.y = newY; - return this; +ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) { + var coordinates = ol.format.XSD.readString(node).split(' '); + if (!goog.isDef(coordinates) || coordinates.length != 2) { + return undefined; + } + var x = +coordinates[0]; + var y = +coordinates[1]; + if (isNaN(x) || isNaN(y)) { + return undefined; + } + return [x, y]; }; /** - * Rotates a vector by a given angle, specified in radians, relative to a given - * axis rotation point. The returned vector is a newly created instance - no - * in-place changes are done. - * @param {!goog.math.Vec2} v A vector. - * @param {!goog.math.Vec2} axisPoint The rotation axis point. - * @param {number} angle The angle, in radians. - * @return {!goog.math.Vec2} The rotated vector in a newly created instance. + * @private + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @return {Object|undefined} TileMatrix object. */ -goog.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) { - var res = v.clone(); - return res.subtract(axisPoint).rotate(angle).add(axisPoint); +ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) { + return ol.xml.pushParseAndPop({}, + ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack); }; /** - * Compares this vector with another for equality. - * @param {!goog.math.Vec2} b The other vector. - * @return {boolean} Whether this vector has the same x and y as the given - * vector. + * @const + * @private + * @type {Array.<string>} */ -goog.math.Vec2.prototype.equals = function(b) { - return this == b || !!b && this.x == b.x && this.y == b.y; -}; +ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/wmts/1.0' +]; /** - * Returns the distance between two vectors. - * @param {!goog.math.Coordinate} a The first vector. - * @param {!goog.math.Coordinate} b The second vector. - * @return {number} The distance. + * @const + * @private + * @type {Array.<string>} */ -goog.math.Vec2.distance = goog.math.Coordinate.distance; +ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [ + null, + 'http://www.opengis.net/ows/1.1' +]; /** - * Returns the squared distance between two vectors. - * @param {!goog.math.Coordinate} a The first vector. - * @param {!goog.math.Coordinate} b The second vector. - * @return {number} The squared distance. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.math.Vec2.squaredDistance = goog.math.Coordinate.squaredDistance; +ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Contents': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readContents_) + }); /** - * Compares vectors for equality. - * @param {!goog.math.Coordinate} a The first vector. - * @param {!goog.math.Coordinate} b The second vector. - * @return {boolean} Whether the vectors have the same x and y coordinates. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.math.Vec2.equals = goog.math.Coordinate.equals; +ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Layer': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readLayer_), + 'TileMatrixSet': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrixSet_) + }); /** - * Returns the sum of two vectors as a new Vec2. - * @param {!goog.math.Coordinate} a The first vector. - * @param {!goog.math.Coordinate} b The second vector. - * @return {!goog.math.Vec2} The sum vector. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.math.Vec2.sum = function(a, b) { - return new goog.math.Vec2(a.x + b.x, a.y + b.y); -}; +ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'Style': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readStyle_), + 'Format': ol.xml.makeObjectPropertyPusher( + ol.format.XSD.readString), + 'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrixSetLink_), + 'ResourceURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readResourceUrl_) + }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Abstract': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'WGS84BoundingBox': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readWgs84BoundingBox_), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); /** - * Returns the difference between two vectors as a new Vec2. - * @param {!goog.math.Coordinate} a The first vector. - * @param {!goog.math.Coordinate} b The second vector. - * @return {!goog.math.Vec2} The difference vector. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.math.Vec2.difference = function(a, b) { - return new goog.math.Vec2(a.x - b.x, a.y - b.y); -}; +ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'LegendURL': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readLegendUrl_) + }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Title': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); /** - * Returns the dot-product of two vectors. - * @param {!goog.math.Coordinate} a The first vector. - * @param {!goog.math.Coordinate} b The second vector. - * @return {number} The dot-product of the two vectors. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.math.Vec2.dot = function(a, b) { - return a.x * b.x + a.y * b.y; -}; +ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TileMatrixSet': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + }); /** - * Returns a new Vec2 that is the linear interpolant between vectors a and b at - * scale-value x. - * @param {!goog.math.Coordinate} a Vector a. - * @param {!goog.math.Coordinate} b Vector b. - * @param {number} x The proportion between a and b. - * @return {!goog.math.Vec2} The interpolated vector. + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -goog.math.Vec2.lerp = function(a, b, x) { - return new goog.math.Vec2(goog.math.lerp(a.x, b.x, x), - goog.math.lerp(a.y, b.y, x)); -}; - -goog.provide('ol.interaction.DragRotateAndZoom'); +ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'LowerCorner': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readCoordinates_), + 'UpperCorner': ol.xml.makeArrayPusher( + ol.format.WMTSCapabilities.readCoordinates_) + }); -goog.require('goog.asserts'); -goog.require('goog.math.Vec2'); -goog.require('ol'); -goog.require('ol.ViewHint'); -goog.require('ol.events.ConditionType'); -goog.require('ol.events.condition'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.interaction.Pointer'); +/** + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private + */ +ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'WellKnownScaleSet': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'TileMatrix': ol.xml.makeObjectPropertyPusher( + ol.format.WMTSCapabilities.readTileMatrix_) + }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'SupportedCRS': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString), + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); /** - * @classdesc - * Allows the user to zoom and rotate the map by clicking and dragging - * on the map. By default, this interaction is limited to when the shift - * key is held down. - * - * This interaction is only supported for mouse devices. - * - * And this interaction is not included in the default interactions. - * - * @constructor - * @extends {ol.interaction.Pointer} - * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options. - * @api stable + * @const + * @type {Object.<string, Object.<string, ol.xml.Parser>>} + * @private */ -ol.interaction.DragRotateAndZoom = function(opt_options) { +ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeParsersNS( + ol.format.WMTSCapabilities.NAMESPACE_URIS_, { + 'TopLeftCorner': ol.xml.makeObjectPropertySetter( + ol.format.WMTSCapabilities.readCoordinates_), + 'ScaleDenominator': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readDecimal), + 'TileWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'TileHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MatrixWidth': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger), + 'MatrixHeight': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readNonNegativeInteger) + }, ol.xml.makeParsersNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, { + 'Identifier': ol.xml.makeObjectPropertySetter( + ol.format.XSD.readString) + })); - var options = goog.isDef(opt_options) ? opt_options : {}; +goog.provide('ol.sphere.WGS84'); - goog.base(this, { - handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_, - handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_, - handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_ - }); +goog.require('ol.Sphere'); - /** - * @private - * @type {ol.events.ConditionType} - */ - this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.events.condition.shiftKeyOnly; - /** - * @private - * @type {number|undefined} - */ - this.lastAngle_ = undefined; +/** + * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid. + * @const + * @type {ol.Sphere} + */ +ol.sphere.WGS84 = new ol.Sphere(6378137); - /** - * @private - * @type {number|undefined} - */ - this.lastMagnitude_ = undefined; +// FIXME handle geolocation not supported - /** - * @private - * @type {number} - */ - this.lastScaleDelta_ = 0; +goog.provide('ol.Geolocation'); +goog.provide('ol.GeolocationProperty'); -}; -goog.inherits(ol.interaction.DragRotateAndZoom, - ol.interaction.Pointer); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.math'); +goog.require('ol.Coordinate'); +goog.require('ol.Object'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.Polygon'); +goog.require('ol.has'); +goog.require('ol.proj'); +goog.require('ol.sphere.WGS84'); /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @this {ol.interaction.DragRotateAndZoom} - * @private + * @enum {string} */ -ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return; - } - - var map = mapBrowserEvent.map; - var size = map.getSize(); - var offset = mapBrowserEvent.pixel; - var delta = new goog.math.Vec2( - offset[0] - size[0] / 2, - size[1] / 2 - offset[1]); - var theta = Math.atan2(delta.y, delta.x); - var magnitude = delta.magnitude(); - var view = map.getView(); - var viewState = view.getState(); - map.render(); - if (goog.isDef(this.lastAngle_)) { - var angleDelta = theta - this.lastAngle_; - ol.interaction.Interaction.rotateWithoutConstraints( - map, view, viewState.rotation - angleDelta); - } - this.lastAngle_ = theta; - if (goog.isDef(this.lastMagnitude_)) { - var resolution = this.lastMagnitude_ * (viewState.resolution / magnitude); - ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution); - } - if (goog.isDef(this.lastMagnitude_)) { - this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; - } - this.lastMagnitude_ = magnitude; +ol.GeolocationProperty = { + ACCURACY: 'accuracy', + ACCURACY_GEOMETRY: 'accuracyGeometry', + ALTITUDE: 'altitude', + ALTITUDE_ACCURACY: 'altitudeAccuracy', + HEADING: 'heading', + POSITION: 'position', + PROJECTION: 'projection', + SPEED: 'speed', + TRACKING: 'tracking', + TRACKING_OPTIONS: 'trackingOptions' }; -/** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.DragRotateAndZoom} - * @private - */ -ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return true; - } - - var map = mapBrowserEvent.map; - var view = map.getView(); - view.setHint(ol.ViewHint.INTERACTING, -1); - var viewState = view.getState(); - var direction = this.lastScaleDelta_ - 1; - ol.interaction.Interaction.rotate(map, view, viewState.rotation); - ol.interaction.Interaction.zoom(map, view, viewState.resolution, - undefined, ol.DRAGROTATEANDZOOM_ANIMATION_DURATION, - direction); - this.lastScaleDelta_ = 0; - return false; -}; - /** - * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.DragRotateAndZoom} - * @private + * @classdesc + * Helper class for providing HTML5 Geolocation capabilities. + * The [Geolocation API](http://www.w3.org/TR/geolocation-API/) + * is used to locate a user's position. + * + * To get notified of position changes, register a listener for the generic + * `change` event on your instance of `ol.Geolocation`. + * + * Example: + * + * var geolocation = new ol.Geolocation({ + * // take the projection to use from the map's view + * projection: view.getProjection() + * }); + * // listen to changes in position + * geolocation.on('change', function(evt) { + * window.console.log(geolocation.getPosition()); + * }); + * + * @constructor + * @extends {ol.Object} + * @param {olx.GeolocationOptions=} opt_options Options. + * @api stable */ -ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) { - if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { - return false; - } +ol.Geolocation = function(opt_options) { - if (this.condition_(mapBrowserEvent)) { - mapBrowserEvent.map.getView().setHint(ol.ViewHint.INTERACTING, 1); - this.lastAngle_ = undefined; - this.lastMagnitude_ = undefined; - return true; - } else { - return false; - } -}; + goog.base(this); -goog.provide('ol.ext.rbush'); -/** @typedef {function(*)} */ -ol.ext.rbush; -(function() { -var exports = {}; -var module = {exports: exports}; -/** - * @fileoverview - * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} - */ -/* - (c) 2013, Vladimir Agafonkin - RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles. - https://github.com/mourner/rbush -*/ + var options = goog.isDef(opt_options) ? opt_options : {}; -(function () { 'use strict'; + /** + * The unprojected (EPSG:4326) device position. + * @private + * @type {ol.Coordinate} + */ + this.position_ = null; -function rbush(maxEntries, format) { + /** + * @private + * @type {ol.TransformFunction} + */ + this.transform_ = ol.proj.identityTransform; - // jshint newcap: false, validthis: true - if (!(this instanceof rbush)) return new rbush(maxEntries, format); + /** + * @private + * @type {number|undefined} + */ + this.watchId_ = undefined; - // max entries in a node is 9 by default; min node fill is 40% for best performance - this._maxEntries = Math.max(4, maxEntries || 9); - this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + goog.events.listen( + this, ol.Object.getChangeEventType(ol.GeolocationProperty.PROJECTION), + this.handleProjectionChanged_, false, this); + goog.events.listen( + this, ol.Object.getChangeEventType(ol.GeolocationProperty.TRACKING), + this.handleTrackingChanged_, false, this); - if (format) { - this._initFormat(format); - } + if (goog.isDef(options.projection)) { + this.setProjection(ol.proj.get(options.projection)); + } + if (goog.isDef(options.trackingOptions)) { + this.setTrackingOptions(options.trackingOptions); + } - this.clear(); -} + this.setTracking(goog.isDef(options.tracking) ? options.tracking : false); -rbush.prototype = { +}; +goog.inherits(ol.Geolocation, ol.Object); - all: function () { - return this._all(this.data, []); - }, - search: function (bbox) { +/** + * @inheritDoc + */ +ol.Geolocation.prototype.disposeInternal = function() { + this.setTracking(false); + goog.base(this, 'disposeInternal'); +}; - var node = this.data, - result = [], - toBBox = this.toBBox; - if (!intersects(bbox, node.bbox)) return result; +/** + * @private + */ +ol.Geolocation.prototype.handleProjectionChanged_ = function() { + var projection = this.getProjection(); + if (goog.isDefAndNotNull(projection)) { + this.transform_ = ol.proj.getTransformFromProjections( + ol.proj.get('EPSG:4326'), projection); + if (!goog.isNull(this.position_)) { + this.set( + ol.GeolocationProperty.POSITION, this.transform_(this.position_)); + } + } +}; - var nodesToSearch = [], - i, len, child, childBBox; - while (node) { - for (i = 0, len = node.children.length; i < len; i++) { +/** + * @private + */ +ol.Geolocation.prototype.handleTrackingChanged_ = function() { + if (ol.has.GEOLOCATION) { + var tracking = this.getTracking(); + if (tracking && !goog.isDef(this.watchId_)) { + this.watchId_ = goog.global.navigator.geolocation.watchPosition( + goog.bind(this.positionChange_, this), + goog.bind(this.positionError_, this), + this.getTrackingOptions()); + } else if (!tracking && goog.isDef(this.watchId_)) { + goog.global.navigator.geolocation.clearWatch(this.watchId_); + this.watchId_ = undefined; + } + } +}; - child = node.children[i]; - childBBox = node.leaf ? toBBox(child) : child.bbox; - if (intersects(bbox, childBBox)) { - if (node.leaf) result.push(child); - else if (contains(bbox, childBBox)) this._all(child, result); - else nodesToSearch.push(child); - } - } - node = nodesToSearch.pop(); - } +/** + * @private + * @param {GeolocationPosition} position position event. + */ +ol.Geolocation.prototype.positionChange_ = function(position) { + var coords = position.coords; + this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy); + this.set(ol.GeolocationProperty.ALTITUDE, + goog.isNull(coords.altitude) ? undefined : coords.altitude); + this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY, + goog.isNull(coords.altitudeAccuracy) ? + undefined : coords.altitudeAccuracy); + this.set(ol.GeolocationProperty.HEADING, goog.isNull(coords.heading) ? + undefined : goog.math.toRadians(coords.heading)); + if (goog.isNull(this.position_)) { + this.position_ = [coords.longitude, coords.latitude]; + } else { + this.position_[0] = coords.longitude; + this.position_[1] = coords.latitude; + } + var projectedPosition = this.transform_(this.position_); + this.set(ol.GeolocationProperty.POSITION, projectedPosition); + this.set(ol.GeolocationProperty.SPEED, + goog.isNull(coords.speed) ? undefined : coords.speed); + var geometry = ol.geom.Polygon.circular( + ol.sphere.WGS84, this.position_, coords.accuracy); + geometry.applyTransform(this.transform_); + this.set(ol.GeolocationProperty.ACCURACY_GEOMETRY, geometry); + this.changed(); +}; - return result; - }, - load: function (data) { - if (!(data && data.length)) return this; +/** + * @private + * @param {GeolocationPositionError} error error object. + */ +ol.Geolocation.prototype.positionError_ = function(error) { + error.type = goog.events.EventType.ERROR; + this.setTracking(false); + this.dispatchEvent(error); +}; - if (data.length < this._minEntries) { - for (var i = 0, len = data.length; i < len; i++) { - this.insert(data[i]); - } - return this; - } - // recursively build the tree with the given data from stratch using OMT algorithm - var node = this._build(data.slice(), 0, data.length - 1, 0); +/** + * Get the accuracy of the position in meters. + * @return {number|undefined} The accuracy of the position measurement in + * meters. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.ACCURACY)); +}; - if (!this.data.children.length) { - // save as is if tree is empty - this.data = node; - } else if (this.data.height === node.height) { - // split root if trees have the same height - this._splitRoot(this.data, node); +/** + * Get a geometry of the position accuracy. + * @return {?ol.geom.Geometry} A geometry of the position accuracy. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAccuracyGeometry = function() { + return /** @type {?ol.geom.Geometry} */ ( + this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null); +}; - } else { - if (this.data.height < node.height) { - // swap trees if inserted one is bigger - var tmpNode = this.data; - this.data = node; - node = tmpNode; - } - // insert the small tree into the large tree at appropriate level - this._insert(node, this.data.height - node.height - 1, true); - } +/** + * Get the altitude associated with the position. + * @return {number|undefined} The altitude of the position in meters above mean + * sea level. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAltitude = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.ALTITUDE)); +}; - return this; - }, - insert: function (item) { - if (item) this._insert(item, this.data.height - 1); - return this; - }, +/** + * Get the altitude accuracy of the position. + * @return {number|undefined} The accuracy of the altitude measurement in + * meters. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getAltitudeAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY)); +}; - clear: function () { - this.data = { - children: [], - height: 1, - bbox: empty(), - leaf: true - }; - return this; - }, - remove: function (item) { - if (!item) return this; +/** + * Get the heading as radians clockwise from North. + * @return {number|undefined} The heading of the device in radians from north. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.HEADING)); +}; - var node = this.data, - bbox = this.toBBox(item), - path = [], - indexes = [], - i, parent, index, goingUp; - // depth-first iterative tree traversal - while (node || path.length) { +/** + * Get the position of the device. + * @return {ol.Coordinate|undefined} The current position of the device reported + * in the current projection. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getPosition = function() { + return /** @type {ol.Coordinate|undefined} */ ( + this.get(ol.GeolocationProperty.POSITION)); +}; - if (!node) { // go up - node = path.pop(); - parent = path[path.length - 1]; - i = indexes.pop(); - goingUp = true; - } - if (node.leaf) { // check current node - index = node.children.indexOf(item); +/** + * Get the projection associated with the position. + * @return {ol.proj.Projection|undefined} The projection the position is + * reported in. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getProjection = function() { + return /** @type {ol.proj.Projection|undefined} */ ( + this.get(ol.GeolocationProperty.PROJECTION)); +}; - if (index !== -1) { - // item found, remove the item and condense tree upwards - node.children.splice(index, 1); - path.push(node); - this._condense(path); - return this; - } - } - if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down - path.push(node); - indexes.push(i); - i = 0; - parent = node; - node = node.children[0]; +/** + * Get the speed in meters per second. + * @return {number|undefined} The instantaneous speed of the device in meters + * per second. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getSpeed = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.SPEED)); +}; - } else if (parent) { // go right - i++; - node = parent.children[i]; - goingUp = false; - } else node = null; // nothing found - } +/** + * Determine if the device location is being tracked. + * @return {boolean} The device location is being tracked. + * @observable + * @api stable + */ +ol.Geolocation.prototype.getTracking = function() { + return /** @type {boolean} */ ( + this.get(ol.GeolocationProperty.TRACKING)); +}; - return this; - }, - toBBox: function (item) { return item; }, +/** + * Get the tracking options. + * @see http://www.w3.org/TR/geolocation-API/#position-options + * @return {GeolocationPositionOptions|undefined} PositionOptions as defined by + * the [HTML5 Geolocation spec + * ](http://www.w3.org/TR/geolocation-API/#position_options_interface). + * @observable + * @api stable + */ +ol.Geolocation.prototype.getTrackingOptions = function() { + return /** @type {GeolocationPositionOptions|undefined} */ ( + this.get(ol.GeolocationProperty.TRACKING_OPTIONS)); +}; - compareMinX: function (a, b) { return a[0] - b[0]; }, - compareMinY: function (a, b) { return a[1] - b[1]; }, - toJSON: function () { return this.data; }, +/** + * Set the projection to use for transforming the coordinates. + * @param {ol.proj.Projection} projection The projection the position is + * reported in. + * @observable + * @api stable + */ +ol.Geolocation.prototype.setProjection = function(projection) { + this.set(ol.GeolocationProperty.PROJECTION, projection); +}; - fromJSON: function (data) { - this.data = data; - return this; - }, - _all: function (node, result) { - var nodesToSearch = []; - while (node) { - if (node.leaf) result.push.apply(result, node.children); - else nodesToSearch.push.apply(nodesToSearch, node.children); +/** + * Enable or disable tracking. + * @param {boolean} tracking Enable tracking. + * @observable + * @api stable + */ +ol.Geolocation.prototype.setTracking = function(tracking) { + this.set(ol.GeolocationProperty.TRACKING, tracking); +}; - node = nodesToSearch.pop(); - } - return result; - }, - _build: function (items, left, right, height) { +/** + * Set the tracking options. + * @see http://www.w3.org/TR/geolocation-API/#position-options + * @param {GeolocationPositionOptions} options PositionOptions as defined by the + * [HTML5 Geolocation spec + * ](http://www.w3.org/TR/geolocation-API/#position_options_interface). + * @observable + * @api stable + */ +ol.Geolocation.prototype.setTrackingOptions = function(options) { + this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options); +}; - var N = right - left + 1, - M = this._maxEntries, - node; +goog.provide('ol.geom.flat.geodesic'); - if (N <= M) { - // reached leaf level; return leaf - node = { - children: items.slice(left, right + 1), - height: 1, - bbox: null, - leaf: true - }; - calcBBox(node, this.toBBox); - return node; - } +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('goog.object'); +goog.require('ol.TransformFunction'); +goog.require('ol.math'); +goog.require('ol.proj'); - if (!height) { - // target height of the bulk-loaded tree - height = Math.ceil(Math.log(N) / Math.log(M)); - // target number of root entries to maximize storage utilization - M = Math.ceil(N / Math.pow(M, height - 1)); - } +/** + * @private + * @param {function(number): ol.Coordinate} interpolate Interpolate function. + * @param {ol.TransformFunction} transform Transform from longitude/latitude to + * projected coordinates. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.line_ = + function(interpolate, transform, squaredTolerance) { + // FIXME reduce garbage generation + // FIXME optimize stack operations - // TODO eliminate recursion? + /** @type {Array.<number>} */ + var flatCoordinates = []; - node = { - children: [], - height: height, - bbox: null - }; + var geoA = interpolate(0); + var geoB = interpolate(1); - // split the items into M mostly square tiles + var a = transform(geoA); + var b = transform(geoB); - var N2 = Math.ceil(N / M), - N1 = N2 * Math.ceil(Math.sqrt(M)), - i, j, right2, right3; + /** @type {Array.<ol.Coordinate>} */ + var geoStack = [geoB, geoA]; + /** @type {Array.<ol.Coordinate>} */ + var stack = [b, a]; + /** @type {Array.<number>} */ + var fractionStack = [1, 0]; - multiSelect(items, left, right, N1, this.compareMinX); + /** @type {Object.<string, boolean>} */ + var fractions = {}; - for (i = left; i <= right; i += N1) { + var maxIterations = 1e5; + var geoM, m, fracA, fracB, fracM, key; - right2 = Math.min(i + N1 - 1, right); + while (--maxIterations > 0 && fractionStack.length > 0) { + // Pop the a coordinate off the stack + fracA = fractionStack.pop(); + geoA = geoStack.pop(); + a = stack.pop(); + // Add the a coordinate if it has not been added yet + key = fracA.toString(); + if (!goog.object.containsKey(fractions, key)) { + flatCoordinates.push(a[0], a[1]); + fractions[key] = true; + } + // Pop the b coordinate off the stack + fracB = fractionStack.pop(); + geoB = geoStack.pop(); + b = stack.pop(); + // Find the m point between the a and b coordinates + fracM = (fracA + fracB) / 2; + geoM = interpolate(fracM); + m = transform(geoM); + if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1], + b[0], b[1]) < squaredTolerance) { + // If the m point is sufficiently close to the straight line, then we + // discard it. Just use the b coordinate and move on to the next line + // segment. + flatCoordinates.push(b[0], b[1]); + key = fracB.toString(); + goog.asserts.assert(!goog.object.containsKey(fractions, key), + 'fractions object should contain key : ' + key); + fractions[key] = true; + } else { + // Otherwise, we need to subdivide the current line segment. Split it + // into two and push the two line segments onto the stack. + fractionStack.push(fracB, fracM, fracM, fracA); + stack.push(b, m, m, a); + geoStack.push(geoB, geoM, geoM, geoA); + } + } + goog.asserts.assert(maxIterations > 0, + 'maxIterations should be more than 0'); - multiSelect(items, i, right2, N2, this.compareMinY); + return flatCoordinates; +}; - for (j = i; j <= right2; j += N2) { - right3 = Math.min(j + N2 - 1, right2); +/** +* Generate a great-circle arcs between two lat/lon points. +* @param {number} lon1 Longitude 1 in degrees. +* @param {number} lat1 Latitude 1 in degrees. +* @param {number} lon2 Longitude 2 in degrees. +* @param {number} lat2 Latitude 2 in degrees. + * @param {ol.proj.Projection} projection Projection. +* @param {number} squaredTolerance Squared tolerance. +* @return {Array.<number>} Flat coordinates. +*/ +ol.geom.flat.geodesic.greatCircleArc = function( + lon1, lat1, lon2, lat2, projection, squaredTolerance) { - // pack each entry recursively - node.children.push(this._build(items, j, right3, height - 1)); - } - } + var geoProjection = ol.proj.get('EPSG:4326'); - calcBBox(node, this.toBBox); + var cosLat1 = Math.cos(goog.math.toRadians(lat1)); + var sinLat1 = Math.sin(goog.math.toRadians(lat1)); + var cosLat2 = Math.cos(goog.math.toRadians(lat2)); + var sinLat2 = Math.sin(goog.math.toRadians(lat2)); + var cosDeltaLon = Math.cos(goog.math.toRadians(lon2 - lon1)); + var sinDeltaLon = Math.sin(goog.math.toRadians(lon2 - lon1)); + var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon; - return node; - }, + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + if (1 <= d) { + return [lon2, lat2]; + } + var D = frac * Math.acos(d); + var cosD = Math.cos(D); + var sinD = Math.sin(D); + var y = sinDeltaLon * cosLat2; + var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon; + var theta = Math.atan2(y, x); + var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta)); + var lon = goog.math.toRadians(lon1) + + Math.atan2(Math.sin(theta) * sinD * cosLat1, + cosD - sinLat1 * Math.sin(lat)); + return [goog.math.toDegrees(lon), goog.math.toDegrees(lat)]; + }, ol.proj.getTransform(geoProjection, projection), squaredTolerance); +}; - _chooseSubtree: function (bbox, node, level, path) { - var i, len, child, targetNode, area, enlargement, minArea, minEnlargement; +/** + * Generate a meridian (line at constant longitude). + * @param {number} lon Longitude. + * @param {number} lat1 Latitude 1. + * @param {number} lat2 Latitude 2. + * @param {ol.proj.Projection} projection Projection. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.meridian = + function(lon, lat1, lat2, projection, squaredTolerance) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + return [lon, lat1 + ((lat2 - lat1) * frac)]; + }, + ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +}; - while (true) { - path.push(node); - if (node.leaf || path.length - 1 === level) break; +/** + * Generate a parallel (line at constant latitude). + * @param {number} lat Latitude. + * @param {number} lon1 Longitude 1. + * @param {number} lon2 Longitude 2. + * @param {ol.proj.Projection} projection Projection. + * @param {number} squaredTolerance Squared tolerance. + * @return {Array.<number>} Flat coordinates. + */ +ol.geom.flat.geodesic.parallel = + function(lat, lon1, lon2, projection, squaredTolerance) { + var epsg4326Projection = ol.proj.get('EPSG:4326'); + return ol.geom.flat.geodesic.line_( + /** + * @param {number} frac Fraction. + * @return {ol.Coordinate} Coordinate. + */ + function(frac) { + return [lon1 + ((lon2 - lon1) * frac), lat]; + }, + ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance); +}; - minArea = minEnlargement = Infinity; +goog.provide('ol.Graticule'); - for (i = 0, len = node.children.length; i < len; i++) { - child = node.children[i]; - area = bboxArea(child.bbox); - enlargement = enlargedArea(bbox, child.bbox) - area; +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.flat.geodesic'); +goog.require('ol.proj'); +goog.require('ol.render.EventType'); +goog.require('ol.style.Stroke'); - // choose entry with the least area enlargement - if (enlargement < minEnlargement) { - minEnlargement = enlargement; - minArea = area < minArea ? area : minArea; - targetNode = child; - } else if (enlargement === minEnlargement) { - // otherwise choose one with the smallest area - if (area < minArea) { - minArea = area; - targetNode = child; - } - } - } - node = targetNode; - } +/** + * Render a grid for a coordinate system on a map. + * @constructor + * @param {olx.GraticuleOptions=} opt_options Options. + * @api + */ +ol.Graticule = function(opt_options) { - return node; - }, + var options = goog.isDef(opt_options) ? opt_options : {}; - _insert: function (item, level, isNode) { + /** + * @type {ol.Map} + * @private + */ + this.map_ = null; - var toBBox = this.toBBox, - bbox = isNode ? item.bbox : toBBox(item), - insertPath = []; + /** + * @type {ol.proj.Projection} + * @private + */ + this.projection_ = null; - // find the best node for accommodating the item, saving all nodes along the path too - var node = this._chooseSubtree(bbox, this.data, level, insertPath); + /** + * @type {number} + * @private + */ + this.maxLat_ = Infinity; - // put the item into the node - node.children.push(item); - extend(node.bbox, bbox); + /** + * @type {number} + * @private + */ + this.maxLon_ = Infinity; - // split on node overflow; propagate upwards if necessary - while (level >= 0) { - if (insertPath[level].children.length > this._maxEntries) { - this._split(insertPath, level); - level--; - } else break; - } + /** + * @type {number} + * @private + */ + this.minLat_ = -Infinity; - // adjust bboxes along the insertion path - this._adjustParentBBoxes(bbox, insertPath, level); - }, + /** + * @type {number} + * @private + */ + this.minLon_ = -Infinity; - // split overflowed node into two - _split: function (insertPath, level) { + /** + * @type {number} + * @private + */ + this.targetSize_ = goog.isDef(options.targetSize) ? + options.targetSize : 100; - var node = insertPath[level], - M = node.children.length, - m = this._minEntries; + /** + * @type {number} + * @private + */ + this.maxLines_ = goog.isDef(options.maxLines) ? options.maxLines : 100; + goog.asserts.assert(this.maxLines_ > 0, + 'this.maxLines_ should be more than 0'); - this._chooseSplitAxis(node, m, M); + /** + * @type {Array.<ol.geom.LineString>} + * @private + */ + this.meridians_ = []; - var newNode = { - children: node.children.splice(this._chooseSplitIndex(node, m, M)), - height: node.height - }; + /** + * @type {Array.<ol.geom.LineString>} + * @private + */ + this.parallels_ = []; - if (node.leaf) newNode.leaf = true; + /** + * @type {ol.style.Stroke} + * @private + */ + this.strokeStyle_ = goog.isDef(options.strokeStyle) ? + options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_; - calcBBox(node, this.toBBox); - calcBBox(newNode, this.toBBox); + /** + * @type {ol.TransformFunction|undefined} + * @private + */ + this.fromLonLatTransform_ = undefined; - if (level) insertPath[level - 1].children.push(newNode); - else this._splitRoot(node, newNode); - }, + /** + * @type {ol.TransformFunction|undefined} + * @private + */ + this.toLonLatTransform_ = undefined; - _splitRoot: function (node, newNode) { - // split root node - this.data = { - children: [node, newNode], - height: node.height + 1 - }; - calcBBox(this.data, this.toBBox); - }, + /** + * @type {ol.Coordinate} + * @private + */ + this.projectionCenterLonLat_ = null; - _chooseSplitIndex: function (node, m, M) { + this.setMap(goog.isDef(options.map) ? options.map : null); +}; - var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index; - minOverlap = minArea = Infinity; +/** + * @type {ol.style.Stroke} + * @private + * @const + */ +ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({ + color: 'rgba(0,0,0,0.2)' +}); - for (i = m; i <= M - m; i++) { - bbox1 = distBBox(node, 0, i, this.toBBox); - bbox2 = distBBox(node, i, M, this.toBBox); - overlap = intersectionArea(bbox1, bbox2); - area = bboxArea(bbox1) + bboxArea(bbox2); +/** + * TODO can be configurable + * @type {Array.<number>} + * @private + */ +ol.Graticule.intervals_ = [90, 45, 30, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, + 0.01, 0.005, 0.002, 0.001]; - // choose distribution with minimum overlap - if (overlap < minOverlap) { - minOverlap = overlap; - index = i; - minArea = area < minArea ? area : minArea; +/** + * @param {number} lon Longitude. + * @param {number} squaredTolerance Squared tolerance. + * @param {ol.Extent} extent Extent. + * @param {number} index Index. + * @return {number} Index. + * @private + */ +ol.Graticule.prototype.addMeridian_ = + function(lon, squaredTolerance, extent, index) { + var lineString = this.getMeridian_(lon, squaredTolerance, index); + if (ol.extent.intersects(lineString.getExtent(), extent)) { + this.meridians_[index++] = lineString; + } + return index; +}; - } else if (overlap === minOverlap) { - // otherwise choose distribution with minimum area - if (area < minArea) { - minArea = area; - index = i; - } - } - } - return index; - }, +/** + * @param {number} lat Latitude. + * @param {number} squaredTolerance Squared tolerance. + * @param {ol.Extent} extent Extent. + * @param {number} index Index. + * @return {number} Index. + * @private + */ +ol.Graticule.prototype.addParallel_ = + function(lat, squaredTolerance, extent, index) { + var lineString = this.getParallel_(lat, squaredTolerance, index); + if (ol.extent.intersects(lineString.getExtent(), extent)) { + this.parallels_[index++] = lineString; + } + return index; +}; - // sorts node children by the best axis for split - _chooseSplitAxis: function (node, m, M) { - var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, - compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, - xMargin = this._allDistMargin(node, m, M, compareMinX), - yMargin = this._allDistMargin(node, m, M, compareMinY); +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} squaredTolerance Squared tolerance. + * @private + */ +ol.Graticule.prototype.createGraticule_ = + function(extent, center, resolution, squaredTolerance) { - // if total distributions margin value is minimal for x, sort by minX, - // otherwise it's already sorted by minY - if (xMargin < yMargin) node.children.sort(compareMinX); - }, + var interval = this.getInterval_(resolution); + if (interval == -1) { + this.meridians_.length = this.parallels_.length = 0; + return; + } - // total margin of all possible split distributions where each node is at least m full - _allDistMargin: function (node, m, M, compare) { + var centerLonLat = this.toLonLatTransform_(center); + var centerLon = centerLonLat[0]; + var centerLat = centerLonLat[1]; + var maxLines = this.maxLines_; + var cnt, idx, lat, lon; - node.children.sort(compare); + // Create meridians - var toBBox = this.toBBox, - leftBBox = distBBox(node, 0, m, toBBox), - rightBBox = distBBox(node, M - m, M, toBBox), - margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), - i, child; + centerLon = Math.floor(centerLon / interval) * interval; + lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_); - for (i = m; i < M - m; i++) { - child = node.children[i]; - extend(leftBBox, node.leaf ? toBBox(child) : child.bbox); - margin += bboxMargin(leftBBox); - } + idx = this.addMeridian_(lon, squaredTolerance, extent, 0); - for (i = M - m - 1; i >= m; i--) { - child = node.children[i]; - extend(rightBBox, node.leaf ? toBBox(child) : child.bbox); - margin += bboxMargin(rightBBox); - } + cnt = 0; + while (lon != this.minLon_ && cnt++ < maxLines) { + lon = Math.max(lon - interval, this.minLon_); + idx = this.addMeridian_(lon, squaredTolerance, extent, idx); + } - return margin; - }, + lon = goog.math.clamp(centerLon, this.minLon_, this.maxLon_); - _adjustParentBBoxes: function (bbox, path, level) { - // adjust bboxes along the given tree path - for (var i = level; i >= 0; i--) { - extend(path[i].bbox, bbox); - } - }, + cnt = 0; + while (lon != this.maxLon_ && cnt++ < maxLines) { + lon = Math.min(lon + interval, this.maxLon_); + idx = this.addMeridian_(lon, squaredTolerance, extent, idx); + } - _condense: function (path) { - // go through the path, removing empty nodes and updating bboxes - for (var i = path.length - 1, siblings; i >= 0; i--) { - if (path[i].children.length === 0) { - if (i > 0) { - siblings = path[i - 1].children; - siblings.splice(siblings.indexOf(path[i]), 1); + this.meridians_.length = idx; - } else this.clear(); + // Create parallels - } else calcBBox(path[i], this.toBBox); - } - }, + centerLat = Math.floor(centerLat / interval) * interval; + lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_); - _initFormat: function (format) { - // data format (minX, minY, maxX, maxY accessors) + idx = this.addParallel_(lat, squaredTolerance, extent, 0); - // uses eval-type function compilation instead of just accepting a toBBox function - // because the algorithms are very sensitive to sorting functions performance, - // so they should be dead simple and without inner calls + cnt = 0; + while (lat != this.minLat_ && cnt++ < maxLines) { + lat = Math.max(lat - interval, this.minLat_); + idx = this.addParallel_(lat, squaredTolerance, extent, idx); + } - // jshint evil: true + lat = goog.math.clamp(centerLat, this.minLat_, this.maxLat_); - var compareArr = ['return a', ' - b', ';']; + cnt = 0; + while (lat != this.maxLat_ && cnt++ < maxLines) { + lat = Math.min(lat + interval, this.maxLat_); + idx = this.addParallel_(lat, squaredTolerance, extent, idx); + } - this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); - this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); + this.parallels_.length = idx; - this.toBBox = new Function('a', 'return [a' + format.join(', a') + '];'); - } }; -// calculate node's bbox from bboxes of its children -function calcBBox(node, toBBox) { - node.bbox = distBBox(node, 0, node.children.length, toBBox); -} - -// min bounding rectangle of node children from k to p-1 -function distBBox(node, k, p, toBBox) { - var bbox = empty(); - - for (var i = k, child; i < p; i++) { - child = node.children[i]; - extend(bbox, node.leaf ? toBBox(child) : child.bbox); +/** + * @param {number} resolution Resolution. + * @return {number} The interval in degrees. + * @private + */ +ol.Graticule.prototype.getInterval_ = function(resolution) { + var centerLon = this.projectionCenterLonLat_[0]; + var centerLat = this.projectionCenterLonLat_[1]; + var interval = -1; + var i, ii, delta, dist; + var target = Math.pow(this.targetSize_ * resolution, 2); + /** @type {Array.<number>} **/ + var p1 = []; + /** @type {Array.<number>} **/ + var p2 = []; + for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) { + delta = ol.Graticule.intervals_[i] / 2; + p1[0] = centerLon - delta; + p1[1] = centerLat - delta; + p2[0] = centerLon + delta; + p2[1] = centerLat + delta; + this.fromLonLatTransform_(p1, p1); + this.fromLonLatTransform_(p2, p2); + dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2); + if (dist <= target) { + break; } + interval = ol.Graticule.intervals_[i]; + } + return interval; +}; - return bbox; -} -function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; } +/** + * Get the map associated with this graticule. + * @return {ol.Map} The map. + * @api + */ +ol.Graticule.prototype.getMap = function() { + return this.map_; +}; -function extend(a, b) { - a[0] = Math.min(a[0], b[0]); - a[1] = Math.min(a[1], b[1]); - a[2] = Math.max(a[2], b[2]); - a[3] = Math.max(a[3], b[3]); - return a; -} -function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; } -function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; } +/** + * @param {number} lon Longitude. + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.LineString} The meridian line string. + * @param {number} index Index. + * @private + */ +ol.Graticule.prototype.getMeridian_ = function(lon, squaredTolerance, index) { + goog.asserts.assert(lon >= this.minLon_, + 'lon should be larger than or equal to this.minLon_'); + goog.asserts.assert(lon <= this.maxLon_, + 'lon should be smaller than or equal to this.maxLon_'); + var flatCoordinates = ol.geom.flat.geodesic.meridian(lon, + this.minLat_, this.maxLat_, this.projection_, squaredTolerance); + goog.asserts.assert(flatCoordinates.length > 0, + 'flatCoordinates cannot be empty'); + var lineString = goog.isDef(this.meridians_[index]) ? + this.meridians_[index] : new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + return lineString; +}; -function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); } -function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); } -function enlargedArea(a, b) { - return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * - (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); -} +/** + * Get the list of meridians. Meridians are lines of equal longitude. + * @return {Array.<ol.geom.LineString>} The meridians. + * @api + */ +ol.Graticule.prototype.getMeridians = function() { + return this.meridians_; +}; -function intersectionArea (a, b) { - var minX = Math.max(a[0], b[0]), - minY = Math.max(a[1], b[1]), - maxX = Math.min(a[2], b[2]), - maxY = Math.min(a[3], b[3]); - return Math.max(0, maxX - minX) * - Math.max(0, maxY - minY); -} +/** + * @param {number} lat Latitude. + * @param {number} squaredTolerance Squared tolerance. + * @return {ol.geom.LineString} The parallel line string. + * @param {number} index Index. + * @private + */ +ol.Graticule.prototype.getParallel_ = function(lat, squaredTolerance, index) { + goog.asserts.assert(lat >= this.minLat_, + 'lat should be larger than or equal to this.minLat_'); + goog.asserts.assert(lat <= this.maxLat_, + 'lat should be smaller than or equal to this.maxLat_'); + var flatCoordinates = ol.geom.flat.geodesic.parallel(lat, + this.minLon_, this.maxLon_, this.projection_, squaredTolerance); + goog.asserts.assert(flatCoordinates.length > 0, + 'flatCoordinates cannot be empty'); + var lineString = goog.isDef(this.parallels_[index]) ? + this.parallels_[index] : new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates); + return lineString; +}; -function contains(a, b) { - return a[0] <= b[0] && - a[1] <= b[1] && - b[2] <= a[2] && - b[3] <= a[3]; -} -function intersects (a, b) { - return b[0] <= a[2] && - b[1] <= a[3] && - b[2] >= a[0] && - b[3] >= a[1]; -} +/** + * Get the list of parallels. Pallels are lines of equal latitude. + * @return {Array.<ol.geom.LineString>} The parallels. + * @api + */ +ol.Graticule.prototype.getParallels = function() { + return this.parallels_; +}; -// sort an array so that items come in groups of n unsorted items, with groups sorted between each other; -// combines selection algorithm with binary divide & conquer approach -function multiSelect(arr, left, right, n, compare) { - var stack = [left, right], - mid; +/** + * @param {ol.render.Event} e Event. + * @private + */ +ol.Graticule.prototype.handlePostCompose_ = function(e) { + var vectorContext = e.vectorContext; + var frameState = e.frameState; + var extent = frameState.extent; + var viewState = frameState.viewState; + var center = viewState.center; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var squaredTolerance = + resolution * resolution / (4 * pixelRatio * pixelRatio); - while (stack.length) { - right = stack.pop(); - left = stack.pop(); + var updateProjectionInfo = goog.isNull(this.projection_) || + !ol.proj.equivalent(this.projection_, projection); - if (right - left <= n) continue; + if (updateProjectionInfo) { + this.updateProjectionInfo_(projection); + } - mid = left + Math.ceil((right - left) / n / 2) * n; - select(arr, left, right, mid, compare); + this.createGraticule_(extent, center, resolution, squaredTolerance); - stack.push(left, mid, mid, right); - } -} + // Draw the lines + vectorContext.setFillStrokeStyle(null, this.strokeStyle_); + var i, l, line; + for (i = 0, l = this.meridians_.length; i < l; ++i) { + line = this.meridians_[i]; + vectorContext.drawLineStringGeometry(line, null); + } + for (i = 0, l = this.parallels_.length; i < l; ++i) { + line = this.parallels_[i]; + vectorContext.drawLineStringGeometry(line, null); + } +}; -// sort array between left and right (inclusive) so that the smallest k elements come first (unordered) -function select(arr, left, right, k, compare) { - var n, i, z, s, sd, newLeft, newRight, t, j; - while (right > left) { - if (right - left > 600) { - n = right - left + 1; - i = k - left + 1; - z = Math.log(n); - s = 0.5 * Math.exp(2 * z / 3); - sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (i - n / 2 < 0 ? -1 : 1); - newLeft = Math.max(left, Math.floor(k - i * s / n + sd)); - newRight = Math.min(right, Math.floor(k + (n - i) * s / n + sd)); - select(arr, newLeft, newRight, k, compare); - } +/** + * @param {ol.proj.Projection} projection Projection. + * @private + */ +ol.Graticule.prototype.updateProjectionInfo_ = function(projection) { + goog.asserts.assert(!goog.isNull(projection), 'projection cannot be null'); - t = arr[k]; - i = left; - j = right; + var extent = projection.getExtent(); + var worldExtent = projection.getWorldExtent(); + var maxLat = worldExtent[3]; + var maxLon = worldExtent[2]; + var minLat = worldExtent[1]; + var minLon = worldExtent[0]; - swap(arr, left, k); - if (compare(arr[right], t) > 0) swap(arr, left, right); + goog.asserts.assert(!goog.isNull(extent), 'extent cannot be null'); + goog.asserts.assert(goog.isDef(maxLat), 'maxLat should be defined'); + goog.asserts.assert(goog.isDef(maxLon), 'maxLon should be defined'); + goog.asserts.assert(goog.isDef(minLat), 'minLat should be defined'); + goog.asserts.assert(goog.isDef(minLon), 'minLon should be defined'); - while (i < j) { - swap(arr, i, j); - i++; - j--; - while (compare(arr[i], t) < 0) i++; - while (compare(arr[j], t) > 0) j--; - } + this.maxLat_ = maxLat; + this.maxLon_ = maxLon; + this.minLat_ = minLat; + this.minLon_ = minLon; - if (compare(arr[left], t) === 0) swap(arr, left, j); - else { - j++; - swap(arr, j, right); - } + var epsg4326Projection = ol.proj.get('EPSG:4326'); - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } -} + this.fromLonLatTransform_ = ol.proj.getTransform( + epsg4326Projection, projection); -function swap(arr, i, j) { - var tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; -} + this.toLonLatTransform_ = ol.proj.getTransform( + projection, epsg4326Projection); + this.projectionCenterLonLat_ = this.toLonLatTransform_( + ol.extent.getCenter(extent)); -// export as AMD/CommonJS module or global variable -if (typeof define === 'function' && define.amd) define(function() { return rbush; }); -else if (typeof module !== 'undefined') module.exports = rbush; -else if (typeof self !== 'undefined') self.rbush = rbush; -else window.rbush = rbush; + this.projection_ = projection; +}; -})(); -ol.ext.rbush = module.exports; -})(); +/** + * Set the map for this graticule. The graticule will be rendered on the + * provided map. + * @param {ol.Map} map Map. + * @api + */ +ol.Graticule.prototype.setMap = function(map) { + if (!goog.isNull(this.map_)) { + this.map_.un(ol.render.EventType.POSTCOMPOSE, + this.handlePostCompose_, this); + this.map_.render(); + } + if (!goog.isNull(map)) { + map.on(ol.render.EventType.POSTCOMPOSE, + this.handlePostCompose_, this); + map.render(); + } + this.map_ = map; +}; -goog.provide('ol.structs.RBush'); +goog.provide('ol.Image'); goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('goog.object'); -goog.require('ol.ext.rbush'); +goog.require('ol.ImageBase'); +goog.require('ol.ImageState'); +goog.require('ol.extent'); /** - * Wrapper around the RBush by Vladimir Agafonkin. - * * @constructor - * @param {number=} opt_maxEntries Max entries. - * @see https://github.com/mourner/rbush - * @struct - * @template T + * @extends {ol.ImageBase} + * @param {ol.Extent} extent Extent. + * @param {number|undefined} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {Array.<ol.Attribution>} attributions Attributions. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. */ -ol.structs.RBush = function(opt_maxEntries) { +ol.Image = function(extent, resolution, pixelRatio, attributions, src, + crossOrigin, imageLoadFunction) { + + goog.base(this, extent, resolution, pixelRatio, ol.ImageState.IDLE, + attributions); /** * @private + * @type {string} */ - this.rbush_ = ol.ext.rbush(opt_maxEntries); + this.src_ = src; /** - * A mapping between the objects added to this rbush wrapper - * and the objects that are actually added to the internal rbush. * @private - * @type {Object.<number, Object>} + * @type {Image} */ - this.items_ = {}; - - if (goog.DEBUG) { - /** - * @private - * @type {number} - */ - this.readers_ = 0; + this.image_ = new Image(); + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; } -}; + /** + * @private + * @type {Object.<number, Image>} + */ + this.imageByContext_ = {}; -/** - * Insert a value into the RBush. - * @param {ol.Extent} extent Extent. - * @param {T} value Value. - */ -ol.structs.RBush.prototype.insert = function(extent, value) { - if (goog.DEBUG && this.readers_) { - throw new Error('Can not insert value while reading'); - } - var item = [ - extent[0], - extent[1], - extent[2], - extent[3], - value - ]; - this.rbush_.insert(item); - // remember the object that was added to the internal rbush - goog.object.add(this.items_, goog.getUid(value).toString(), item); -}; - + /** + * @private + * @type {Array.<goog.events.Key>} + */ + this.imageListenerKeys_ = null; -/** - * Bulk-insert values into the RBush. - * @param {Array.<ol.Extent>} extents Extents. - * @param {Array.<T>} values Values. - */ -ol.structs.RBush.prototype.load = function(extents, values) { - if (goog.DEBUG && this.readers_) { - throw new Error('Can not insert values while reading'); - } - goog.asserts.assert(extents.length === values.length); + /** + * @protected + * @type {ol.ImageState} + */ + this.state = ol.ImageState.IDLE; - var items = new Array(values.length); - for (var i = 0, l = values.length; i < l; i++) { - var extent = extents[i]; - var value = values[i]; + /** + * @private + * @type {ol.ImageLoadFunctionType} + */ + this.imageLoadFunction_ = imageLoadFunction; - var item = [ - extent[0], - extent[1], - extent[2], - extent[3], - value - ]; - items[i] = item; - goog.object.add(this.items_, goog.getUid(value).toString(), item); - } - this.rbush_.load(items); }; +goog.inherits(ol.Image, ol.ImageBase); /** - * Remove a value from the RBush. - * @param {T} value Value. - * @return {boolean} Removed. + * Get the HTML image element (may be a Canvas, Image, or Video). + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + * @api */ -ol.structs.RBush.prototype.remove = function(value) { - if (goog.DEBUG && this.readers_) { - throw new Error('Can not remove value while reading'); +ol.Image.prototype.getImage = function(opt_context) { + if (goog.isDef(opt_context)) { + var image; + var key = goog.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (goog.object.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; } - var uid = goog.getUid(value).toString(); - goog.asserts.assert(goog.object.containsKey(this.items_, uid)); - - // get the object in which the value was wrapped when adding to the - // internal rbush. then use that object to do the removal. - var item = goog.object.get(this.items_, uid); - goog.object.remove(this.items_, uid); - return this.rbush_.remove(item) !== null; -}; - - -/** - * Update the extent of a value in the RBush. - * @param {ol.Extent} extent Extent. - * @param {T} value Value. - */ -ol.structs.RBush.prototype.update = function(extent, value) { - this.remove(value); - this.insert(extent, value); }; /** - * Return all values in the RBush. - * @return {Array.<T>} All. - */ -ol.structs.RBush.prototype.getAll = function() { - var items = this.rbush_.all(); - return goog.array.map(items, function(item) { - return item[4]; - }); -}; - - -/** - * Return all values in the given extent. - * @param {ol.Extent} extent Extent. - * @return {Array.<T>} All in extent. + * Tracks loading or read errors. + * + * @private */ -ol.structs.RBush.prototype.getInExtent = function(extent) { - var items = this.rbush_.search(extent); - return goog.array.map(items, function(item) { - return item[4]; - }); +ol.Image.prototype.handleImageError_ = function() { + this.state = ol.ImageState.ERROR; + this.unlistenImage_(); + this.changed(); }; /** - * Calls a callback function with each value in the tree. - * If the callback returns a truthy value, this value is returned without - * checking the rest of the tree. - * @param {function(this: S, T): *} callback Callback. - * @param {S=} opt_this The object to use as `this` in `callback`. - * @return {*} Callback return value. - * @template S + * Tracks successful image load. + * + * @private */ -ol.structs.RBush.prototype.forEach = function(callback, opt_this) { - if (goog.DEBUG) { - ++this.readers_; - try { - return this.forEach_(this.getAll(), callback, opt_this); - } finally { - --this.readers_; - } - } else { - return this.forEach_(this.getAll(), callback, opt_this); +ol.Image.prototype.handleImageLoad_ = function() { + if (!goog.isDef(this.resolution)) { + this.resolution = ol.extent.getHeight(this.extent) / this.image_.height; } + this.state = ol.ImageState.LOADED; + this.unlistenImage_(); + this.changed(); }; /** - * Calls a callback function with each value in the provided extent. - * @param {ol.Extent} extent Extent. - * @param {function(this: S, T): *} callback Callback. - * @param {S=} opt_this The object to use as `this` in `callback`. - * @return {*} Callback return value. - * @template S + * Load not yet loaded URI. */ -ol.structs.RBush.prototype.forEachInExtent = - function(extent, callback, opt_this) { - if (goog.DEBUG) { - ++this.readers_; - try { - return this.forEach_(this.getInExtent(extent), callback, opt_this); - } finally { - --this.readers_; - } - } else { - return this.forEach_(this.getInExtent(extent), callback, opt_this); +ol.Image.prototype.load = function() { + if (this.state == ol.ImageState.IDLE) { + this.state = ol.ImageState.LOADING; + this.changed(); + goog.asserts.assert(goog.isNull(this.imageListenerKeys_), + 'this.imageListenerKeys_ should be null'); + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + this.imageLoadFunction_(this, this.src_); } }; /** - * @param {Array.<T>} values Values. - * @param {function(this: S, T): *} callback Callback. - * @param {S=} opt_this The object to use as `this` in `callback`. + * Discards event handlers which listen for load completion or errors. + * * @private - * @return {*} Callback return value. - * @template S */ -ol.structs.RBush.prototype.forEach_ = function(values, callback, opt_this) { - var result; - for (var i = 0, l = values.length; i < l; i++) { - result = callback.call(opt_this, values[i]); - if (result) { - return result; - } - } - return result; +ol.Image.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_), + 'this.imageListenerKeys_ should not be null'); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; }; +goog.provide('ol.ImageLoadFunctionType'); + /** - * @return {boolean} Is empty. + * A function that takes an {@link ol.Image} for the image and a `{string}` for + * the src as arguments. It is supposed to make it so the underlying image + * {@link ol.Image#getImage} is assigned the content specified by the src. If + * not specified, the default is + * + * function(image, src) { + * image.getImage().src = src; + * } + * + * Providing a custom `imageLoadFunction` can be useful to load images with + * post requests or - in general - through XHR requests, where the src of the + * image element would be set to a data URI when the content is loaded. + * + * @typedef {function(ol.Image, string)} + * @api */ -ol.structs.RBush.prototype.isEmpty = function() { - return goog.object.isEmpty(this.items_); -}; +ol.ImageLoadFunctionType; + +goog.provide('ol.TileLoadFunctionType'); +goog.provide('ol.TileVectorLoadFunctionType'); /** - * Remove all values from the RBush. + * A function that takes an {@link ol.ImageTile} for the image tile and a + * `{string}` for the src as arguments. + * + * @typedef {function(ol.ImageTile, string)} + * @api */ -ol.structs.RBush.prototype.clear = function() { - this.rbush_.clear(); - this.items_ = {}; -}; +ol.TileLoadFunctionType; /** - * @param {ol.Extent=} opt_extent Extent. - * @return {ol.Extent} Extent. + * A function that is called with a tile url for the features to load and + * a callback that takes the loaded features as argument. + * + * @typedef {function(string, function(Array.<ol.Feature>))} + * @api */ -ol.structs.RBush.prototype.getExtent = function(opt_extent) { - // FIXME add getExtent() to rbush - return this.rbush_.data.bbox; -}; +ol.TileVectorLoadFunctionType; -// FIXME bulk feature upload - suppress events -// FIXME put features in an ol.Collection -// FIXME make change-detection more refined (notably, geometry hint) - -goog.provide('ol.source.Vector'); -goog.provide('ol.source.VectorEvent'); -goog.provide('ol.source.VectorEventType'); +goog.provide('ol.ImageTile'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.events'); -goog.require('goog.events.Event'); goog.require('goog.events.EventType'); goog.require('goog.object'); -goog.require('ol.ObjectEventType'); -goog.require('ol.proj'); -goog.require('ol.source.Source'); -goog.require('ol.structs.RBush'); - - -/** - * @enum {string} - */ -ol.source.VectorEventType = { - /** - * Triggered when a feature is added to the source. - * @event ol.source.VectorEvent#addfeature - * @api stable - */ - ADDFEATURE: 'addfeature', - - /** - * Triggered when a feature is updated. - * @event ol.source.VectorEvent#changefeature - * @api - */ - CHANGEFEATURE: 'changefeature', - - /** - * Triggered when the clear method is called on the source. - * @event ol.source.VectorEvent#clear - * @api - */ - CLEAR: 'clear', - - /** - * Triggered when a feature is removed from the source. - * See {@link ol.source.Vector#clear source.clear()} for exceptions. - * @event ol.source.VectorEvent#removefeature - * @api stable - */ - REMOVEFEATURE: 'removefeature' -}; +goog.require('ol.Tile'); +goog.require('ol.TileCoord'); +goog.require('ol.TileLoadFunctionType'); +goog.require('ol.TileState'); /** - * @classdesc - * Provides a source of features for vector layers. - * * @constructor - * @extends {ol.source.Source} - * @fires ol.source.VectorEvent - * @param {olx.source.VectorOptions=} opt_options Vector source options. - * @api stable + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileState} state State. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. */ -ol.source.Vector = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; +ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) { - goog.base(this, { - attributions: options.attributions, - logo: options.logo, - projection: options.projection, - state: goog.isDef(options.state) ? - /** @type {ol.source.State} */ (options.state) : undefined - }); + goog.base(this, tileCoord, state); /** + * Image URI + * * @private - * @type {ol.structs.RBush.<ol.Feature>} + * @type {string} */ - this.rBush_ = new ol.structs.RBush(); + this.src_ = src; /** * @private - * @type {Object.<string, ol.Feature>} + * @type {Image} */ - this.nullGeometryFeatures_ = {}; + this.image_ = new Image(); + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; + } /** - * A lookup of features by id (the return from feature.getId()). * @private - * @type {Object.<string, ol.Feature>} + * @type {Object.<number, Image>} */ - this.idIndex_ = {}; + this.imageByContext_ = {}; /** - * A lookup of features without id (keyed by goog.getUid(feature)). * @private - * @type {Object.<string, ol.Feature>} + * @type {Array.<goog.events.Key>} */ - this.undefIdIndex_ = {}; + this.imageListenerKeys_ = null; /** * @private - * @type {Object.<string, Array.<goog.events.Key>>} + * @type {ol.TileLoadFunctionType} */ - this.featureChangeKeys_ = {}; - - if (goog.isDef(options.features)) { - this.addFeaturesInternal(options.features); - } + this.tileLoadFunction_ = tileLoadFunction; }; -goog.inherits(ol.source.Vector, ol.source.Source); +goog.inherits(ol.ImageTile, ol.Tile); /** - * Add a single feature to the source. If you want to add a batch of features - * at once, call {@link ol.source.Vector#addFeatures source.addFeatures()} - * instead. - * @param {ol.Feature} feature Feature to add. - * @api stable + * @inheritDoc */ -ol.source.Vector.prototype.addFeature = function(feature) { - this.addFeatureInternal(feature); - this.changed(); +ol.ImageTile.prototype.disposeInternal = function() { + if (this.state == ol.TileState.LOADING) { + this.unlistenImage_(); + } + goog.base(this, 'disposeInternal'); }; /** - * Add a feature without firing a `change` event. - * @param {ol.Feature} feature Feature. - * @protected + * @inheritDoc + * @api */ -ol.source.Vector.prototype.addFeatureInternal = function(feature) { - var featureKey = goog.getUid(feature).toString(); - this.setupChangeEvents_(featureKey, feature); - - var geometry = feature.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - var extent = geometry.getExtent(); - this.rBush_.insert(extent, feature); +ol.ImageTile.prototype.getImage = function(opt_context) { + if (goog.isDef(opt_context)) { + var image; + var key = goog.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (goog.object.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; } else { - this.nullGeometryFeatures_[featureKey] = feature; + return this.image_; } - - this.addToIndex_(featureKey, feature); - this.dispatchEvent( - new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature)); }; /** - * @param {string} featureKey - * @param {ol.Feature} feature - * @private + * @inheritDoc */ -ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) { - goog.asserts.assert(!(featureKey in this.featureChangeKeys_)); - this.featureChangeKeys_[featureKey] = [ - goog.events.listen(feature, - goog.events.EventType.CHANGE, - this.handleFeatureChange_, false, this), - goog.events.listen(feature, - ol.ObjectEventType.PROPERTYCHANGE, - this.handleFeatureChange_, false, this) - ]; +ol.ImageTile.prototype.getKey = function() { + return this.src_; }; /** - * @param {string} featureKey - * @param {ol.Feature} feature + * Tracks loading or read errors. + * * @private */ -ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) { - var id = feature.getId(); - if (goog.isDef(id)) { - this.idIndex_[id.toString()] = feature; - } else { - goog.asserts.assert(!(featureKey in this.undefIdIndex_), - 'Feature already added to the source'); - this.undefIdIndex_[featureKey] = feature; - } -}; - - -/** - * Add a batch of features to the source. - * @param {Array.<ol.Feature>} features Features to add. - * @api stable - */ -ol.source.Vector.prototype.addFeatures = function(features) { - this.addFeaturesInternal(features); +ol.ImageTile.prototype.handleImageError_ = function() { + this.state = ol.TileState.ERROR; + this.unlistenImage_(); this.changed(); }; /** - * Add features without firing a `change` event. - * @param {Array.<ol.Feature>} features Features. - * @protected + * Tracks successful image load. + * + * @private */ -ol.source.Vector.prototype.addFeaturesInternal = function(features) { - var featureKey, i, length, feature; - var extents = []; - var validFeatures = []; - for (i = 0, length = features.length; i < length; i++) { - feature = features[i]; - featureKey = goog.getUid(feature).toString(); - this.setupChangeEvents_(featureKey, feature); - - var geometry = feature.getGeometry(); - if (goog.isDefAndNotNull(geometry)) { - var extent = geometry.getExtent(); - extents.push(extent); - validFeatures.push(feature); - } else { - this.nullGeometryFeatures_[featureKey] = feature; +ol.ImageTile.prototype.handleImageLoad_ = function() { + if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) { + if (!goog.isDef(this.image_.naturalWidth)) { + this.image_.naturalWidth = this.image_.width; + this.image_.naturalHeight = this.image_.height; } } - this.rBush_.load(extents, validFeatures); - - for (i = 0, length = features.length; i < length; i++) { - feature = features[i]; - featureKey = goog.getUid(feature).toString(); - this.addToIndex_(featureKey, feature); - this.dispatchEvent(new ol.source.VectorEvent( - ol.source.VectorEventType.ADDFEATURE, feature)); - } -}; - -/** - * Remove all features from the source. - * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events. - * @api stable - */ -ol.source.Vector.prototype.clear = function(opt_fast) { - if (opt_fast) { - for (var featureId in this.featureChangeKeys_) { - var keys = this.featureChangeKeys_[featureId]; - goog.array.forEach(keys, goog.events.unlistenByKey); - } - this.featureChangeKeys_ = {}; - this.idIndex_ = {}; - this.undefIdIndex_ = {}; + if (this.image_.naturalWidth && this.image_.naturalHeight) { + this.state = ol.TileState.LOADED; } else { - var rmFeatureInternal = this.removeFeatureInternal; - this.rBush_.forEach(rmFeatureInternal, this); - goog.object.forEach(this.nullGeometryFeatures_, rmFeatureInternal, this); - goog.asserts.assert(goog.object.isEmpty(this.featureChangeKeys_)); - goog.asserts.assert(goog.object.isEmpty(this.idIndex_)); - goog.asserts.assert(goog.object.isEmpty(this.undefIdIndex_)); + this.state = ol.TileState.EMPTY; } - - this.rBush_.clear(); - this.nullGeometryFeatures_ = {}; - - var clearEvent = new ol.source.VectorEvent(ol.source.VectorEventType.CLEAR); - this.dispatchEvent(clearEvent); + this.unlistenImage_(); this.changed(); }; /** - * Iterate through all features on the source, calling the provided callback - * with each one. If the callback returns any "truthy" value, iteration will - * stop and the function will return the same value. - * - * @param {function(this: T, ol.Feature): S} callback Called with each feature - * on the source. Return a truthy value to stop iteration. - * @param {T=} opt_this The object to use as `this` in the callback. - * @return {S|undefined} The return value from the last call to the callback. - * @template T,S - * @api stable + * Load not yet loaded URI. */ -ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) { - return this.rBush_.forEach(callback, opt_this); +ol.ImageTile.prototype.load = function() { + if (this.state == ol.TileState.IDLE) { + this.state = ol.TileState.LOADING; + this.changed(); + goog.asserts.assert(goog.isNull(this.imageListenerKeys_), + 'this.imageListenerKeys_ should be null'); + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + this.tileLoadFunction_(this, this.src_); + } }; /** - * Iterate through all features whose geometries contain the provided - * coordinate, calling the callback with each feature. If the callback returns - * a "truthy" value, iteration will stop and the function will return the same - * value. + * Discards event handlers which listen for load completion or errors. * - * @param {ol.Coordinate} coordinate Coordinate. - * @param {function(this: T, ol.Feature): S} callback Called with each feature - * whose goemetry contains the provided coordinate. - * @param {T=} opt_this The object to use as `this` in the callback. - * @return {S|undefined} The return value from the last call to the callback. - * @template T,S + * @private */ -ol.source.Vector.prototype.forEachFeatureAtCoordinate = - function(coordinate, callback, opt_this) { - var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]]; - return this.forEachFeatureInExtent(extent, function(feature) { - var geometry = feature.getGeometry(); - goog.asserts.assert(goog.isDefAndNotNull(geometry)); - if (geometry.containsCoordinate(coordinate)) { - return callback.call(opt_this, feature); - } else { - return undefined; - } - }); +ol.ImageTile.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_), + 'this.imageListenerKeys_ should not be null'); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; }; +goog.provide('ol.ImageUrlFunction'); +goog.provide('ol.ImageUrlFunctionType'); -/** - * Iterate through all features whose bounding box intersects the provided - * extent (note that the feature's geometry may not intersect the extent), - * calling the callback with each feature. If the callback returns a "truthy" - * value, iteration will stop and the function will return the same value. - * - * If you are interested in features whose geometry intersects an extent, call - * the {@link ol.source.Vector#forEachFeatureIntersectingExtent - * source.forEachFeatureIntersectingExtent()} method instead. - * - * @param {ol.Extent} extent Extent. - * @param {function(this: T, ol.Feature): S} callback Called with each feature - * whose bounding box intersects the provided extent. - * @param {T=} opt_this The object to use as `this` in the callback. - * @return {S|undefined} The return value from the last call to the callback. - * @template T,S - * @api - */ -ol.source.Vector.prototype.forEachFeatureInExtent = - function(extent, callback, opt_this) { - return this.rBush_.forEachInExtent(extent, callback, opt_this); -}; +goog.require('ol.Size'); /** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {function(this: T, ol.Feature): S} f Callback. - * @param {T=} opt_this The object to use as `this` in `f`. - * @return {S|undefined} - * @template T,S + * @typedef {function(this:ol.source.Image, ol.Extent, ol.Size, + * ol.proj.Projection): (string|undefined)} */ -ol.source.Vector.prototype.forEachFeatureInExtentAtResolution = - function(extent, resolution, f, opt_this) { - return this.forEachFeatureInExtent(extent, f, opt_this); -}; +ol.ImageUrlFunctionType; /** - * Iterate through all features whose geometry intersects the provided extent, - * calling the callback with each feature. If the callback returns a "truthy" - * value, iteration will stop and the function will return the same value. - * - * If you only want to test for bounding box intersection, call the - * {@link ol.source.Vector#forEachFeatureInExtent - * source.forEachFeatureInExtent()} method instead. - * - * @param {ol.Extent} extent Extent. - * @param {function(this: T, ol.Feature): S} callback Called with each feature - * whose geometry intersects the provided extent. - * @param {T=} opt_this The object to use as `this` in the callback. - * @return {S|undefined} The return value from the last call to the callback. - * @template T,S - * @api + * @param {string} baseUrl Base URL (may have query data). + * @param {Object.<string,*>} params to encode in the URL. + * @param {function(string, Object.<string,*>, ol.Extent, ol.Size, + * ol.proj.Projection): (string|undefined)} paramsFunction params function. + * @return {ol.ImageUrlFunctionType} Image URL function. */ -ol.source.Vector.prototype.forEachFeatureIntersectingExtent = - function(extent, callback, opt_this) { - return this.forEachFeatureInExtent(extent, +ol.ImageUrlFunction.createFromParamsFunction = + function(baseUrl, params, paramsFunction) { + return ( /** - * @param {ol.Feature} feature Feature. - * @return {S|undefined} - * @template S + * @this {ol.source.Image} + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} URL. */ - function(feature) { - var geometry = feature.getGeometry(); - goog.asserts.assert(goog.isDefAndNotNull(geometry)); - if (geometry.intersectsExtent(extent)) { - var result = callback.call(opt_this, feature); - if (result) { - return result; - } - } + function(extent, size, projection) { + return paramsFunction(baseUrl, params, extent, size, projection); }); }; /** - * Get all features on the source. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @this {ol.source.Image} + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @return {string|undefined} Image URL. */ -ol.source.Vector.prototype.getFeatures = function() { - var features = this.rBush_.getAll(); - if (!goog.object.isEmpty(this.nullGeometryFeatures_)) { - goog.array.extend( - features, goog.object.getValues(this.nullGeometryFeatures_)); - } - return features; +ol.ImageUrlFunction.nullImageUrlFunction = + function(extent, size) { + return undefined; }; +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Get all features whose geometry intersects the provided coordinate. - * @param {ol.Coordinate} coordinate Coordinate. - * @return {Array.<ol.Feature>} Features. - * @api stable + * @fileoverview Provides a files drag and drop event detector. It works on + * HTML5 browsers. + * + * @see ../demos/filedrophandler.html */ -ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) { - var features = []; - this.forEachFeatureAtCoordinate(coordinate, function(feature) { - features.push(feature); - }); - return features; -}; +goog.provide('goog.events.FileDropHandler'); +goog.provide('goog.events.FileDropHandler.EventType'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('goog.log'); +goog.require('goog.log.Level'); -/** - * @param {ol.Extent} extent Extent. - * @return {Array.<ol.Feature>} Features. - */ -ol.source.Vector.prototype.getFeaturesInExtent = function(extent) { - return this.rBush_.getInExtent(extent); -}; /** - * Get the closest feature to the provided coordinate. - * @param {ol.Coordinate} coordinate Coordinate. - * @return {ol.Feature} Closest feature. - * @api stable + * A files drag and drop event detector. Gets an {@code element} as parameter + * and fires {@code goog.events.FileDropHandler.EventType.DROP} event when files + * are dropped in the {@code element}. + * + * @param {Element|Document} element The element or document to listen on. + * @param {boolean=} opt_preventDropOutside Whether to prevent a drop on the + * area outside the {@code element}. Default false. + * @constructor + * @extends {goog.events.EventTarget} + * @final */ -ol.source.Vector.prototype.getClosestFeatureToCoordinate = - function(coordinate) { - // Find the closest feature using branch and bound. We start searching an - // infinite extent, and find the distance from the first feature found. This - // becomes the closest feature. We then compute a smaller extent which any - // closer feature must intersect. We continue searching with this smaller - // extent, trying to find a closer feature. Every time we find a closer - // feature, we update the extent being searched so that any even closer - // feature must intersect it. We continue until we run out of features. - var x = coordinate[0]; - var y = coordinate[1]; - var closestFeature = null; - var closestPoint = [NaN, NaN]; - var minSquaredDistance = Infinity; - var extent = [-Infinity, -Infinity, Infinity, Infinity]; - this.rBush_.forEachInExtent(extent, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - var geometry = feature.getGeometry(); - goog.asserts.assert(goog.isDefAndNotNull(geometry)); - var previousMinSquaredDistance = minSquaredDistance; - minSquaredDistance = geometry.closestPointXY( - x, y, closestPoint, minSquaredDistance); - if (minSquaredDistance < previousMinSquaredDistance) { - closestFeature = feature; - // This is sneaky. Reduce the extent that it is currently being - // searched while the R-Tree traversal using this same extent object - // is still in progress. This is safe because the new extent is - // strictly contained by the old extent. - var minDistance = Math.sqrt(minSquaredDistance); - extent[0] = x - minDistance; - extent[1] = y - minDistance; - extent[2] = x + minDistance; - extent[3] = y + minDistance; - } - }); - return closestFeature; -}; +goog.events.FileDropHandler = function(element, opt_preventDropOutside) { + goog.events.EventTarget.call(this); + /** + * Handler for drag/drop events. + * @type {!goog.events.EventHandler<!goog.events.FileDropHandler>} + * @private + */ + this.eventHandler_ = new goog.events.EventHandler(this); -/** - * Get the extent of the features currently in the source. - * @return {ol.Extent} Extent. - * @api stable - */ -ol.source.Vector.prototype.getExtent = function() { - return this.rBush_.getExtent(); + var doc = element; + if (opt_preventDropOutside) { + doc = goog.dom.getOwnerDocument(element); + } + + // Add dragenter listener to the owner document of the element. + this.eventHandler_.listen(doc, + goog.events.EventType.DRAGENTER, + this.onDocDragEnter_); + + // Add dragover listener to the owner document of the element only if the + // document is not the element itself. + if (doc != element) { + this.eventHandler_.listen(doc, + goog.events.EventType.DRAGOVER, + this.onDocDragOver_); + } + + // Add dragover and drop listeners to the element. + this.eventHandler_.listen(element, + goog.events.EventType.DRAGOVER, + this.onElemDragOver_); + this.eventHandler_.listen(element, + goog.events.EventType.DROP, + this.onElemDrop_); }; +goog.inherits(goog.events.FileDropHandler, goog.events.EventTarget); /** - * Get a feature by its identifier (the value returned by feature.getId()). - * Note that the index treats string and numeric identifiers as the same. So - * `source.getFeatureById(2)` will return a feature with id `'2'` or `2`. - * - * @param {string|number} id Feature identifier. - * @return {ol.Feature} The feature (or `null` if not found). - * @api stable + * Whether the drag event contains files. It is initialized only in the + * dragenter event. It is used in all the drag events to prevent default actions + * only if the drag contains files. Preventing default actions is necessary to + * go from dragenter to dragover and from dragover to drop. However we do not + * always want to prevent default actions, e.g. when the user drags text or + * links on a text area we should not prevent the browser default action that + * inserts the text in the text area. It is also necessary to stop propagation + * when handling drag events on the element to prevent them from propagating + * to the document. + * @private + * @type {boolean} */ -ol.source.Vector.prototype.getFeatureById = function(id) { - var feature = this.idIndex_[id.toString()]; - return goog.isDef(feature) ? feature : null; -}; +goog.events.FileDropHandler.prototype.dndContainsFiles_ = false; /** - * @param {goog.events.Event} event Event. + * A logger, used to help us debug the algorithm. + * @type {goog.log.Logger} * @private */ -ol.source.Vector.prototype.handleFeatureChange_ = function(event) { - var feature = /** @type {ol.Feature} */ (event.target); - var featureKey = goog.getUid(feature).toString(); - var geometry = feature.getGeometry(); - if (!goog.isDefAndNotNull(geometry)) { - if (!(featureKey in this.nullGeometryFeatures_)) { - this.rBush_.remove(feature); - this.nullGeometryFeatures_[featureKey] = feature; - } - } else { - var extent = geometry.getExtent(); - if (featureKey in this.nullGeometryFeatures_) { - delete this.nullGeometryFeatures_[featureKey]; - this.rBush_.insert(extent, feature); - } else { - this.rBush_.update(extent, feature); - } - } - var id = feature.getId(); - var removed; - if (goog.isDef(id)) { - var sid = id.toString(); - if (featureKey in this.undefIdIndex_) { - delete this.undefIdIndex_[featureKey]; - this.idIndex_[sid] = feature; - } else { - if (this.idIndex_[sid] !== feature) { - removed = this.removeFromIdIndex_(feature); - goog.asserts.assert(removed, - 'Expected feature to be removed from index'); - this.idIndex_[sid] = feature; - } - } - } else { - if (!(featureKey in this.undefIdIndex_)) { - removed = this.removeFromIdIndex_(feature); - goog.asserts.assert(removed, - 'Expected feature to be removed from index'); - this.undefIdIndex_[featureKey] = feature; - } else { - goog.asserts.assert(this.undefIdIndex_[featureKey] === feature); - } - } - this.changed(); - this.dispatchEvent(new ol.source.VectorEvent( - ol.source.VectorEventType.CHANGEFEATURE, feature)); -}; +goog.events.FileDropHandler.prototype.logger_ = + goog.log.getLogger('goog.events.FileDropHandler'); /** - * @return {boolean} Is empty. + * The types of events fired by this class. + * @enum {string} */ -ol.source.Vector.prototype.isEmpty = function() { - return this.rBush_.isEmpty() && - goog.object.isEmpty(this.nullGeometryFeatures_); +goog.events.FileDropHandler.EventType = { + DROP: goog.events.EventType.DROP }; -/** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {ol.proj.Projection} projection Projection. - */ -ol.source.Vector.prototype.loadFeatures = goog.nullFunction; +/** @override */ +goog.events.FileDropHandler.prototype.disposeInternal = function() { + goog.events.FileDropHandler.superClass_.disposeInternal.call(this); + this.eventHandler_.dispose(); +}; /** - * Remove a single feature from the source. If you want to remove all features - * at once, use the {@link ol.source.Vector#clear source.clear()} method - * instead. - * @param {ol.Feature} feature Feature to remove. - * @api stable + * Dispatches the DROP event. + * @param {goog.events.BrowserEvent} e The underlying browser event. + * @private */ -ol.source.Vector.prototype.removeFeature = function(feature) { - var featureKey = goog.getUid(feature).toString(); - if (featureKey in this.nullGeometryFeatures_) { - delete this.nullGeometryFeatures_[featureKey]; - } else { - this.rBush_.remove(feature); - } - this.removeFeatureInternal(feature); - this.changed(); +goog.events.FileDropHandler.prototype.dispatch_ = function(e) { + goog.log.fine(this.logger_, 'Firing DROP event...'); + var event = new goog.events.BrowserEvent(e.getBrowserEvent()); + event.type = goog.events.FileDropHandler.EventType.DROP; + this.dispatchEvent(event); }; /** - * Remove feature without firing a `change` event. - * @param {ol.Feature} feature Feature. - * @protected + * Handles dragenter on the document. + * @param {goog.events.BrowserEvent} e The dragenter event. + * @private */ -ol.source.Vector.prototype.removeFeatureInternal = function(feature) { - var featureKey = goog.getUid(feature).toString(); - goog.asserts.assert(featureKey in this.featureChangeKeys_); - goog.array.forEach(this.featureChangeKeys_[featureKey], - goog.events.unlistenByKey); - delete this.featureChangeKeys_[featureKey]; - var id = feature.getId(); - if (goog.isDef(id)) { - delete this.idIndex_[id.toString()]; - } else { - delete this.undefIdIndex_[featureKey]; +goog.events.FileDropHandler.prototype.onDocDragEnter_ = function(e) { + goog.log.log(this.logger_, goog.log.Level.FINER, + '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); + var dt = e.getBrowserEvent().dataTransfer; + // Check whether the drag event contains files. + this.dndContainsFiles_ = !!(dt && + ((dt.types && + (goog.array.contains(dt.types, 'Files') || + goog.array.contains(dt.types, 'public.file-url'))) || + (dt.files && dt.files.length > 0))); + // If it does + if (this.dndContainsFiles_) { + // Prevent default actions. + e.preventDefault(); } - this.dispatchEvent(new ol.source.VectorEvent( - ol.source.VectorEventType.REMOVEFEATURE, feature)); + goog.log.log(this.logger_, goog.log.Level.FINER, + 'dndContainsFiles_: ' + this.dndContainsFiles_); }; /** - * Remove a feature from the id index. Called internally when the feature id - * may have changed. - * @param {ol.Feature} feature The feature. - * @return {boolean} Removed the feature from the index. + * Handles dragging something over the document. + * @param {goog.events.BrowserEvent} e The dragover event. * @private */ -ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) { - var removed = false; - for (var id in this.idIndex_) { - if (this.idIndex_[id] === feature) { - delete this.idIndex_[id]; - removed = true; - break; - } +goog.events.FileDropHandler.prototype.onDocDragOver_ = function(e) { + goog.log.log(this.logger_, goog.log.Level.FINEST, + '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); + if (this.dndContainsFiles_) { + // Prevent default actions. + e.preventDefault(); + // Disable the drop on the document outside the drop zone. + var dt = e.getBrowserEvent().dataTransfer; + dt.dropEffect = 'none'; } - return removed; }; - /** - * @classdesc - * Events emitted by {@link ol.source.Vector} instances are instances of this - * type. - * - * @constructor - * @extends {goog.events.Event} - * @implements {oli.source.VectorEvent} - * @param {string} type Type. - * @param {ol.Feature=} opt_feature Feature. + * Handles dragging something over the element (drop zone). + * @param {goog.events.BrowserEvent} e The dragover event. + * @private */ -ol.source.VectorEvent = function(type, opt_feature) { - - goog.base(this, type); - - /** - * The feature being added or removed. - * @type {ol.Feature|undefined} - * @api stable - */ - this.feature = opt_feature; - +goog.events.FileDropHandler.prototype.onElemDragOver_ = function(e) { + goog.log.log(this.logger_, goog.log.Level.FINEST, + '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); + if (this.dndContainsFiles_) { + // Prevent default actions and stop the event from propagating further to + // the document. Both lines are needed! (See comment above). + e.preventDefault(); + e.stopPropagation(); + // Allow the drop on the drop zone. + var dt = e.getBrowserEvent().dataTransfer; + dt.effectAllowed = 'all'; + dt.dropEffect = 'copy'; + } }; -goog.inherits(ol.source.VectorEvent, goog.events.Event); - -goog.provide('ol.DrawEvent'); -goog.provide('ol.interaction.Draw'); - -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.Event'); -goog.require('ol.Collection'); -goog.require('ol.Coordinate'); -goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); -goog.require('ol.Map'); -goog.require('ol.MapBrowserEvent'); -goog.require('ol.MapBrowserEvent.EventType'); -goog.require('ol.Object'); -goog.require('ol.events.condition'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.interaction.InteractionProperty'); -goog.require('ol.interaction.Pointer'); -goog.require('ol.source.Vector'); -goog.require('ol.style.Style'); /** - * @enum {string} + * Handles dropping something onto the element (drop zone). + * @param {goog.events.BrowserEvent} e The drop event. + * @private */ -ol.DrawEventType = { - /** - * Triggered upon feature draw start - * @event ol.DrawEvent#drawstart - * @api stable - */ - DRAWSTART: 'drawstart', - /** - * Triggered upon feature draw end - * @event ol.DrawEvent#drawend - * @api stable - */ - DRAWEND: 'drawend' +goog.events.FileDropHandler.prototype.onElemDrop_ = function(e) { + goog.log.log(this.logger_, goog.log.Level.FINER, + '"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type); + // If the drag and drop event contains files. + if (this.dndContainsFiles_) { + // Prevent default actions and stop the event from propagating further to + // the document. Both lines are needed! (See comment above). + e.preventDefault(); + e.stopPropagation(); + // Dispatch DROP event. + this.dispatch_(e); + } }; +// Copyright 2007 Bob Ippolito. All Rights Reserved. +// Modifications Copyright 2009 The Closure Library Authors. All Rights +// Reserved. +/** + * @license Portions of this code are from MochiKit, received by + * The Closure Authors under the MIT license. All other code is Copyright + * 2005-2009 The Closure Authors. All Rights Reserved. + */ /** - * @classdesc - * Events emitted by {@link ol.interaction.Draw} instances are instances of - * this type. + * @fileoverview Classes for tracking asynchronous operations and handling the + * results. The Deferred object here is patterned after the Deferred object in + * the Twisted python networking framework. * - * @constructor - * @extends {goog.events.Event} - * @implements {oli.DrawEvent} - * @param {ol.DrawEventType} type Type. - * @param {ol.Feature} feature The feature drawn. + * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html + * + * Based on the Dojo code which in turn is based on the MochiKit code. + * + * @author arv@google.com (Erik Arvidsson) + * @author brenneman@google.com (Shawn Brenneman) */ -ol.DrawEvent = function(type, feature) { - - goog.base(this, type); - /** - * The feature being drawn. - * @type {ol.Feature} - * @api stable - */ - this.feature = feature; +goog.provide('goog.async.Deferred'); +goog.provide('goog.async.Deferred.AlreadyCalledError'); +goog.provide('goog.async.Deferred.CanceledError'); -}; -goog.inherits(ol.DrawEvent, goog.events.Event); +goog.require('goog.Promise'); +goog.require('goog.Thenable'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.debug.Error'); /** - * @classdesc - * Interaction that allows drawing geometries. + * A Deferred represents the result of an asynchronous operation. A Deferred + * instance has no result when it is created, and is "fired" (given an initial + * result) by calling {@code callback} or {@code errback}. + * + * Once fired, the result is passed through a sequence of callback functions + * registered with {@code addCallback} or {@code addErrback}. The functions may + * mutate the result before it is passed to the next function in the sequence. + * + * Callbacks and errbacks may be added at any time, including after the Deferred + * has been "fired". If there are no pending actions in the execution sequence + * of a fired Deferred, any new callback functions will be called with the last + * computed result. Adding a callback function is the only way to access the + * result of the Deferred. + * + * If a Deferred operation is canceled, an optional user-provided cancellation + * function is invoked which may perform any special cleanup, followed by firing + * the Deferred's errback sequence with a {@code CanceledError}. If the + * Deferred has already fired, cancellation is ignored. + * + * Deferreds may be templated to a specific type they produce using generics + * with syntax such as: + * <code> + * /** @type {goog.async.Deferred<string>} */ + * var d = new goog.async.Deferred(); + * // Compiler can infer that foo is a string. + * d.addCallback(function(foo) {...}); + * d.callback('string'); // Checked to be passed a string + * </code> + * Since deferreds are often used to produce different values across a chain, + * the type information is not propagated across chains, but rather only + * associated with specifically cast objects. * + * @param {Function=} opt_onCancelFunction A function that will be called if the + * Deferred is canceled. If provided, this function runs before the + * Deferred is fired with a {@code CanceledError}. + * @param {Object=} opt_defaultScope The default object context to call + * callbacks and errbacks in. * @constructor - * @extends {ol.interaction.Pointer} - * @fires ol.DrawEvent - * @param {olx.interaction.DrawOptions} options Options. - * @api stable + * @implements {goog.Thenable<VALUE>} + * @template VALUE */ -ol.interaction.Draw = function(options) { - - goog.base(this, { - handleDownEvent: ol.interaction.Draw.handleDownEvent_, - handleEvent: ol.interaction.Draw.handleEvent, - handleUpEvent: ol.interaction.Draw.handleUpEvent_ - }); - - /** - * @type {ol.Pixel} - * @private - */ - this.downPx_ = null; - - /** - * Target source for drawn features. - * @type {ol.source.Vector} - * @private - */ - this.source_ = goog.isDef(options.source) ? options.source : null; - - /** - * Target collection for drawn features. - * @type {ol.Collection.<ol.Feature>} - * @private - */ - this.features_ = goog.isDef(options.features) ? options.features : null; - +goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) { /** - * Pixel distance for snapping. - * @type {number} + * Entries in the sequence are arrays containing a callback, an errback, and + * an optional scope. The callback or errback in an entry may be null. + * @type {!Array<!Array>} * @private */ - this.snapTolerance_ = goog.isDef(options.snapTolerance) ? - options.snapTolerance : 12; + this.sequence_ = []; /** - * The number of points that must be drawn before a polygon ring can be - * finished. The default is 3. - * @type {number} + * Optional function that will be called if the Deferred is canceled. + * @type {Function|undefined} * @private */ - this.minPointsPerRing_ = goog.isDef(options.minPointsPerRing) ? - options.minPointsPerRing : 3; + this.onCancelFunction_ = opt_onCancelFunction; /** - * Geometry type. - * @type {ol.geom.GeometryType} + * The default scope to execute callbacks and errbacks in. + * @type {Object} * @private */ - this.type_ = options.type; + this.defaultScope_ = opt_defaultScope || null; /** - * Drawing mode (derived from geometry type. - * @type {ol.interaction.DrawMode} + * Whether the Deferred has been fired. + * @type {boolean} * @private */ - this.mode_ = ol.interaction.Draw.getMode_(this.type_); + this.fired_ = false; /** - * Finish coordinate for the feature (first point for polygons, last point for - * linestrings). - * @type {ol.Coordinate} + * Whether the last result in the execution sequence was an error. + * @type {boolean} * @private */ - this.finishCoordinate_ = null; + this.hadError_ = false; /** - * Sketch feature. - * @type {ol.Feature} + * The current Deferred result, updated as callbacks and errbacks are + * executed. + * @type {*} * @private */ - this.sketchFeature_ = null; + this.result_ = undefined; /** - * Sketch point. - * @type {ol.Feature} + * Whether the Deferred is blocked waiting on another Deferred to fire. If a + * callback or errback returns a Deferred as a result, the execution sequence + * is blocked until that Deferred result becomes available. + * @type {boolean} * @private */ - this.sketchPoint_ = null; + this.blocked_ = false; /** - * Sketch line. Used when drawing polygon. - * @type {ol.Feature} + * Whether this Deferred is blocking execution of another Deferred. If this + * instance was returned as a result in another Deferred's execution + * sequence,that other Deferred becomes blocked until this instance's + * execution sequence completes. No additional callbacks may be added to a + * Deferred once it is blocking another instance. + * @type {boolean} * @private */ - this.sketchLine_ = null; + this.blocking_ = false; /** - * Sketch polygon. Used when drawing polygon. - * @type {Array.<Array.<ol.Coordinate>>} + * Whether the Deferred has been canceled without having a custom cancel + * function. + * @type {boolean} * @private */ - this.sketchPolygonCoords_ = null; + this.silentlyCanceled_ = false; /** - * Squared tolerance for handling up events. If the squared distance - * between a down and up event is greater than this tolerance, up events - * will not be handled. + * If an error is thrown during Deferred execution with no errback to catch + * it, the error is rethrown after a timeout. Reporting the error after a + * timeout allows execution to continue in the calling context (empty when + * no error is scheduled). * @type {number} * @private */ - this.squaredClickTolerance_ = 4; - - /** - * Draw overlay where our sketch features are drawn. - * @type {ol.FeatureOverlay} - * @private - */ - this.overlay_ = new ol.FeatureOverlay({ - style: goog.isDef(options.style) ? - options.style : ol.interaction.Draw.getDefaultStyleFunction() - }); + this.unhandledErrorId_ = 0; /** - * Name of the geometry attribute for newly created features. - * @type {string|undefined} + * If this Deferred was created by branch(), this will be the "parent" + * Deferred. + * @type {goog.async.Deferred} * @private */ - this.geometryName_ = options.geometryName; + this.parent_ = null; /** + * The number of Deferred objects that have been branched off this one. This + * will be decremented whenever a branch is fired or canceled. + * @type {number} * @private - * @type {ol.events.ConditionType} */ - this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.events.condition.noModifierKeys; - - goog.events.listen(this, - ol.Object.getChangeEventType(ol.interaction.InteractionProperty.ACTIVE), - this.updateState_, false, this); - -}; -goog.inherits(ol.interaction.Draw, ol.interaction.Pointer); - - -/** - * @return {ol.style.StyleFunction} Styles. - */ -ol.interaction.Draw.getDefaultStyleFunction = function() { - var styles = ol.style.createDefaultEditingStyles(); - return function(feature, resolution) { - return styles[feature.getGeometry().getType()]; - }; -}; - - -/** - * @inheritDoc - */ -ol.interaction.Draw.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); - this.updateState_(); -}; - - -/** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.Draw} - * @api - */ -ol.interaction.Draw.handleEvent = function(mapBrowserEvent) { - var map = mapBrowserEvent.map; - if (!map.isDef()) { - return true; - } - var pass = true; - if (mapBrowserEvent.type === ol.MapBrowserEvent.EventType.POINTERMOVE) { - pass = this.handlePointerMove_(mapBrowserEvent); - } else if (mapBrowserEvent.type === ol.MapBrowserEvent.EventType.DBLCLICK) { - pass = false; - } - return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && pass; -}; - - -/** - * @param {ol.MapBrowserPointerEvent} event Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.Draw} - * @private - */ -ol.interaction.Draw.handleDownEvent_ = function(event) { - if (this.condition_(event)) { - this.downPx_ = event.pixel; - return true; - } else { - return false; - } -}; - - -/** - * @param {ol.MapBrowserPointerEvent} event Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.Draw} - * @private - */ -ol.interaction.Draw.handleUpEvent_ = function(event) { - var downPx = this.downPx_; - var clickPx = event.pixel; - var dx = downPx[0] - clickPx[0]; - var dy = downPx[1] - clickPx[1]; - var squaredDistance = dx * dx + dy * dy; - var pass = true; - if (squaredDistance <= this.squaredClickTolerance_) { - this.handlePointerMove_(event); - if (goog.isNull(this.finishCoordinate_)) { - this.startDrawing_(event); - } else if (this.mode_ === ol.interaction.DrawMode.POINT || - this.atFinish_(event)) { - this.finishDrawing(); - } else { - this.addToDrawing_(event); - } - pass = false; - } - return pass; -}; - - -/** - * Handle move events. - * @param {ol.MapBrowserEvent} event A move event. - * @return {boolean} Pass the event to other interactions. - * @private - */ -ol.interaction.Draw.prototype.handlePointerMove_ = function(event) { - if (this.mode_ === ol.interaction.DrawMode.POINT && - goog.isNull(this.finishCoordinate_)) { - this.startDrawing_(event); - } else if (!goog.isNull(this.finishCoordinate_)) { - this.modifyDrawing_(event); - } else { - this.createOrUpdateSketchPoint_(event); - } - return true; -}; - + this.branches_ = 0; -/** - * Determine if an event is within the snapping tolerance of the start coord. - * @param {ol.MapBrowserEvent} event Event. - * @return {boolean} The event is within the snapping tolerance of the start. - * @private - */ -ol.interaction.Draw.prototype.atFinish_ = function(event) { - var at = false; - if (!goog.isNull(this.sketchFeature_)) { - var geometry = this.sketchFeature_.getGeometry(); - var potentiallyDone = false; - var potentiallyFinishCoordinates = [this.finishCoordinate_]; - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - potentiallyDone = geometry.getCoordinates().length > 2; - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - potentiallyDone = geometry.getCoordinates()[0].length > - this.minPointsPerRing_; - potentiallyFinishCoordinates = [this.sketchPolygonCoords_[0][0], - this.sketchPolygonCoords_[0][this.sketchPolygonCoords_[0].length - 2]]; - } - if (potentiallyDone) { - var map = event.map; - for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) { - var finishCoordinate = potentiallyFinishCoordinates[i]; - var finishPixel = map.getPixelFromCoordinate(finishCoordinate); - var pixel = event.pixel; - var dx = pixel[0] - finishPixel[0]; - var dy = pixel[1] - finishPixel[1]; - at = Math.sqrt(dx * dx + dy * dy) <= this.snapTolerance_; - if (at) { - this.finishCoordinate_ = finishCoordinate; - break; - } + if (goog.async.Deferred.LONG_STACK_TRACES) { + /** + * Holds the stack trace at time of deferred creation if the JS engine + * provides the Error.captureStackTrace API. + * @private {?string} + */ + this.constructorStack_ = null; + if (Error.captureStackTrace) { + var target = { stack: '' }; + Error.captureStackTrace(target, goog.async.Deferred); + // Check if Error.captureStackTrace worked. It fails in gjstest. + if (typeof target.stack == 'string') { + // Remove first line and force stringify to prevent memory leak due to + // holding on to actual stack frames. + this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, ''); } } } - return at; }; /** - * @param {ol.MapBrowserEvent} event Event. - * @private + * @define {boolean} Whether unhandled errors should always get rethrown to the + * global scope. Defaults to the value of goog.DEBUG. */ -ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) { - var coordinates = event.coordinate.slice(); - if (goog.isNull(this.sketchPoint_)) { - this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates)); - this.updateSketchFeatures_(); - } else { - var sketchPointGeom = this.sketchPoint_.getGeometry(); - goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point); - sketchPointGeom.setCoordinates(coordinates); - } -}; +goog.define('goog.async.Deferred.STRICT_ERRORS', false); /** - * Start the drawing. - * @param {ol.MapBrowserEvent} event Event. - * @private + * @define {boolean} Whether to attempt to make stack traces long. Defaults to + * the value of goog.DEBUG. */ -ol.interaction.Draw.prototype.startDrawing_ = function(event) { - var start = event.coordinate; - this.finishCoordinate_ = start; - var geometry; - if (this.mode_ === ol.interaction.DrawMode.POINT) { - geometry = new ol.geom.Point(start.slice()); - } else { - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - geometry = new ol.geom.LineString([start.slice(), start.slice()]); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - this.sketchLine_ = new ol.Feature(new ol.geom.LineString([start.slice(), - start.slice()])); - this.sketchPolygonCoords_ = [[start.slice(), start.slice()]]; - geometry = new ol.geom.Polygon(this.sketchPolygonCoords_); - } - } - goog.asserts.assert(goog.isDef(geometry)); - this.sketchFeature_ = new ol.Feature(); - if (goog.isDef(this.geometryName_)) { - this.sketchFeature_.setGeometryName(this.geometryName_); - } - this.sketchFeature_.setGeometry(geometry); - this.updateSketchFeatures_(); - this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWSTART, - this.sketchFeature_)); -}; +goog.define('goog.async.Deferred.LONG_STACK_TRACES', false); /** - * Modify the drawing. - * @param {ol.MapBrowserEvent} event Event. - * @private + * Cancels a Deferred that has not yet been fired, or is blocked on another + * deferred operation. If this Deferred is waiting for a blocking Deferred to + * fire, the blocking Deferred will also be canceled. + * + * If this Deferred was created by calling branch() on a parent Deferred with + * opt_propagateCancel set to true, the parent may also be canceled. If + * opt_deepCancel is set, cancel() will be called on the parent (as well as any + * other ancestors if the parent is also a branch). If one or more branches were + * created with opt_propagateCancel set to true, the parent will be canceled if + * cancel() is called on all of those branches. + * + * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even + * if cancel() hasn't been called on some of the parent's branches. Has no + * effect on a branch without opt_propagateCancel set to true. */ -ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { - var coordinate = event.coordinate; - var geometry = this.sketchFeature_.getGeometry(); - var coordinates, last; - if (this.mode_ === ol.interaction.DrawMode.POINT) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - last = geometry.getCoordinates(); - last[0] = coordinate[0]; - last[1] = coordinate[1]; - geometry.setCoordinates(last); - } else { - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - coordinates = geometry.getCoordinates(); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - coordinates = this.sketchPolygonCoords_[0]; +goog.async.Deferred.prototype.cancel = function(opt_deepCancel) { + if (!this.hasFired()) { + if (this.parent_) { + // Get rid of the parent reference before potentially running the parent's + // canceler function to ensure that this cancellation isn't + // double-counted. + var parent = this.parent_; + delete this.parent_; + if (opt_deepCancel) { + parent.cancel(opt_deepCancel); + } else { + parent.branchCancel_(); + } } - if (this.atFinish_(event)) { - // snap to finish - coordinate = this.finishCoordinate_.slice(); + + if (this.onCancelFunction_) { + // Call in user-specified scope. + this.onCancelFunction_.call(this.defaultScope_, this); + } else { + this.silentlyCanceled_ = true; } - var sketchPointGeom = this.sketchPoint_.getGeometry(); - goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point); - sketchPointGeom.setCoordinates(coordinate); - last = coordinates[coordinates.length - 1]; - last[0] = coordinate[0]; - last[1] = coordinate[1]; - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - geometry.setCoordinates(coordinates); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - var sketchLineGeom = this.sketchLine_.getGeometry(); - goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString); - sketchLineGeom.setCoordinates(coordinates); - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - geometry.setCoordinates(this.sketchPolygonCoords_); + if (!this.hasFired()) { + this.errback(new goog.async.Deferred.CanceledError(this)); } + } else if (this.result_ instanceof goog.async.Deferred) { + this.result_.cancel(); } - this.updateSketchFeatures_(); }; /** - * Add a new coordinate to the drawing. - * @param {ol.MapBrowserEvent} event Event. + * Handle a single branch being canceled. Once all branches are canceled, this + * Deferred will be canceled as well. + * * @private */ -ol.interaction.Draw.prototype.addToDrawing_ = function(event) { - var coordinate = event.coordinate; - var geometry = this.sketchFeature_.getGeometry(); - var coordinates; - if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - this.finishCoordinate_ = coordinate.slice(); - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - coordinates = geometry.getCoordinates(); - coordinates.push(coordinate.slice()); - geometry.setCoordinates(coordinates); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - this.sketchPolygonCoords_[0].push(coordinate.slice()); - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - geometry.setCoordinates(this.sketchPolygonCoords_); +goog.async.Deferred.prototype.branchCancel_ = function() { + this.branches_--; + if (this.branches_ <= 0) { + this.cancel(); } - this.updateSketchFeatures_(); }; /** - * Stop drawing and add the sketch feature to the target layer. - * @api + * Called after a blocking Deferred fires. Unblocks this Deferred and resumes + * its execution sequence. + * + * @param {boolean} isSuccess Whether the result is a success or an error. + * @param {*} res The result of the blocking Deferred. + * @private */ -ol.interaction.Draw.prototype.finishDrawing = function() { - var sketchFeature = this.abortDrawing_(); - goog.asserts.assert(!goog.isNull(sketchFeature)); - var coordinates; - var geometry = sketchFeature.getGeometry(); - if (this.mode_ === ol.interaction.DrawMode.POINT) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - coordinates = geometry.getCoordinates(); - } else if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - coordinates = geometry.getCoordinates(); - // remove the redundant last point - coordinates.pop(); - geometry.setCoordinates(coordinates); - } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - // When we finish drawing a polygon on the last point, - // the last coordinate is duplicated as for LineString - // we force the replacement by the first point - this.sketchPolygonCoords_[0].pop(); - this.sketchPolygonCoords_[0].push(this.sketchPolygonCoords_[0][0]); - geometry.setCoordinates(this.sketchPolygonCoords_); - coordinates = geometry.getCoordinates(); - } +goog.async.Deferred.prototype.continue_ = function(isSuccess, res) { + this.blocked_ = false; + this.updateResult_(isSuccess, res); +}; - // cast multi-part geometries - if (this.type_ === ol.geom.GeometryType.MULTI_POINT) { - sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates])); - } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) { - sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates])); - } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) { - sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates])); - } - if (!goog.isNull(this.features_)) { - this.features_.push(sketchFeature); - } - if (!goog.isNull(this.source_)) { - this.source_.addFeature(sketchFeature); - } - this.dispatchEvent(new ol.DrawEvent(ol.DrawEventType.DRAWEND, sketchFeature)); +/** + * Updates the current result based on the success or failure of the last action + * in the execution sequence. + * + * @param {boolean} isSuccess Whether the new result is a success or an error. + * @param {*} res The result. + * @private + */ +goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) { + this.fired_ = true; + this.result_ = res; + this.hadError_ = !isSuccess; + this.fire_(); }; /** - * Stop drawing without adding the sketch feature to the target layer. - * @return {ol.Feature} The sketch feature (or null if none). + * Verifies that the Deferred has not yet been fired. + * * @private + * @throws {Error} If this has already been fired. */ -ol.interaction.Draw.prototype.abortDrawing_ = function() { - this.finishCoordinate_ = null; - var sketchFeature = this.sketchFeature_; - if (!goog.isNull(sketchFeature)) { - this.sketchFeature_ = null; - this.sketchPoint_ = null; - this.sketchLine_ = null; - this.overlay_.getFeatures().clear(); +goog.async.Deferred.prototype.check_ = function() { + if (this.hasFired()) { + if (!this.silentlyCanceled_) { + throw new goog.async.Deferred.AlreadyCalledError(this); + } + this.silentlyCanceled_ = false; } - return sketchFeature; }; /** - * @inheritDoc + * Fire the execution sequence for this Deferred by passing the starting result + * to the first registered callback. + * @param {VALUE=} opt_result The starting result. */ -ol.interaction.Draw.prototype.shouldStopEvent = goog.functions.FALSE; +goog.async.Deferred.prototype.callback = function(opt_result) { + this.check_(); + this.assertNotDeferred_(opt_result); + this.updateResult_(true /* isSuccess */, opt_result); +}; /** - * Redraw the skecth features. - * @private + * Fire the execution sequence for this Deferred by passing the starting error + * result to the first registered errback. + * @param {*=} opt_result The starting error. */ -ol.interaction.Draw.prototype.updateSketchFeatures_ = function() { - var sketchFeatures = []; - if (!goog.isNull(this.sketchFeature_)) { - sketchFeatures.push(this.sketchFeature_); - } - if (!goog.isNull(this.sketchLine_)) { - sketchFeatures.push(this.sketchLine_); - } - if (!goog.isNull(this.sketchPoint_)) { - sketchFeatures.push(this.sketchPoint_); - } - this.overlay_.setFeatures(new ol.Collection(sketchFeatures)); +goog.async.Deferred.prototype.errback = function(opt_result) { + this.check_(); + this.assertNotDeferred_(opt_result); + this.makeStackTraceLong_(opt_result); + this.updateResult_(false /* isSuccess */, opt_result); }; /** + * Attempt to make the error's stack trace be long in that it contains the + * stack trace from the point where the deferred was created on top of the + * current stack trace to give additional context. + * @param {*} error * @private */ -ol.interaction.Draw.prototype.updateState_ = function() { - var map = this.getMap(); - var active = this.getActive(); - if (goog.isNull(map) || !active) { - this.abortDrawing_(); +goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) { + if (!goog.async.Deferred.LONG_STACK_TRACES) { + return; + } + if (this.constructorStack_ && goog.isObject(error) && error.stack && + // Stack looks like it was system generated. See + // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi + (/^[^\n]+(\n [^\n]+)+/).test(error.stack)) { + error.stack = error.stack + '\nDEFERRED OPERATION:\n' + + this.constructorStack_; } - this.overlay_.setMap(active ? map : null); }; /** - * Get the drawing mode. The mode for mult-part geometries is the same as for - * their single-part cousins. - * @param {ol.geom.GeometryType} type Geometry type. - * @return {ol.interaction.DrawMode} Drawing mode. + * Asserts that an object is not a Deferred. + * @param {*} obj The object to test. + * @throws {Error} Throws an exception if the object is a Deferred. * @private */ -ol.interaction.Draw.getMode_ = function(type) { - var mode; - if (type === ol.geom.GeometryType.POINT || - type === ol.geom.GeometryType.MULTI_POINT) { - mode = ol.interaction.DrawMode.POINT; - } else if (type === ol.geom.GeometryType.LINE_STRING || - type === ol.geom.GeometryType.MULTI_LINE_STRING) { - mode = ol.interaction.DrawMode.LINE_STRING; - } else if (type === ol.geom.GeometryType.POLYGON || - type === ol.geom.GeometryType.MULTI_POLYGON) { - mode = ol.interaction.DrawMode.POLYGON; - } - goog.asserts.assert(goog.isDef(mode)); - return mode; +goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) { + goog.asserts.assert( + !(obj instanceof goog.async.Deferred), + 'An execution sequence may not be initiated with a blocking Deferred.'); }; /** - * Draw mode. This collapses multi-part geometry types with their single-part - * cousins. - * @enum {string} + * Register a callback function to be called with a successful result. If no + * value is returned by the callback function, the result value is unchanged. If + * a new value is returned, it becomes the Deferred result and will be passed to + * the next callback in the execution sequence. + * + * If the function throws an error, the error becomes the new result and will be + * passed to the next errback in the execution chain. + * + * If the function returns a Deferred, the execution sequence will be blocked + * until that Deferred fires. Its result will be passed to the next callback (or + * errback if it is an error result) in this Deferred's execution sequence. + * + * @param {!function(this:T,VALUE):?} cb The function to be called with a + * successful result. + * @param {T=} opt_scope An optional scope to call the callback in. + * @return {!goog.async.Deferred} This Deferred. + * @template T */ -ol.interaction.DrawMode = { - POINT: 'Point', - LINE_STRING: 'LineString', - POLYGON: 'Polygon' +goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) { + return this.addCallbacks(cb, null, opt_scope); }; -goog.provide('ol.interaction.Modify'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.functions'); -goog.require('ol.Collection'); -goog.require('ol.CollectionEventType'); -goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); -goog.require('ol.MapBrowserEvent.EventType'); -goog.require('ol.ViewHint'); -goog.require('ol.coordinate'); -goog.require('ol.events.condition'); -goog.require('ol.extent'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); -goog.require('ol.interaction.Pointer'); -goog.require('ol.structs.RBush'); -goog.require('ol.style.Style'); - /** - * @typedef {{depth: (Array.<number>|undefined), - * feature: ol.Feature, - * geometry: ol.geom.SimpleGeometry, - * index: (number|undefined), - * segment: Array.<ol.Extent>}} + * Register a callback function to be called with an error result. If no value + * is returned by the function, the error result is unchanged. If a new error + * value is returned or thrown, that error becomes the Deferred result and will + * be passed to the next errback in the execution sequence. + * + * If the errback function handles the error by returning a non-error value, + * that result will be passed to the next normal callback in the sequence. + * + * If the function returns a Deferred, the execution sequence will be blocked + * until that Deferred fires. Its result will be passed to the next callback (or + * errback if it is an error result) in this Deferred's execution sequence. + * + * @param {!function(this:T,?):?} eb The function to be called on an + * unsuccessful result. + * @param {T=} opt_scope An optional scope to call the errback in. + * @return {!goog.async.Deferred<VALUE>} This Deferred. + * @template T */ -ol.interaction.SegmentDataType; - +goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) { + return this.addCallbacks(null, eb, opt_scope); +}; /** - * @classdesc - * Interaction for modifying vector data. + * Registers one function as both a callback and errback. * - * @constructor - * @extends {ol.interaction.Pointer} - * @param {olx.interaction.ModifyOptions} options Options. - * @api stable + * @param {!function(this:T,?):?} f The function to be called on any result. + * @param {T=} opt_scope An optional scope to call the function in. + * @return {!goog.async.Deferred} This Deferred. + * @template T */ -ol.interaction.Modify = function(options) { - - goog.base(this, { - handleDownEvent: ol.interaction.Modify.handleDownEvent_, - handleDragEvent: ol.interaction.Modify.handleDragEvent_, - handleEvent: ol.interaction.Modify.handleEvent, - handleUpEvent: ol.interaction.Modify.handleUpEvent_ - }); - - /** - * @type {ol.events.ConditionType} - * @private - */ - this.deleteCondition_ = goog.isDef(options.deleteCondition) ? - options.deleteCondition : - /** @type {ol.events.ConditionType} */ (goog.functions.and( - ol.events.condition.noModifierKeys, - ol.events.condition.singleClick)); - - /** - * Editing vertex. - * @type {ol.Feature} - * @private - */ - this.vertexFeature_ = null; - - /** - * Segments intersecting {@link this.vertexFeature_} by segment uid. - * @type {Object.<string, boolean>} - * @private - */ - this.vertexSegments_ = null; - - /** - * @type {ol.Pixel} - * @private - */ - this.lastPixel_ = [0, 0]; - - /** - * Segment RTree for each layer - * @type {Object.<*, ol.structs.RBush>} - * @private - */ - this.rBush_ = new ol.structs.RBush(); +goog.async.Deferred.prototype.addBoth = function(f, opt_scope) { + return this.addCallbacks(f, f, opt_scope); +}; - /** - * @type {number} - * @private - */ - this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ? - options.pixelTolerance : 10; - /** - * @type {boolean} - * @private - */ - this.snappedToVertex_ = false; +/** + * Registers a callback function and an errback function at the same position + * in the execution sequence. Only one of these functions will execute, + * depending on the error state during the execution sequence. + * + * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If + * the callback is invoked, the errback will be skipped, and vice versa. + * + * @param {(function(this:T,VALUE):?)|null} cb The function to be called on a + * successful result. + * @param {(function(this:T,?):?)|null} eb The function to be called on an + * unsuccessful result. + * @param {T=} opt_scope An optional scope to call the functions in. + * @return {!goog.async.Deferred} This Deferred. + * @template T + */ +goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) { + goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used'); + this.sequence_.push([cb, eb, opt_scope]); + if (this.hasFired()) { + this.fire_(); + } + return this; +}; - /** - * @type {Array} - * @private - */ - this.dragSegments_ = null; - /** - * Draw overlay where are sketch features are drawn. - * @type {ol.FeatureOverlay} - * @private - */ - this.overlay_ = new ol.FeatureOverlay({ - style: goog.isDef(options.style) ? options.style : - ol.interaction.Modify.getDefaultStyleFunction() +/** + * Implements {@see goog.Thenable} for seamless integration with + * {@see goog.Promise}. + * Deferred results are mutable and may represent multiple values over + * their lifetime. Calling {@code then} on a Deferred returns a Promise + * with the result of the Deferred at that point in its callback chain. + * Note that if the Deferred result is never mutated, and only + * {@code then} calls are made, the Deferred will behave like a Promise. + * + * @override + */ +goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected, + opt_context) { + var resolve, reject; + var promise = new goog.Promise(function(res, rej) { + // Copying resolvers to outer scope, so that they are available when the + // deferred callback fires (which may be synchronous). + resolve = res; + reject = rej; }); + this.addCallbacks(resolve, function(reason) { + if (reason instanceof goog.async.Deferred.CanceledError) { + promise.cancel(); + } else { + reject(reason); + } + }); + return promise.then(opt_onFulfilled, opt_onRejected, opt_context); +}; +goog.Thenable.addImplementation(goog.async.Deferred); - /** - * @const - * @private - * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)> } - */ - this.SEGMENT_WRITERS_ = { - 'Point': this.writePointGeometry_, - 'LineString': this.writeLineStringGeometry_, - 'LinearRing': this.writeLineStringGeometry_, - 'Polygon': this.writePolygonGeometry_, - 'MultiPoint': this.writeMultiPointGeometry_, - 'MultiLineString': this.writeMultiLineStringGeometry_, - 'MultiPolygon': this.writeMultiPolygonGeometry_, - 'GeometryCollection': this.writeGeometryCollectionGeometry_ - }; - - /** - * @type {ol.Collection.<ol.Feature>} - * @private - */ - this.features_ = options.features; - - this.features_.forEach(this.addFeature_, this); - goog.events.listen(this.features_, ol.CollectionEventType.ADD, - this.handleFeatureAdd_, false, this); - goog.events.listen(this.features_, ol.CollectionEventType.REMOVE, - this.handleFeatureRemove_, false, this); +/** + * Links another Deferred to the end of this Deferred's execution sequence. The + * result of this execution sequence will be passed as the starting result for + * the chained Deferred, invoking either its first callback or errback. + * + * @param {!goog.async.Deferred} otherDeferred The Deferred to chain. + * @return {!goog.async.Deferred} This Deferred. + */ +goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) { + this.addCallbacks( + otherDeferred.callback, otherDeferred.errback, otherDeferred); + return this; }; -goog.inherits(ol.interaction.Modify, ol.interaction.Pointer); /** - * @param {ol.Feature} feature Feature. - * @private + * Makes this Deferred wait for another Deferred's execution sequence to + * complete before continuing. + * + * This is equivalent to adding a callback that returns {@code otherDeferred}, + * but doesn't prevent additional callbacks from being added to + * {@code otherDeferred}. + * + * @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred + * to wait for. + * @return {!goog.async.Deferred} This Deferred. */ -ol.interaction.Modify.prototype.addFeature_ = function(feature) { - var geometry = feature.getGeometry(); - if (goog.isDef(this.SEGMENT_WRITERS_[geometry.getType()])) { - this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry); - } - var map = this.getMap(); - if (!goog.isNull(map)) { - this.handlePointerAtPixel_(this.lastPixel_, map); +goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) { + if (!(otherDeferred instanceof goog.async.Deferred)) { + // The Thenable case. + return this.addCallback(function() { + return otherDeferred; + }); } + return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred)); }; /** - * @inheritDoc + * Creates a branch off this Deferred's execution sequence, and returns it as a + * new Deferred. The branched Deferred's starting result will be shared with the + * parent at the point of the branch, even if further callbacks are added to the + * parent. + * + * All branches at the same stage in the execution sequence will receive the + * same starting value. + * + * @param {boolean=} opt_propagateCancel If cancel() is called on every child + * branch created with opt_propagateCancel, the parent will be canceled as + * well. + * @return {!goog.async.Deferred<VALUE>} A Deferred that will be started with + * the computed result from this stage in the execution sequence. */ -ol.interaction.Modify.prototype.setMap = function(map) { - this.overlay_.setMap(map); - goog.base(this, 'setMap', map); +goog.async.Deferred.prototype.branch = function(opt_propagateCancel) { + var d = new goog.async.Deferred(); + this.chainDeferred(d); + if (opt_propagateCancel) { + d.parent_ = this; + this.branches_++; + } + return d; }; /** - * @param {ol.CollectionEvent} evt Event. - * @private + * @return {boolean} Whether the execution sequence has been started on this + * Deferred by invoking {@code callback} or {@code errback}. */ -ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) { - var feature = evt.element; - goog.asserts.assertInstanceof(feature, ol.Feature); - this.addFeature_(feature); +goog.async.Deferred.prototype.hasFired = function() { + return this.fired_; }; /** - * @param {ol.CollectionEvent} evt Event. - * @private + * @param {*} res The latest result in the execution sequence. + * @return {boolean} Whether the current result is an error that should cause + * the next errback to fire. May be overridden by subclasses to handle + * special error types. + * @protected */ -ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) { - var feature = evt.element; - var rBush = this.rBush_; - var i, nodesToRemove = []; - rBush.forEachInExtent(feature.getGeometry().getExtent(), function(node) { - if (feature === node.feature) { - nodesToRemove.push(node); - } - }); - for (i = nodesToRemove.length - 1; i >= 0; --i) { - rBush.remove(nodesToRemove[i]); - } - // There remains only vertexFeature… - if (!goog.isNull(this.vertexFeature_) && - this.features_.getLength() === 0) { - this.overlay_.removeFeature(this.vertexFeature_); - this.vertexFeature_ = null; - } +goog.async.Deferred.prototype.isError = function(res) { + return res instanceof Error; }; /** - * @param {ol.Feature} feature Feature - * @param {ol.geom.Point} geometry Geometry. + * @return {boolean} Whether an errback exists in the remaining sequence. * @private */ -ol.interaction.Modify.prototype.writePointGeometry_ = - function(feature, geometry) { - var coordinates = geometry.getCoordinates(); - var segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - feature: feature, - geometry: geometry, - segment: [coordinates, coordinates] +goog.async.Deferred.prototype.hasErrback_ = function() { + return goog.array.some(this.sequence_, function(sequenceRow) { + // The errback is the second element in the array. + return goog.isFunction(sequenceRow[1]); }); - this.rBush_.insert(geometry.getExtent(), segmentData); }; /** - * @param {ol.Feature} feature Feature - * @param {ol.geom.MultiPoint} geometry Geometry. + * Exhausts the execution sequence while a result is available. The result may + * be modified by callbacks or errbacks, and execution will block if the + * returned result is an incomplete Deferred. + * * @private */ -ol.interaction.Modify.prototype.writeMultiPointGeometry_ = - function(feature, geometry) { - var points = geometry.getCoordinates(); - var coordinates, i, ii, segmentData; - for (i = 0, ii = points.length; i < ii; ++i) { - coordinates = points[i]; - segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - feature: feature, - geometry: geometry, - depth: [i], - index: i, - segment: [coordinates, coordinates] - }); - this.rBush_.insert(geometry.getExtent(), segmentData); +goog.async.Deferred.prototype.fire_ = function() { + if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) { + // It is possible to add errbacks after the Deferred has fired. If a new + // errback is added immediately after the Deferred encountered an unhandled + // error, but before that error is rethrown, the error is unscheduled. + goog.async.Deferred.unscheduleError_(this.unhandledErrorId_); + this.unhandledErrorId_ = 0; } -}; + if (this.parent_) { + this.parent_.branches_--; + delete this.parent_; + } -/** - * @param {ol.Feature} feature Feature - * @param {ol.geom.LineString} geometry Geometry. - * @private - */ -ol.interaction.Modify.prototype.writeLineStringGeometry_ = - function(feature, geometry) { - var coordinates = geometry.getCoordinates(); - var i, ii, segment, segmentData; - for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { - segment = coordinates.slice(i, i + 2); - segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - feature: feature, - geometry: geometry, - index: i, - segment: segment - }); - this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + var res = this.result_; + var unhandledException = false; + var isNewlyBlocked = false; + + while (this.sequence_.length && !this.blocked_) { + var sequenceEntry = this.sequence_.shift(); + + var callback = sequenceEntry[0]; + var errback = sequenceEntry[1]; + var scope = sequenceEntry[2]; + + var f = this.hadError_ ? errback : callback; + if (f) { + /** @preserveTry */ + try { + var ret = f.call(scope || this.defaultScope_, res); + + // If no result, then use previous result. + if (goog.isDef(ret)) { + // Bubble up the error as long as the return value hasn't changed. + this.hadError_ = this.hadError_ && (ret == res || this.isError(ret)); + this.result_ = res = ret; + } + + if (goog.Thenable.isImplementedBy(res)) { + isNewlyBlocked = true; + this.blocked_ = true; + } + + } catch (ex) { + res = ex; + this.hadError_ = true; + this.makeStackTraceLong_(res); + + if (!this.hasErrback_()) { + // If an error is thrown with no additional errbacks in the queue, + // prepare to rethrow the error. + unhandledException = true; + } + } + } + } + + this.result_ = res; + + if (isNewlyBlocked) { + var onCallback = goog.bind(this.continue_, this, true /* isSuccess */); + var onErrback = goog.bind(this.continue_, this, false /* isSuccess */); + + if (res instanceof goog.async.Deferred) { + res.addCallbacks(onCallback, onErrback); + res.blocking_ = true; + } else { + res.then(onCallback, onErrback); + } + } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) && + !(res instanceof goog.async.Deferred.CanceledError)) { + this.hadError_ = true; + unhandledException = true; + } + + if (unhandledException) { + // Rethrow the unhandled error after a timeout. Execution will continue, but + // the error will be seen by global handlers and the user. The throw will + // be canceled if another errback is appended before the timeout executes. + // The error's original stack trace is preserved where available. + this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res); } }; /** - * @param {ol.Feature} feature Feature - * @param {ol.geom.MultiLineString} geometry Geometry. - * @private + * Creates a Deferred that has an initial result. + * + * @param {*=} opt_result The result. + * @return {!goog.async.Deferred} The new Deferred. */ -ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ = - function(feature, geometry) { - var lines = geometry.getCoordinates(); - var coordinates, i, ii, j, jj, segment, segmentData; - for (j = 0, jj = lines.length; j < jj; ++j) { - coordinates = lines[j]; - for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { - segment = coordinates.slice(i, i + 2); - segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - feature: feature, - geometry: geometry, - depth: [j], - index: i, - segment: segment - }); - this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); - } - } +goog.async.Deferred.succeed = function(opt_result) { + var d = new goog.async.Deferred(); + d.callback(opt_result); + return d; }; /** - * @param {ol.Feature} feature Feature - * @param {ol.geom.Polygon} geometry Geometry. - * @private + * Creates a Deferred that fires when the given promise resolves. + * Use only during migration to Promises. + * + * @param {!goog.Promise<T>} promise + * @return {!goog.async.Deferred<T>} The new Deferred. + * @template T */ -ol.interaction.Modify.prototype.writePolygonGeometry_ = - function(feature, geometry) { - var rings = geometry.getCoordinates(); - var coordinates, i, ii, j, jj, segment, segmentData; - for (j = 0, jj = rings.length; j < jj; ++j) { - coordinates = rings[j]; - for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { - segment = coordinates.slice(i, i + 2); - segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - feature: feature, - geometry: geometry, - depth: [j], - index: i, - segment: segment - }); - this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); - } - } +goog.async.Deferred.fromPromise = function(promise) { + var d = new goog.async.Deferred(); + d.callback(); + d.addCallback(function() { + return promise; + }); + return d; }; /** - * @param {ol.Feature} feature Feature - * @param {ol.geom.MultiPolygon} geometry Geometry. - * @private + * Creates a Deferred that has an initial error result. + * + * @param {*} res The error result. + * @return {!goog.async.Deferred} The new Deferred. */ -ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = - function(feature, geometry) { - var polygons = geometry.getCoordinates(); - var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; - for (k = 0, kk = polygons.length; k < kk; ++k) { - rings = polygons[k]; - for (j = 0, jj = rings.length; j < jj; ++j) { - coordinates = rings[j]; - for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { - segment = coordinates.slice(i, i + 2); - segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - feature: feature, - geometry: geometry, - depth: [j, k], - index: i, - segment: segment - }); - this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); - } - } - } +goog.async.Deferred.fail = function(res) { + var d = new goog.async.Deferred(); + d.errback(res); + return d; }; /** - * @param {ol.Feature} feature Feature - * @param {ol.geom.GeometryCollection} geometry Geometry. - * @private + * Creates a Deferred that has already been canceled. + * + * @return {!goog.async.Deferred} The new Deferred. */ -ol.interaction.Modify.prototype.writeGeometryCollectionGeometry_ = - function(feature, geometry) { - var i, geometries = geometry.getGeometriesArray(); - for (i = 0; i < geometries.length; ++i) { - this.SEGMENT_WRITERS_[geometries[i].getType()].call( - this, feature, geometries[i]); - } +goog.async.Deferred.canceled = function() { + var d = new goog.async.Deferred(); + d.cancel(); + return d; }; /** - * @param {ol.Coordinate} coordinates Coordinates. - * @return {ol.Feature} Vertex feature. - * @private + * Normalizes values that may or may not be Deferreds. + * + * If the input value is a Deferred, the Deferred is branched (so the original + * execution sequence is not modified) and the input callback added to the new + * branch. The branch is returned to the caller. + * + * If the input value is not a Deferred, the callback will be executed + * immediately and an already firing Deferred will be returned to the caller. + * + * In the following (contrived) example, if <code>isImmediate</code> is true + * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay. + * + * <pre> + * var value; + * if (isImmediate) { + * value = 3; + * } else { + * value = new goog.async.Deferred(); + * setTimeout(function() { value.callback(6); }, 2000); + * } + * + * var d = goog.async.Deferred.when(value, alert); + * </pre> + * + * @param {*} value Deferred or normal value to pass to the callback. + * @param {!function(this:T, ?):?} callback The callback to execute. + * @param {T=} opt_scope An optional scope to call the callback in. + * @return {!goog.async.Deferred} A new Deferred that will call the input + * callback with the input value. + * @template T */ -ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = - function(coordinates) { - var vertexFeature = this.vertexFeature_; - if (goog.isNull(vertexFeature)) { - vertexFeature = new ol.Feature(new ol.geom.Point(coordinates)); - this.vertexFeature_ = vertexFeature; - this.overlay_.addFeature(vertexFeature); +goog.async.Deferred.when = function(value, callback, opt_scope) { + if (value instanceof goog.async.Deferred) { + return value.branch(true).addCallback(callback, opt_scope); } else { - var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); - geometry.setCoordinates(coordinates); + return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope); } - return vertexFeature; }; + /** - * @param {ol.MapBrowserPointerEvent} evt Event. - * @return {boolean} Start drag sequence? - * @this {ol.interaction.Modify} - * @private + * An error sub class that is used when a Deferred has already been called. + * @param {!goog.async.Deferred} deferred The Deferred. + * + * @constructor + * @extends {goog.debug.Error} */ -ol.interaction.Modify.handleDownEvent_ = function(evt) { - this.handlePointerAtPixel_(evt.pixel, evt.map); - this.dragSegments_ = []; - var vertexFeature = this.vertexFeature_; - if (!goog.isNull(vertexFeature)) { - var insertVertices = []; - var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); - var vertex = geometry.getCoordinates(); - var vertexExtent = ol.extent.boundingExtent([vertex]); - var segmentDataMatches = this.rBush_.getInExtent(vertexExtent); - for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) { - var segmentDataMatch = segmentDataMatches[i]; - var segment = segmentDataMatch.segment; - if (ol.coordinate.equals(segment[0], vertex)) { - this.dragSegments_.push([segmentDataMatch, 0]); - } else if (ol.coordinate.equals(segment[1], vertex)) { - this.dragSegments_.push([segmentDataMatch, 1]); - } else if (goog.getUid(segment) in this.vertexSegments_) { - insertVertices.push([segmentDataMatch, vertex]); - } - } - for (i = insertVertices.length - 1; i >= 0; --i) { - this.insertVertex_.apply(this, insertVertices[i]); - } - } - return !goog.isNull(this.vertexFeature_); +goog.async.Deferred.AlreadyCalledError = function(deferred) { + goog.debug.Error.call(this); + + /** + * The Deferred that raised this error. + * @type {goog.async.Deferred} + */ + this.deferred = deferred; }; +goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error); + + +/** @override */ +goog.async.Deferred.AlreadyCalledError.prototype.message = + 'Deferred has already fired'; + + +/** @override */ +goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError'; + /** - * @param {ol.MapBrowserPointerEvent} evt Event. - * @this {ol.interaction.Modify} - * @private + * An error sub class that is used when a Deferred is canceled. + * + * @param {!goog.async.Deferred} deferred The Deferred object. + * @constructor + * @extends {goog.debug.Error} */ -ol.interaction.Modify.handleDragEvent_ = function(evt) { - var vertex = evt.coordinate; - for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) { - var dragSegment = this.dragSegments_[i]; - var segmentData = dragSegment[0]; - var depth = segmentData.depth; - var geometry = segmentData.geometry; - var coordinates = geometry.getCoordinates(); - var segment = segmentData.segment; - var index = dragSegment[1]; +goog.async.Deferred.CanceledError = function(deferred) { + goog.debug.Error.call(this); - while (vertex.length < geometry.getStride()) { - vertex.push(0); - } + /** + * The Deferred that raised this error. + * @type {goog.async.Deferred} + */ + this.deferred = deferred; +}; +goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error); - switch (geometry.getType()) { - case ol.geom.GeometryType.POINT: - coordinates = vertex; - segment[0] = segment[1] = vertex; - break; - case ol.geom.GeometryType.MULTI_POINT: - coordinates[segmentData.index] = vertex; - segment[0] = segment[1] = vertex; - break; - case ol.geom.GeometryType.LINE_STRING: - coordinates[segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case ol.geom.GeometryType.MULTI_LINE_STRING: - coordinates[depth[0]][segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case ol.geom.GeometryType.POLYGON: - coordinates[depth[0]][segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case ol.geom.GeometryType.MULTI_POLYGON: - coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex; - segment[index] = vertex; - break; - } - geometry.setCoordinates(coordinates); - this.createOrUpdateVertexFeature_(vertex); - } +/** @override */ +goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled'; + + +/** @override */ +goog.async.Deferred.CanceledError.prototype.name = 'CanceledError'; + + + +/** + * Wrapper around errors that are scheduled to be thrown by failing deferreds + * after a timeout. + * + * @param {*} error Error from a failing deferred. + * @constructor + * @final + * @private + * @struct + */ +goog.async.Deferred.Error_ = function(error) { + /** @const @private {number} */ + this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0); + + /** @const @private {*} */ + this.error_ = error; }; /** - * @param {ol.MapBrowserPointerEvent} evt Event. - * @return {boolean} Stop drag sequence? - * @this {ol.interaction.Modify} - * @private + * Actually throws the error and removes it from the list of pending + * deferred errors. */ -ol.interaction.Modify.handleUpEvent_ = function(evt) { - var segmentData; - for (var i = this.dragSegments_.length - 1; i >= 0; --i) { - segmentData = this.dragSegments_[i][0]; - this.rBush_.update(ol.extent.boundingExtent(segmentData.segment), - segmentData); - } - return false; +goog.async.Deferred.Error_.prototype.throwError = function() { + goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_], + 'Cannot throw an error that is not scheduled.'); + delete goog.async.Deferred.errorMap_[this.id_]; + throw this.error_; }; /** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.Modify} - * @api + * Resets the error throw timer. */ -ol.interaction.Modify.handleEvent = function(mapBrowserEvent) { - var handled; - if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] && - mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) { - this.handlePointerMove_(mapBrowserEvent); - } - if (!goog.isNull(this.vertexFeature_) && this.snappedToVertex_ && - this.deleteCondition_(mapBrowserEvent)) { - var geometry = this.vertexFeature_.getGeometry(); - goog.asserts.assertInstanceof(geometry, ol.geom.Point); - handled = this.removeVertex_(); - } - return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && - !handled; +goog.async.Deferred.Error_.prototype.resetTimer = function() { + goog.global.clearTimeout(this.id_); }; /** - * @param {ol.MapBrowserEvent} evt Event. + * Map of unhandled errors scheduled to be rethrown in a future timestep. + * @private {!Object<number|string, goog.async.Deferred.Error_>} + */ +goog.async.Deferred.errorMap_ = {}; + + +/** + * Schedules an error to be thrown after a delay. + * @param {*} error Error from a failing deferred. + * @return {number} Id of the error. * @private */ -ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) { - this.lastPixel_ = evt.pixel; - this.handlePointerAtPixel_(evt.pixel, evt.map); +goog.async.Deferred.scheduleError_ = function(error) { + var deferredError = new goog.async.Deferred.Error_(error); + goog.async.Deferred.errorMap_[deferredError.id_] = deferredError; + return deferredError.id_; }; /** - * @param {ol.Pixel} pixel Pixel - * @param {ol.Map} map Map. + * Unschedules an error from being thrown. + * @param {number} id Id of the deferred error to unschedule. * @private */ -ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) { - var pixelCoordinate = map.getCoordinateFromPixel(pixel); - var sortByDistance = function(a, b) { - return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) - - ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment); - }; +goog.async.Deferred.unscheduleError_ = function(id) { + var error = goog.async.Deferred.errorMap_[id]; + if (error) { + error.resetTimer(); + delete goog.async.Deferred.errorMap_[id]; + } +}; - var lowerLeft = map.getCoordinateFromPixel( - [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]); - var upperRight = map.getCoordinateFromPixel( - [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]); - var box = ol.extent.boundingExtent([lowerLeft, upperRight]); - var rBush = this.rBush_; - var nodes = rBush.getInExtent(box); - if (nodes.length > 0) { - nodes.sort(sortByDistance); - var node = nodes[0]; - var closestSegment = node.segment; - var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, - closestSegment)); - var vertexPixel = map.getPixelFromCoordinate(vertex); - if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= - this.pixelTolerance_) { - var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); - var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); - var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); - var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); - var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); - this.snappedToVertex_ = dist <= this.pixelTolerance_; - if (this.snappedToVertex_) { - vertex = squaredDist1 > squaredDist2 ? - closestSegment[1] : closestSegment[0]; - } - this.createOrUpdateVertexFeature_(vertex); - var vertexSegments = {}; - vertexSegments[goog.getUid(closestSegment)] = true; - var segment; - for (var i = 1, ii = nodes.length; i < ii; ++i) { - segment = nodes[i].segment; - if ((ol.coordinate.equals(closestSegment[0], segment[0]) && - ol.coordinate.equals(closestSegment[1], segment[1]) || - (ol.coordinate.equals(closestSegment[0], segment[1]) && - ol.coordinate.equals(closestSegment[1], segment[0])))) { - vertexSegments[goog.getUid(segment)] = true; - } else { - break; - } - } - this.vertexSegments_ = vertexSegments; - return; - } - } - if (!goog.isNull(this.vertexFeature_)) { - this.overlay_.removeFeature(this.vertexFeature_); - this.vertexFeature_ = null; +/** + * Asserts that there are no pending deferred errors. If there are any + * scheduled errors, one will be thrown immediately to make this function fail. + */ +goog.async.Deferred.assertNoErrors = function() { + var map = goog.async.Deferred.errorMap_; + for (var key in map) { + var error = map[key]; + error.resetTimer(); + error.throwError(); } }; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @param {ol.interaction.SegmentDataType} segmentData Segment data. - * @param {ol.Coordinate} vertex Vertex. - * @private + * @fileoverview A wrapper for the HTML5 FileError object. + * */ -ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) { - var segment = segmentData.segment; - var feature = segmentData.feature; - var geometry = segmentData.geometry; - var depth = segmentData.depth; - var index = segmentData.index; - var coordinates; - - while (vertex.length < geometry.getStride()) { - vertex.push(0); - } - switch (geometry.getType()) { - case ol.geom.GeometryType.MULTI_LINE_STRING: - goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString); - coordinates = geometry.getCoordinates(); - coordinates[depth[0]].splice(index + 1, 0, vertex); - break; - case ol.geom.GeometryType.POLYGON: - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon); - coordinates = geometry.getCoordinates(); - coordinates[depth[0]].splice(index + 1, 0, vertex); - break; - case ol.geom.GeometryType.MULTI_POLYGON: - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon); - coordinates = geometry.getCoordinates(); - coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex); - break; - case ol.geom.GeometryType.LINE_STRING: - goog.asserts.assertInstanceof(geometry, ol.geom.LineString); - coordinates = geometry.getCoordinates(); - coordinates.splice(index + 1, 0, vertex); - break; - default: - return; - } +goog.provide('goog.fs.Error'); +goog.provide('goog.fs.Error.ErrorCode'); - geometry.setCoordinates(coordinates); - var rTree = this.rBush_; - goog.asserts.assert(goog.isDef(segment)); - rTree.remove(segmentData); - goog.asserts.assert(goog.isDef(index)); - this.updateSegmentIndices_(geometry, index, depth, 1); - var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - segment: [segment[0], vertex], - feature: feature, - geometry: geometry, - depth: depth, - index: index - }); - rTree.insert(ol.extent.boundingExtent(newSegmentData.segment), - newSegmentData); - this.dragSegments_.push([newSegmentData, 1]); +goog.require('goog.debug.Error'); +goog.require('goog.object'); +goog.require('goog.string'); - var newSegmentData2 = /** @type {ol.interaction.SegmentDataType} */ ({ - segment: [vertex, segment[1]], - feature: feature, - geometry: geometry, - depth: depth, - index: index + 1 - }); - rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment), - newSegmentData2); - this.dragSegments_.push([newSegmentData2, 0]); -}; /** - * Removes a vertex from all matching features. - * @return {boolean} True when a vertex was removed. - * @private + * A filesystem error. Since the filesystem API is asynchronous, stack traces + * are less useful for identifying where errors come from, so this includes a + * large amount of metadata in the message. + * + * @param {!DOMError} error + * @param {string} action The action being undertaken when the error was raised. + * @constructor + * @extends {goog.debug.Error} + * @final */ -ol.interaction.Modify.prototype.removeVertex_ = function() { - var dragSegments = this.dragSegments_; - var segmentsByFeature = {}; - var deleted = false; - var component, coordinates, dragSegment, geometry, i, index, left; - var newIndex, newSegment, right, segmentData, uid; - for (i = dragSegments.length - 1; i >= 0; --i) { - dragSegment = dragSegments[i]; - segmentData = dragSegment[0]; - geometry = segmentData.geometry; - coordinates = geometry.getCoordinates(); - uid = goog.getUid(segmentData.feature); - left = right = index = undefined; - if (dragSegment[1] === 0) { - right = segmentData; - index = segmentData.index; - } else if (dragSegment[1] == 1) { - left = segmentData; - index = segmentData.index + 1; - } - if (!(uid in segmentsByFeature)) { - segmentsByFeature[uid] = [left, right, index]; - } - newSegment = segmentsByFeature[uid]; - if (goog.isDef(left)) { - newSegment[0] = left; - } - if (goog.isDef(right)) { - newSegment[1] = right; - } - if (goog.isDef(newSegment[0]) && goog.isDef(newSegment[1])) { - component = coordinates; - deleted = false; - newIndex = index - 1; - switch (geometry.getType()) { - case ol.geom.GeometryType.MULTI_LINE_STRING: - coordinates[segmentData.depth[0]].splice(index, 1); - deleted = true; - break; - case ol.geom.GeometryType.LINE_STRING: - coordinates.splice(index, 1); - deleted = true; - break; - case ol.geom.GeometryType.MULTI_POLYGON: - component = component[segmentData.depth[1]]; - /* falls through */ - case ol.geom.GeometryType.POLYGON: - component = component[segmentData.depth[0]]; - if (component.length > 4) { - if (index == component.length - 1) { - index = 0; - } - component.splice(index, 1); - deleted = true; - if (index === 0) { - // close the ring again - component.pop(); - component.push(component[0]); - newIndex = component.length - 1; - } - } - break; - } +goog.fs.Error = function(error, action) { + /** @type {string} */ + this.name; - if (deleted) { - this.rBush_.remove(newSegment[0]); - this.rBush_.remove(newSegment[1]); - geometry.setCoordinates(coordinates); - goog.asserts.assert(newIndex >= 0); - var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({ - depth: segmentData.depth, - feature: segmentData.feature, - geometry: segmentData.geometry, - index: newIndex, - segment: [newSegment[0].segment[0], newSegment[1].segment[1]] - }); - this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment), - newSegmentData); - this.updateSegmentIndices_(geometry, index, segmentData.depth, -1); + /** + * @type {goog.fs.Error.ErrorCode} + * @deprecated Use the 'name' or 'message' field instead. + */ + this.code; - this.overlay_.removeFeature(this.vertexFeature_); - this.vertexFeature_ = null; - } - } + if (goog.isDef(error.name)) { + this.name = error.name; + // TODO(user): Remove warning suppression after JSCompiler stops + // firing a spurious warning here. + /** @suppress {deprecated} */ + this.code = goog.fs.Error.getCodeFromName_(error.name); + } else { + this.code = error.code; + this.name = goog.fs.Error.getNameFromCode_(error.code); } - return deleted; + goog.fs.Error.base(this, 'constructor', + goog.string.subs('%s %s', this.name, action)); }; +goog.inherits(goog.fs.Error, goog.debug.Error); /** - * @param {ol.geom.SimpleGeometry} geometry Geometry. - * @param {number} index Index. - * @param {Array.<number>|undefined} depth Depth. - * @param {number} delta Delta (1 or -1). - * @private + * Names of errors that may be thrown by the File API, the File System API, or + * the File Writer API. + * + * @see http://dev.w3.org/2006/webapi/FileAPI/#ErrorAndException + * @see http://www.w3.org/TR/file-system-api/#definitions + * @see http://dev.w3.org/2009/dap/file-system/file-writer.html#definitions + * @enum {string} */ -ol.interaction.Modify.prototype.updateSegmentIndices_ = function( - geometry, index, depth, delta) { - this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) { - if (segmentDataMatch.geometry === geometry && - (!goog.isDef(depth) || - goog.array.equals(segmentDataMatch.depth, depth)) && - segmentDataMatch.index > index) { - segmentDataMatch.index += delta; - } - }); +goog.fs.Error.ErrorName = { + ABORT: 'AbortError', + ENCODING: 'EncodingError', + INVALID_MODIFICATION: 'InvalidModificationError', + INVALID_STATE: 'InvalidStateError', + NOT_FOUND: 'NotFoundError', + NOT_READABLE: 'NotReadableError', + NO_MODIFICATION_ALLOWED: 'NoModificationAllowedError', + PATH_EXISTS: 'PathExistsError', + QUOTA_EXCEEDED: 'QuotaExceededError', + SECURITY: 'SecurityError', + SYNTAX: 'SyntaxError', + TYPE_MISMATCH: 'TypeMismatchError' }; /** - * @return {ol.style.StyleFunction} Styles. + * Error codes for file errors. + * @see http://www.w3.org/TR/file-system-api/#idl-def-FileException + * + * @enum {number} + * @deprecated Use the 'name' or 'message' attribute instead. */ -ol.interaction.Modify.getDefaultStyleFunction = function() { - var style = ol.style.createDefaultEditingStyles(); - return function(feature, resolution) { - return style[ol.geom.GeometryType.POINT]; - }; +goog.fs.Error.ErrorCode = { + NOT_FOUND: 1, + SECURITY: 2, + ABORT: 3, + NOT_READABLE: 4, + ENCODING: 5, + NO_MODIFICATION_ALLOWED: 6, + INVALID_STATE: 7, + SYNTAX: 8, + INVALID_MODIFICATION: 9, + QUOTA_EXCEEDED: 10, + TYPE_MISMATCH: 11, + PATH_EXISTS: 12 }; -goog.provide('ol.interaction.Select'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.functions'); -goog.require('ol.CollectionEventType'); -goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); -goog.require('ol.events.condition'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.interaction.Interaction'); -goog.require('ol.style.Style'); +/** + * @param {goog.fs.Error.ErrorCode} code + * @return {string} name + * @private + */ +goog.fs.Error.getNameFromCode_ = function(code) { + var name = goog.object.findKey(goog.fs.Error.NameToCodeMap_, function(c) { + return code == c; + }); + if (!goog.isDef(name)) { + throw new Error('Invalid code: ' + code); + } + return name; +}; /** - * @classdesc - * Handles selection of vector data. A {@link ol.FeatureOverlay} is maintained - * internally to store the selected feature(s). Which features are selected is - * determined by the `condition` option, and optionally the `toggle` or - * `add`/`remove` options. - * - * @constructor - * @extends {ol.interaction.Interaction} - * @param {olx.interaction.SelectOptions=} opt_options Options. - * @api stable + * Returns the code that corresponds to the given name. + * @param {string} name + * @return {goog.fs.Error.ErrorCode} code + * @private */ -ol.interaction.Select = function(opt_options) { +goog.fs.Error.getCodeFromName_ = function(name) { + return goog.fs.Error.NameToCodeMap_[name]; +}; - goog.base(this, { - handleEvent: ol.interaction.Select.handleEvent - }); - var options = goog.isDef(opt_options) ? opt_options : {}; +/** + * Mapping from error names to values from the ErrorCode enum. + * @see http://www.w3.org/TR/file-system-api/#definitions. + * @private {!Object<string, goog.fs.Error.ErrorCode>} + */ +goog.fs.Error.NameToCodeMap_ = goog.object.create( + goog.fs.Error.ErrorName.ABORT, + goog.fs.Error.ErrorCode.ABORT, - /** - * @private - * @type {ol.events.ConditionType} - */ - this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.events.condition.singleClick; + goog.fs.Error.ErrorName.ENCODING, + goog.fs.Error.ErrorCode.ENCODING, - /** - * @private - * @type {ol.events.ConditionType} - */ - this.addCondition_ = goog.isDef(options.addCondition) ? - options.addCondition : ol.events.condition.never; + goog.fs.Error.ErrorName.INVALID_MODIFICATION, + goog.fs.Error.ErrorCode.INVALID_MODIFICATION, - /** - * @private - * @type {ol.events.ConditionType} - */ - this.removeCondition_ = goog.isDef(options.removeCondition) ? - options.removeCondition : ol.events.condition.never; + goog.fs.Error.ErrorName.INVALID_STATE, + goog.fs.Error.ErrorCode.INVALID_STATE, - /** - * @private - * @type {ol.events.ConditionType} - */ - this.toggleCondition_ = goog.isDef(options.toggleCondition) ? - options.toggleCondition : ol.events.condition.shiftKeyOnly; + goog.fs.Error.ErrorName.NOT_FOUND, + goog.fs.Error.ErrorCode.NOT_FOUND, - var layerFilter; - if (goog.isDef(options.layers)) { - if (goog.isFunction(options.layers)) { - layerFilter = options.layers; - } else { - var layers = options.layers; - layerFilter = - /** - * @param {ol.layer.Layer} layer Layer. - * @return {boolean} Include. - */ - function(layer) { - return goog.array.contains(layers, layer); - }; - } - } else { - layerFilter = goog.functions.TRUE; - } + goog.fs.Error.ErrorName.NOT_READABLE, + goog.fs.Error.ErrorCode.NOT_READABLE, - /** - * @private - * @type {function(ol.layer.Layer): boolean} - */ - this.layerFilter_ = layerFilter; + goog.fs.Error.ErrorName.NO_MODIFICATION_ALLOWED, + goog.fs.Error.ErrorCode.NO_MODIFICATION_ALLOWED, - /** - * @private - * @type {ol.FeatureOverlay} - */ - this.featureOverlay_ = new ol.FeatureOverlay({ - style: goog.isDef(options.style) ? options.style : - ol.interaction.Select.getDefaultStyleFunction() - }); + goog.fs.Error.ErrorName.PATH_EXISTS, + goog.fs.Error.ErrorCode.PATH_EXISTS, - var features = this.featureOverlay_.getFeatures(); - goog.events.listen(features, ol.CollectionEventType.ADD, - this.addFeature_, false, this); - goog.events.listen(features, ol.CollectionEventType.REMOVE, - this.removeFeature_, false, this); + goog.fs.Error.ErrorName.QUOTA_EXCEEDED, + goog.fs.Error.ErrorCode.QUOTA_EXCEEDED, -}; -goog.inherits(ol.interaction.Select, ol.interaction.Interaction); + goog.fs.Error.ErrorName.SECURITY, + goog.fs.Error.ErrorCode.SECURITY, + + goog.fs.Error.ErrorName.SYNTAX, + goog.fs.Error.ErrorCode.SYNTAX, + goog.fs.Error.ErrorName.TYPE_MISMATCH, + goog.fs.Error.ErrorCode.TYPE_MISMATCH); + +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Get the selected features. - * @return {ol.Collection.<ol.Feature>} Features collection. - * @api stable + * @fileoverview A wrapper for the HTML5 File ProgressEvent objects. + * */ -ol.interaction.Select.prototype.getFeatures = function() { - return this.featureOverlay_.getFeatures(); -}; +goog.provide('goog.fs.ProgressEvent'); +goog.require('goog.events.Event'); -/** - * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {ol.interaction.Select} - * @api - */ -ol.interaction.Select.handleEvent = function(mapBrowserEvent) { - if (!this.condition_(mapBrowserEvent)) { - return true; - } - var add = this.addCondition_(mapBrowserEvent); - var remove = this.removeCondition_(mapBrowserEvent); - var toggle = this.toggleCondition_(mapBrowserEvent); - var set = !add && !remove && !toggle; - var map = mapBrowserEvent.map; - var features = this.featureOverlay_.getFeatures(); - if (set) { - // Replace the currently selected feature(s) with the feature at the pixel, - // or clear the selected feature(s) if there is no feature at the pixel. - /** @type {ol.Feature|undefined} */ - var feature = map.forEachFeatureAtPixel(mapBrowserEvent.pixel, - /** - * @param {ol.Feature} feature Feature. - * @param {ol.layer.Layer} layer Layer. - */ - function(feature, layer) { - return feature; - }, undefined, this.layerFilter_); - if (goog.isDef(feature) && - features.getLength() == 1 && - features.item(0) == feature) { - // No change - } else { - if (features.getLength() !== 0) { - features.clear(); - } - if (goog.isDef(feature)) { - features.push(feature); - } - } - } else { - // Modify the currently selected feature(s). - var /** @type {Array.<ol.Feature>} */ deselected = []; - var /** @type {Array.<ol.Feature>} */ selected = []; - map.forEachFeatureAtPixel(mapBrowserEvent.pixel, - /** - * @param {ol.Feature} feature Feature. - * @param {ol.layer.Layer} layer Layer. - */ - function(feature, layer) { - var index = goog.array.indexOf(features.getArray(), feature); - if (index == -1) { - if (add || toggle) { - selected.push(feature); - } - } else { - if (remove || toggle) { - deselected.push(feature); - } - } - }, undefined, this.layerFilter_); - var i; - for (i = deselected.length - 1; i >= 0; --i) { - features.remove(deselected[i]); - } - features.extend(selected); - } - return ol.events.condition.mouseMove(mapBrowserEvent); -}; /** - * Remove the interaction from its current map, if any, and attach it to a new - * map, if any. Pass `null` to just remove the interaction from the current map. - * @param {ol.Map} map Map. - * @api stable + * A wrapper for the progress events emitted by the File APIs. + * + * @param {!ProgressEvent} event The underlying event object. + * @param {!Object} target The file access object emitting the event. + * @extends {goog.events.Event} + * @constructor + * @final */ -ol.interaction.Select.prototype.setMap = function(map) { - var currentMap = this.getMap(); - var selectedFeatures = this.featureOverlay_.getFeatures(); - if (!goog.isNull(currentMap)) { - selectedFeatures.forEach(currentMap.unskipFeature, currentMap); - } - goog.base(this, 'setMap', map); - this.featureOverlay_.setMap(map); - if (!goog.isNull(map)) { - selectedFeatures.forEach(map.skipFeature, map); - } +goog.fs.ProgressEvent = function(event, target) { + goog.fs.ProgressEvent.base(this, 'constructor', event.type, target); + + /** + * The underlying event object. + * @type {!ProgressEvent} + * @private + */ + this.event_ = event; }; +goog.inherits(goog.fs.ProgressEvent, goog.events.Event); /** - * @return {ol.style.StyleFunction} Styles. + * @return {boolean} Whether or not the total size of the of the file being + * saved is known. */ -ol.interaction.Select.getDefaultStyleFunction = function() { - var styles = ol.style.createDefaultEditingStyles(); - goog.array.extend(styles[ol.geom.GeometryType.POLYGON], - styles[ol.geom.GeometryType.LINE_STRING]); - goog.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION], - styles[ol.geom.GeometryType.LINE_STRING]); - - return function(feature, resolution) { - return styles[feature.getGeometry().getType()]; - }; +goog.fs.ProgressEvent.prototype.isLengthComputable = function() { + return this.event_.lengthComputable; }; /** - * @param {ol.CollectionEvent} evt Event. - * @private + * @return {number} The number of bytes saved so far. */ -ol.interaction.Select.prototype.addFeature_ = function(evt) { - var feature = evt.element; - var map = this.getMap(); - goog.asserts.assertInstanceof(feature, ol.Feature); - if (!goog.isNull(map)) { - map.skipFeature(feature); - } +goog.fs.ProgressEvent.prototype.getLoaded = function() { + return this.event_.loaded; }; /** - * @param {ol.CollectionEvent} evt Event. - * @private + * @return {number} The total number of bytes in the file being saved. */ -ol.interaction.Select.prototype.removeFeature_ = function(evt) { - var feature = evt.element; - var map = this.getMap(); - goog.asserts.assertInstanceof(feature, ol.Feature); - if (!goog.isNull(map)) { - map.unskipFeature(feature); - } +goog.fs.ProgressEvent.prototype.getTotal = function() { + return this.event_.total; }; -goog.provide('ol.layer.Heatmap'); - -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.math'); -goog.require('ol.Object'); -goog.require('ol.dom'); -goog.require('ol.layer.Vector'); -goog.require('ol.render.EventType'); -goog.require('ol.style.Icon'); -goog.require('ol.style.Style'); - +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * @enum {string} + * @fileoverview A wrapper for the HTML5 FileReader object. + * */ -ol.layer.HeatmapLayerProperty = { - GRADIENT: 'gradient' -}; + +goog.provide('goog.fs.FileReader'); +goog.provide('goog.fs.FileReader.EventType'); +goog.provide('goog.fs.FileReader.ReadyState'); + +goog.require('goog.async.Deferred'); +goog.require('goog.events.EventTarget'); +goog.require('goog.fs.Error'); +goog.require('goog.fs.ProgressEvent'); /** - * @classdesc - * Layer for rendering vector data as a heatmap. - * Note that any property set in the options is set as a {@link ol.Object} - * property on the layer object; for example, setting `title: 'My Title'` in the - * options means that `title` is observable, and has get/set accessors. + * An object for monitoring the reading of files. This emits ProgressEvents of + * the types listed in {@link goog.fs.FileReader.EventType}. * * @constructor - * @extends {ol.layer.Vector} - * @fires ol.render.Event - * @param {olx.layer.HeatmapOptions=} opt_options Options. - * @api + * @extends {goog.events.EventTarget} + * @final */ -ol.layer.Heatmap = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, /** @type {olx.layer.VectorOptions} */ (options)); +goog.fs.FileReader = function() { + goog.fs.FileReader.base(this, 'constructor'); /** + * The underlying FileReader object. + * + * @type {!FileReader} * @private - * @type {Uint8ClampedArray} */ - this.gradient_ = null; - - goog.events.listen(this, - ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.GRADIENT), - this.handleGradientChanged_, false, this); + this.reader_ = new FileReader(); - this.setGradient(goog.isDef(options.gradient) ? - options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT); + this.reader_.onloadstart = goog.bind(this.dispatchProgressEvent_, this); + this.reader_.onprogress = goog.bind(this.dispatchProgressEvent_, this); + this.reader_.onload = goog.bind(this.dispatchProgressEvent_, this); + this.reader_.onabort = goog.bind(this.dispatchProgressEvent_, this); + this.reader_.onerror = goog.bind(this.dispatchProgressEvent_, this); + this.reader_.onloadend = goog.bind(this.dispatchProgressEvent_, this); +}; +goog.inherits(goog.fs.FileReader, goog.events.EventTarget); - var circle = ol.layer.Heatmap.createCircle_( - goog.isDef(options.radius) ? options.radius : 8, - goog.isDef(options.blur) ? options.blur : 15, - goog.isDef(options.shadow) ? options.shadow : 250); +/** + * Possible states for a FileReader. + * + * @enum {number} + */ +goog.fs.FileReader.ReadyState = { /** - * @type {Array.<Array.<ol.style.Style>>} + * The object has been constructed, but there is no pending read. */ - var styleCache = new Array(256); - - var weight = goog.isDef(options.weight) ? options.weight : 'weight'; - var weightFunction; - if (goog.isString(weight)) { - weightFunction = function(feature) { - return feature.get(weight); - }; - } else { - weightFunction = weight; - } - goog.asserts.assert(goog.isFunction(weightFunction)); - - this.setStyle(function(feature, resolution) { - var weight = weightFunction(feature); - var opacity = goog.isDef(weight) ? goog.math.clamp(weight, 0, 1) : 1; - // cast to 8 bits - var index = (255 * opacity) | 0; - var style = styleCache[index]; - if (!goog.isDef(style)) { - style = [ - new ol.style.Style({ - image: new ol.style.Icon({ - opacity: opacity, - src: circle - }) - }) - ]; - styleCache[index] = style; - } - return style; - }); - - // For performance reasons, don't sort the features before rendering. - // The render order is not relevant for a heatmap representation. - this.setRenderOrder(null); - - goog.events.listen(this, ol.render.EventType.RENDER, - this.handleRender_, false, this); - + INIT: 0, + /** + * Data is being read. + */ + LOADING: 1, + /** + * The data has been read from the file, the read was aborted, or an error + * occurred. + */ + DONE: 2 }; -goog.inherits(ol.layer.Heatmap, ol.layer.Vector); /** - * @const - * @type {Array.<string>} + * Events emitted by a FileReader. + * + * @enum {string} */ -ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00']; +goog.fs.FileReader.EventType = { + /** + * Emitted when the reading begins. readyState will be LOADING. + */ + LOAD_START: 'loadstart', + /** + * Emitted when progress has been made in reading the file. readyState will be + * LOADING. + */ + PROGRESS: 'progress', + /** + * Emitted when the data has been successfully read. readyState will be + * LOADING. + */ + LOAD: 'load', + /** + * Emitted when the reading has been aborted. readyState will be LOADING. + */ + ABORT: 'abort', + /** + * Emitted when an error is encountered or the reading has been aborted. + * readyState will be LOADING. + */ + ERROR: 'error', + /** + * Emitted when the reading is finished, whether successfully or not. + * readyState will be DONE. + */ + LOAD_END: 'loadend' +}; /** - * @param {Array.<string>} colors - * @return {Uint8ClampedArray} - * @private + * Abort the reading of the file. */ -ol.layer.Heatmap.createGradient_ = function(colors) { - var width = 1; - var height = 256; - var context = ol.dom.createCanvasContext2D(width, height); - - var gradient = context.createLinearGradient(0, 0, width, height); - var step = 1 / (colors.length - 1); - for (var i = 0, ii = colors.length; i < ii; ++i) { - gradient.addColorStop(i * step, colors[i]); +goog.fs.FileReader.prototype.abort = function() { + try { + this.reader_.abort(); + } catch (e) { + throw new goog.fs.Error(e, 'aborting read'); } - - context.fillStyle = gradient; - context.fillRect(0, 0, width, height); - - return context.getImageData(0, 0, width, height).data; }; /** - * @param {number} radius Radius size in pixel. - * @param {number} blur Blur size in pixel. - * @param {number} shadow Shadow offset size in pixel. - * @return {string} - * @private + * @return {goog.fs.FileReader.ReadyState} The current state of the FileReader. */ -ol.layer.Heatmap.createCircle_ = function(radius, blur, shadow) { - var halfSize = radius + blur + 1; - var size = 2 * halfSize; - var context = ol.dom.createCanvasContext2D(size, size); - context.shadowOffsetX = context.shadowOffsetY = shadow; - context.shadowBlur = blur; - context.shadowColor = '#000'; - context.beginPath(); - var center = halfSize - shadow; - context.arc(center, center, radius, 0, Math.PI * 2, true); - context.fill(); - return context.canvas.toDataURL(); +goog.fs.FileReader.prototype.getReadyState = function() { + return /** @type {goog.fs.FileReader.ReadyState} */ (this.reader_.readyState); }; /** - * @return {Array.<string>} Colors. - * @api - * @observable + * @return {*} The result of the file read. */ -ol.layer.Heatmap.prototype.getGradient = function() { - return /** @type {Array.<string>} */ ( - this.get(ol.layer.HeatmapLayerProperty.GRADIENT)); +goog.fs.FileReader.prototype.getResult = function() { + return this.reader_.result; }; -goog.exportProperty( - ol.layer.Heatmap.prototype, - 'getGradient', - ol.layer.Heatmap.prototype.getGradient); /** - * @private + * @return {goog.fs.Error} The error encountered while reading, if any. */ -ol.layer.Heatmap.prototype.handleGradientChanged_ = function() { - this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient()); +goog.fs.FileReader.prototype.getError = function() { + return this.reader_.error && + new goog.fs.Error(this.reader_.error, 'reading file'); }; /** - * @param {ol.render.Event} event Post compose event + * Wrap a progress event emitted by the underlying file reader and re-emit it. + * + * @param {!ProgressEvent} event The underlying event. * @private */ -ol.layer.Heatmap.prototype.handleRender_ = function(event) { - goog.asserts.assert(event.type == ol.render.EventType.RENDER); - var context = event.context; - var canvas = context.canvas; - var image = context.getImageData(0, 0, canvas.width, canvas.height); - var view8 = image.data; - var i, ii, alpha, offset; - for (i = 0, ii = view8.length; i < ii; i += 4) { - alpha = view8[i + 3] * 4; - if (alpha) { - view8[i] = this.gradient_[alpha]; - view8[i + 1] = this.gradient_[alpha + 1]; - view8[i + 2] = this.gradient_[alpha + 2]; - } - } - context.putImageData(image, 0, 0); +goog.fs.FileReader.prototype.dispatchProgressEvent_ = function(event) { + this.dispatchEvent(new goog.fs.ProgressEvent(event, this)); }; -/** - * @param {Array.<string>} colors Gradient. - * @api - * @observable - */ -ol.layer.Heatmap.prototype.setGradient = function(colors) { - this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors); +/** @override */ +goog.fs.FileReader.prototype.disposeInternal = function() { + goog.fs.FileReader.base(this, 'disposeInternal'); + delete this.reader_; }; -goog.exportProperty( - ol.layer.Heatmap.prototype, - 'setGradient', - ol.layer.Heatmap.prototype.setGradient); - -goog.provide('ol.loadingstrategy'); - -goog.require('ol.TileCoord'); /** - * Strategy function for loading all features with a single request. - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @return {Array.<ol.Extent>} Extents. - * @api + * Starts reading a blob as a binary string. + * @param {!Blob} blob The blob to read. */ -ol.loadingstrategy.all = function(extent, resolution) { - return [[-Infinity, -Infinity, Infinity, Infinity]]; +goog.fs.FileReader.prototype.readAsBinaryString = function(blob) { + this.reader_.readAsBinaryString(blob); }; /** - * Strategy function for loading features based on the view's extent and - * resolution. - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @return {Array.<ol.Extent>} Extents. - * @api + * Reads a blob as a binary string. + * @param {!Blob} blob The blob to read. + * @return {!goog.async.Deferred} The deferred Blob contents as a binary string. + * If an error occurs, the errback is called with a {@link goog.fs.Error}. */ -ol.loadingstrategy.bbox = function(extent, resolution) { - return [extent]; +goog.fs.FileReader.readAsBinaryString = function(blob) { + var reader = new goog.fs.FileReader(); + var d = goog.fs.FileReader.createDeferred_(reader); + reader.readAsBinaryString(blob); + return d; }; /** - * Creates a strategy function for loading features based on a tile grid. - * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. - * @return {function(ol.Extent, number): Array.<ol.Extent>} Loading strategy. - * @api + * Starts reading a blob as an array buffer. + * @param {!Blob} blob The blob to read. */ -ol.loadingstrategy.createTile = function(tileGrid) { - return ( - /** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @return {Array.<ol.Extent>} Extents. - */ - function(extent, resolution) { - var z = tileGrid.getZForResolution(resolution); - var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); - /** @type {Array.<ol.Extent>} */ - var extents = []; - /** @type {ol.TileCoord} */ - var tileCoord = [z, 0, 0]; - for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX; - ++tileCoord[1]) { - for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY; - ++tileCoord[2]) { - extents.push(tileGrid.getTileCoordExtent(tileCoord)); - } - } - return extents; - }); +goog.fs.FileReader.prototype.readAsArrayBuffer = function(blob) { + this.reader_.readAsArrayBuffer(blob); }; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A utility to load JavaScript files via DOM script tags. - * Refactored from goog.net.Jsonp. Works cross-domain. - * + * Reads a blob as an array buffer. + * @param {!Blob} blob The blob to read. + * @return {!goog.async.Deferred} The deferred Blob contents as an array buffer. + * If an error occurs, the errback is called with a {@link goog.fs.Error}. */ +goog.fs.FileReader.readAsArrayBuffer = function(blob) { + var reader = new goog.fs.FileReader(); + var d = goog.fs.FileReader.createDeferred_(reader); + reader.readAsArrayBuffer(blob); + return d; +}; -goog.provide('goog.net.jsloader'); -goog.provide('goog.net.jsloader.Error'); -goog.provide('goog.net.jsloader.ErrorCode'); -goog.provide('goog.net.jsloader.Options'); -goog.require('goog.array'); -goog.require('goog.async.Deferred'); -goog.require('goog.debug.Error'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); +/** + * Starts reading a blob as text. + * @param {!Blob} blob The blob to read. + * @param {string=} opt_encoding The name of the encoding to use. + */ +goog.fs.FileReader.prototype.readAsText = function(blob, opt_encoding) { + this.reader_.readAsText(blob, opt_encoding); +}; /** - * The name of the property of goog.global under which the JavaScript - * verification object is stored by the loaded script. - * @type {string} - * @private + * Reads a blob as text. + * @param {!Blob} blob The blob to read. + * @param {string=} opt_encoding The name of the encoding to use. + * @return {!goog.async.Deferred} The deferred Blob contents as text. + * If an error occurs, the errback is called with a {@link goog.fs.Error}. */ -goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification'; +goog.fs.FileReader.readAsText = function(blob, opt_encoding) { + var reader = new goog.fs.FileReader(); + var d = goog.fs.FileReader.createDeferred_(reader); + reader.readAsText(blob, opt_encoding); + return d; +}; /** - * The default length of time, in milliseconds, we are prepared to wait for a - * load request to complete. - * @type {number} + * Starts reading a blob as a data URL. + * @param {!Blob} blob The blob to read. */ -goog.net.jsloader.DEFAULT_TIMEOUT = 5000; +goog.fs.FileReader.prototype.readAsDataUrl = function(blob) { + this.reader_.readAsDataURL(blob); +}; /** - * Optional parameters for goog.net.jsloader.send. - * timeout: The length of time, in milliseconds, we are prepared to wait - * for a load request to complete. Default it 5 seconds. - * document: The HTML document under which to load the JavaScript. Default is - * the current document. - * cleanupWhenDone: If true clean up the script tag after script completes to - * load. This is important if you just want to read data from the JavaScript - * and then throw it away. Default is false. - * - * @typedef {{ - * timeout: (number|undefined), - * document: (HTMLDocument|undefined), - * cleanupWhenDone: (boolean|undefined) - * }} + * Reads a blob as a data URL. + * @param {!Blob} blob The blob to read. + * @return {!goog.async.Deferred} The deferred Blob contents as a data URL. + * If an error occurs, the errback is called with a {@link goog.fs.Error}. */ -goog.net.jsloader.Options; +goog.fs.FileReader.readAsDataUrl = function(blob) { + var reader = new goog.fs.FileReader(); + var d = goog.fs.FileReader.createDeferred_(reader); + reader.readAsDataUrl(blob); + return d; +}; /** - * Scripts (URIs) waiting to be loaded. - * @type {Array<string>} + * Creates a new deferred object for the results of a read method. + * @param {goog.fs.FileReader} reader The reader to create a deferred for. + * @return {!goog.async.Deferred} The deferred results. * @private */ -goog.net.jsloader.scriptsToLoad_ = []; +goog.fs.FileReader.createDeferred_ = function(reader) { + var deferred = new goog.async.Deferred(); + reader.listen(goog.fs.FileReader.EventType.LOAD_END, + goog.partial(function(d, r, e) { + var result = r.getResult(); + var error = r.getError(); + if (result != null && !error) { + d.callback(result); + } else { + d.errback(error); + } + r.dispose(); + }, deferred, reader)); + return deferred; +}; +// FIXME should handle all geo-referenced data, not just vector data -/** - * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing - * the order of script loads. - * - * Because we have to load the scripts in serial (load script 1, exec script 1, - * load script 2, exec script 2, and so on), this will be slower than doing - * the network fetches in parallel. - * - * If you need to load a large number of scripts but dependency order doesn't - * matter, you should just call goog.net.jsloader.load N times. - * - * If you need to load a large number of scripts on the same domain, - * you may want to use goog.module.ModuleLoader. - * - * @param {Array<string>} uris The URIs to load. - * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See - * goog.net.jsloader.options documentation for details. - */ -goog.net.jsloader.loadMany = function(uris, opt_options) { - // Loading the scripts in serial introduces asynchronosity into the flow. - // Therefore, there are race conditions where client A can kick off the load - // sequence for client B, even though client A's scripts haven't all been - // loaded yet. - // - // To work around this issue, all module loads share a queue. - if (!uris.length) { - return; - } +goog.provide('ol.interaction.DragAndDrop'); +goog.provide('ol.interaction.DragAndDropEvent'); - var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length; - goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris); - if (isAnotherModuleLoading) { - // jsloader is still loading some other scripts. - // In order to prevent the race condition noted above, we just add - // these URIs to the end of the scripts' queue and return. - return; - } +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.FileDropHandler'); +goog.require('goog.events.FileDropHandler.EventType'); +goog.require('goog.fs.FileReader'); +goog.require('goog.functions'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.proj'); - uris = goog.net.jsloader.scriptsToLoad_; - var popAndLoadNextScript = function() { - var uri = uris.shift(); - var deferred = goog.net.jsloader.load(uri, opt_options); - if (uris.length) { - deferred.addBoth(popAndLoadNextScript); - } - }; - popAndLoadNextScript(); -}; /** - * Loads and evaluates a JavaScript file. - * When the script loads, a user callback is called. - * It is the client's responsibility to verify that the script ran successfully. + * @classdesc + * Handles input of vector data by drag and drop. * - * @param {string} uri The URI of the JavaScript. - * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See - * goog.net.jsloader.Options documentation for details. - * @return {!goog.async.Deferred} The deferred result, that may be used to add - * callbacks and/or cancel the transmission. - * The error callback will be called with a single goog.net.jsloader.Error - * parameter. + * @constructor + * @extends {ol.interaction.Interaction} + * @fires ol.interaction.DragAndDropEvent + * @param {olx.interaction.DragAndDropOptions=} opt_options Options. + * @api stable */ -goog.net.jsloader.load = function(uri, opt_options) { - var options = opt_options || {}; - var doc = options.document || document; +ol.interaction.DragAndDrop = function(opt_options) { - var script = goog.dom.createElement(goog.dom.TagName.SCRIPT); - var request = {script_: script, timeout_: undefined}; - var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request); + var options = goog.isDef(opt_options) ? opt_options : {}; - // Set a timeout. - var timeout = null; - var timeoutDuration = goog.isDefAndNotNull(options.timeout) ? - options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT; - if (timeoutDuration > 0) { - timeout = window.setTimeout(function() { - goog.net.jsloader.cleanup_(script, true); - deferred.errback(new goog.net.jsloader.Error( - goog.net.jsloader.ErrorCode.TIMEOUT, - 'Timeout reached for loading script ' + uri)); - }, timeoutDuration); - request.timeout_ = timeout; - } + goog.base(this, { + handleEvent: ol.interaction.DragAndDrop.handleEvent + }); - // Hang the user callback to be called when the script completes to load. - // NOTE(user): This callback will be called in IE even upon error. In any - // case it is the client's responsibility to verify that the script ran - // successfully. - script.onload = script.onreadystatechange = function() { - if (!script.readyState || script.readyState == 'loaded' || - script.readyState == 'complete') { - var removeScriptNode = options.cleanupWhenDone || false; - goog.net.jsloader.cleanup_(script, removeScriptNode, timeout); - deferred.callback(null); - } - }; + /** + * @private + * @type {Array.<function(new: ol.format.Feature)>} + */ + this.formatConstructors_ = goog.isDef(options.formatConstructors) ? + options.formatConstructors : []; - // Add an error callback. - // NOTE(user): Not supported in IE. - script.onerror = function() { - goog.net.jsloader.cleanup_(script, true, timeout); - deferred.errback(new goog.net.jsloader.Error( - goog.net.jsloader.ErrorCode.LOAD_ERROR, - 'Error while loading script ' + uri)); - }; + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = goog.isDef(options.projection) ? + ol.proj.get(options.projection) : null; - // Add the script element to the document. - goog.dom.setProperties(script, { - 'type': 'text/javascript', - 'charset': 'UTF-8', - // NOTE(user): Safari never loads the script if we don't set - // the src attribute before appending. - 'src': uri - }); - var scriptParent = goog.net.jsloader.getScriptParentElement_(doc); - scriptParent.appendChild(script); + /** + * @private + * @type {goog.events.FileDropHandler} + */ + this.fileDropHandler_ = null; + + /** + * @private + * @type {goog.events.Key|undefined} + */ + this.dropListenKey_ = undefined; - return deferred; }; +goog.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction); /** - * Loads a JavaScript file and verifies it was evaluated successfully, using a - * verification object. - * The verification object is set by the loaded JavaScript at the end of the - * script. - * We verify this object was set and return its value in the success callback. - * If the object is not defined we trigger an error callback. - * - * @param {string} uri The URI of the JavaScript. - * @param {string} verificationObjName The name of the verification object that - * the loaded script should set. - * @param {goog.net.jsloader.Options} options Optional parameters. See - * goog.net.jsloader.Options documentation for details. - * @return {!goog.async.Deferred} The deferred result, that may be used to add - * callbacks and/or cancel the transmission. - * The success callback will be called with a single parameter containing - * the value of the verification object. - * The error callback will be called with a single goog.net.jsloader.Error - * parameter. + * @inheritDoc */ -goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) { - // Define the global objects variable. - if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) { - goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {}; - } - var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]; - - // Verify that the expected object does not exist yet. - if (goog.isDef(verifyObjs[verificationObjName])) { - // TODO(user): Error or reset variable? - return goog.async.Deferred.fail(new goog.net.jsloader.Error( - goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS, - 'Verification object ' + verificationObjName + ' already defined.')); +ol.interaction.DragAndDrop.prototype.disposeInternal = function() { + if (goog.isDef(this.dropListenKey_)) { + goog.events.unlistenByKey(this.dropListenKey_); } - - // Send request to load the JavaScript. - var sendDeferred = goog.net.jsloader.load(uri, options); - - // Create a deferred object wrapping the send result. - var deferred = new goog.async.Deferred( - goog.bind(sendDeferred.cancel, sendDeferred)); - - // Call user back with object that was set by the script. - sendDeferred.addCallback(function() { - var result = verifyObjs[verificationObjName]; - if (goog.isDef(result)) { - deferred.callback(result); - delete verifyObjs[verificationObjName]; - } else { - // Error: script was not loaded properly. - deferred.errback(new goog.net.jsloader.Error( - goog.net.jsloader.ErrorCode.VERIFY_ERROR, - 'Script ' + uri + ' loaded, but verification object ' + - verificationObjName + ' was not defined.')); - } - }); - - // Pass error to new deferred object. - sendDeferred.addErrback(function(error) { - if (goog.isDef(verifyObjs[verificationObjName])) { - delete verifyObjs[verificationObjName]; - } - deferred.errback(error); - }); - - return deferred; + goog.base(this, 'disposeInternal'); }; /** - * Gets the DOM element under which we should add new script elements. - * How? Take the first head element, and if not found take doc.documentElement, - * which always exists. - * - * @param {!HTMLDocument} doc The relevant document. - * @return {!Element} The script parent element. + * @param {goog.events.BrowserEvent} event Event. * @private */ -goog.net.jsloader.getScriptParentElement_ = function(doc) { - var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD); - if (!headElements || goog.array.isEmpty(headElements)) { - return doc.documentElement; - } else { - return headElements[0]; +ol.interaction.DragAndDrop.prototype.handleDrop_ = function(event) { + var files = event.getBrowserEvent().dataTransfer.files; + var i, ii, file; + for (i = 0, ii = files.length; i < ii; ++i) { + file = files[i]; + // The empty string param is a workaround for + // https://code.google.com/p/closure-library/issues/detail?id=524 + var reader = goog.fs.FileReader.readAsText(file, ''); + reader.addCallback(goog.partial(this.handleResult_, file), this); } }; /** - * Cancels a given request. - * @this {{script_: Element, timeout_: number}} The request context. + * @param {File} file File. + * @param {string} result Result. * @private */ -goog.net.jsloader.cancel_ = function() { - var request = this; - if (request && request.script_) { - var scriptNode = request.script_; - if (scriptNode && scriptNode.tagName == 'SCRIPT') { - goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_); +ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, result) { + var map = this.getMap(); + goog.asserts.assert(!goog.isNull(map), 'map should not be null'); + var projection = this.projection_; + if (goog.isNull(projection)) { + var view = map.getView(); + goog.asserts.assert(!goog.isNull(view), 'view should not be null'); + projection = view.getProjection(); + goog.asserts.assert(goog.isDef(projection), + 'projection should be defined'); + } + var formatConstructors = this.formatConstructors_; + var features = []; + var i, ii; + for (i = 0, ii = formatConstructors.length; i < ii; ++i) { + var formatConstructor = formatConstructors[i]; + var format = new formatConstructor(); + var readFeatures = this.tryReadFeatures_(format, result); + if (!goog.isNull(readFeatures)) { + var featureProjection = format.readProjection(result); + var transform = ol.proj.getTransform(featureProjection, projection); + var j, jj; + for (j = 0, jj = readFeatures.length; j < jj; ++j) { + var feature = readFeatures[j]; + var geometry = feature.getGeometry(); + if (goog.isDefAndNotNull(geometry)) { + geometry.applyTransform(transform); + } + features.push(feature); + } } } + this.dispatchEvent( + new ol.interaction.DragAndDropEvent( + ol.interaction.DragAndDropEventType.ADD_FEATURES, this, file, + features, projection)); }; /** - * Removes the script node and the timeout. - * - * @param {Node} scriptNode The node to be cleaned up. - * @param {boolean} removeScriptNode If true completely remove the script node. - * @param {?number=} opt_timeout The timeout handler to cleanup. - * @private + * Handles the {@link ol.MapBrowserEvent map browser event} unconditionally and + * neither prevents the browser default nor stops event propagation. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.DragAndDrop} + * @api */ -goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode, - opt_timeout) { - if (goog.isDefAndNotNull(opt_timeout)) { - goog.global.clearTimeout(opt_timeout); +ol.interaction.DragAndDrop.handleEvent = goog.functions.TRUE; + + +/** + * @inheritDoc + */ +ol.interaction.DragAndDrop.prototype.setMap = function(map) { + if (goog.isDef(this.dropListenKey_)) { + goog.events.unlistenByKey(this.dropListenKey_); + this.dropListenKey_ = undefined; + } + if (!goog.isNull(this.fileDropHandler_)) { + goog.dispose(this.fileDropHandler_); + this.fileDropHandler_ = null; } + goog.asserts.assert(!goog.isDef(this.dropListenKey_), + 'this.dropListenKey_ should be undefined'); + goog.base(this, 'setMap', map); + if (!goog.isNull(map)) { + this.fileDropHandler_ = new goog.events.FileDropHandler(map.getViewport()); + this.dropListenKey_ = goog.events.listen( + this.fileDropHandler_, goog.events.FileDropHandler.EventType.DROP, + this.handleDrop_, false, this); + } +}; - scriptNode.onload = goog.nullFunction; - scriptNode.onerror = goog.nullFunction; - scriptNode.onreadystatechange = goog.nullFunction; - // Do this after a delay (removing the script node of a running script can - // confuse older IEs). - if (removeScriptNode) { - window.setTimeout(function() { - goog.dom.removeNode(scriptNode); - }, 0); +/** + * @param {ol.format.Feature} format Format. + * @param {string} text Text. + * @private + * @return {Array.<ol.Feature>} Features. + */ +ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text) { + try { + return format.readFeatures(text); + } catch (e) { + return null; } }; /** - * Possible error codes for jsloader. - * @enum {number} + * @enum {string} */ -goog.net.jsloader.ErrorCode = { - LOAD_ERROR: 0, - TIMEOUT: 1, - VERIFY_ERROR: 2, - VERIFY_OBJECT_ALREADY_EXISTS: 3 +ol.interaction.DragAndDropEventType = { + /** + * Triggered when features are added + * @event ol.interaction.DragAndDropEvent#addfeatures + * @api stable + */ + ADD_FEATURES: 'addfeatures' }; /** - * A jsloader error. + * @classdesc + * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances + * of this type. * - * @param {goog.net.jsloader.ErrorCode} code The error code. - * @param {string=} opt_message Additional message. * @constructor - * @extends {goog.debug.Error} - * @final + * @extends {goog.events.Event} + * @implements {oli.interaction.DragAndDropEvent} + * @param {ol.interaction.DragAndDropEventType} type Type. + * @param {Object} target Target. + * @param {File} file File. + * @param {Array.<ol.Feature>=} opt_features Features. + * @param {ol.proj.Projection=} opt_projection Projection. */ -goog.net.jsloader.Error = function(code, opt_message) { - var msg = 'Jsloader error (code #' + code + ')'; - if (opt_message) { - msg += ': ' + opt_message; - } - goog.net.jsloader.Error.base(this, 'constructor', msg); +ol.interaction.DragAndDropEvent = + function(type, target, file, opt_features, opt_projection) { + + goog.base(this, type, target); /** - * The code for this error. - * - * @type {goog.net.jsloader.ErrorCode} + * @type {Array.<ol.Feature>|undefined} + * @api stable */ - this.code = code; + this.features = opt_features; + + /** + * @type {File} + * @api stable + */ + this.file = file; + + /** + * @type {ol.proj.Projection|undefined} + * @api + */ + this.projection = opt_projection; + }; -goog.inherits(goog.net.jsloader.Error, goog.debug.Error); +goog.inherits(ol.interaction.DragAndDropEvent, goog.events.Event); -// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// Copyright 2007 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -103862,4076 +106029,4910 @@ goog.inherits(goog.net.jsloader.Error, goog.debug.Error); // See the License for the specific language governing permissions and // limitations under the License. -// The original file lives here: http://go/cross_domain_channel.js - /** - * @fileoverview Implements a cross-domain communication channel. A - * typical web page is prevented by browser security from sending - * request, such as a XMLHttpRequest, to other servers than the ones - * from which it came. The Jsonp class provides a workaround by - * using dynamically generated script tags. Typical usage:. - * - * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet')); - * var payload = { 'foo': 1, 'bar': true }; - * jsonp.send(payload, function(reply) { alert(reply) }); + * @fileoverview Defines a 2-element vector class that can be used for + * coordinate math, useful for animation systems and point manipulation. * - * This script works in all browsers that are currently supported by - * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+, - * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+. + * Vec2 objects inherit from goog.math.Coordinate and may be used wherever a + * Coordinate is required. Where appropriate, Vec2 functions accept both Vec2 + * and Coordinate objects as input. * + * @author brenneman@google.com (Shawn Brenneman) */ -goog.provide('goog.net.Jsonp'); - -goog.require('goog.Uri'); -goog.require('goog.net.jsloader'); - -// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING -// -// This class allows us (Google) to send data from non-Google and thus -// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return -// anything sensitive, such as session or cookie specific data. Return -// only data that you want parties external to Google to have. Also -// NEVER use this method to send data from web pages to untrusted -// servers, or redirects to unknown servers (www.google.com/cache, -// /q=xx&btnl, /url, www.googlepages.com, etc.) -// -// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +goog.provide('goog.math.Vec2'); + +goog.require('goog.math'); +goog.require('goog.math.Coordinate'); /** - * Creates a new cross domain channel that sends data to the specified - * host URL. By default, if no reply arrives within 5s, the channel - * assumes the call failed to complete successfully. - * - * @param {goog.Uri|string} uri The Uri of the server side code that receives - * data posted through this channel (e.g., - * "http://maps.google.com/maps/geo"). - * - * @param {string=} opt_callbackParamName The parameter name that is used to - * specify the callback. Defaults to "callback". + * Class for a two-dimensional vector object and assorted functions useful for + * manipulating points. * + * @param {number} x The x coordinate for the vector. + * @param {number} y The y coordinate for the vector. + * @struct * @constructor - * @final + * @extends {goog.math.Coordinate} */ -goog.net.Jsonp = function(uri, opt_callbackParamName) { - /** - * The uri_ object will be used to encode the payload that is sent to the - * server. - * @type {goog.Uri} - * @private - */ - this.uri_ = new goog.Uri(uri); - +goog.math.Vec2 = function(x, y) { /** - * This is the callback parameter name that is added to the uri. - * @type {string} - * @private + * X-value + * @type {number} */ - this.callbackParamName_ = opt_callbackParamName ? - opt_callbackParamName : 'callback'; + this.x = x; /** - * The length of time, in milliseconds, this channel is prepared - * to wait for for a request to complete. The default value is 5 seconds. + * Y-value * @type {number} - * @private */ - this.timeout_ = 5000; + this.y = y; }; +goog.inherits(goog.math.Vec2, goog.math.Coordinate); /** - * The name of the property of goog.global under which the callback is - * stored. + * @return {!goog.math.Vec2} A random unit-length vector. */ -goog.net.Jsonp.CALLBACKS = '_callbacks_'; +goog.math.Vec2.randomUnit = function() { + var angle = Math.random() * Math.PI * 2; + return new goog.math.Vec2(Math.cos(angle), Math.sin(angle)); +}; /** - * Used to generate unique callback IDs. The counter must be global because - * all channels share a common callback object. - * @private + * @return {!goog.math.Vec2} A random vector inside the unit-disc. */ -goog.net.Jsonp.scriptCounter_ = 0; +goog.math.Vec2.random = function() { + var mag = Math.sqrt(Math.random()); + var angle = Math.random() * Math.PI * 2; + + return new goog.math.Vec2(Math.cos(angle) * mag, Math.sin(angle) * mag); +}; /** - * Sets the length of time, in milliseconds, this channel is prepared - * to wait for for a request to complete. If the call is not competed - * within the set time span, it is assumed to have failed. To wait - * indefinitely for a request to complete set the timout to a negative - * number. - * - * @param {number} timeout The length of time before calls are - * interrupted. + * Returns a new Vec2 object from a given coordinate. + * @param {!goog.math.Coordinate} a The coordinate. + * @return {!goog.math.Vec2} A new vector object. */ -goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) { - this.timeout_ = timeout; +goog.math.Vec2.fromCoordinate = function(a) { + return new goog.math.Vec2(a.x, a.y); }; /** - * Returns the current timeout value, in milliseconds. - * - * @return {number} The timeout value. + * @return {!goog.math.Vec2} A new vector with the same coordinates as this one. + * @override */ -goog.net.Jsonp.prototype.getRequestTimeout = function() { - return this.timeout_; +goog.math.Vec2.prototype.clone = function() { + return new goog.math.Vec2(this.x, this.y); }; /** - * Sends the given payload to the URL specified at the construction - * time. The reply is delivered to the given replyCallback. If the - * errorCallback is specified and the reply does not arrive within the - * timeout period set on this channel, the errorCallback is invoked - * with the original payload. - * - * If no reply callback is specified, then the response is expected to - * consist of calls to globally registered functions. No &callback= - * URL parameter will be sent in the request, and the script element - * will be cleaned up after the timeout. - * - * @param {Object=} opt_payload Name-value pairs. If given, these will be - * added as parameters to the supplied URI as GET parameters to the - * given server URI. - * - * @param {Function=} opt_replyCallback A function expecting one - * argument, called when the reply arrives, with the response data. - * - * @param {Function=} opt_errorCallback A function expecting one - * argument, called on timeout, with the payload (if given), otherwise - * null. - * - * @param {string=} opt_callbackParamValue Value to be used as the - * parameter value for the callback parameter (callbackParamName). - * To be used when the value needs to be fixed by the client for a - * particular request, to make use of the cached responses for the request. - * NOTE: If multiple requests are made with the same - * opt_callbackParamValue, only the last call will work whenever the - * response comes back. - * - * @return {!Object} A request descriptor that may be used to cancel this - * transmission, or null, if the message may not be cancelled. + * Returns the magnitude of the vector measured from the origin. + * @return {number} The length of the vector. */ -goog.net.Jsonp.prototype.send = function(opt_payload, - opt_replyCallback, - opt_errorCallback, - opt_callbackParamValue) { +goog.math.Vec2.prototype.magnitude = function() { + return Math.sqrt(this.x * this.x + this.y * this.y); +}; - var payload = opt_payload || null; - var id = opt_callbackParamValue || - '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) + - goog.now().toString(36); +/** + * Returns the squared magnitude of the vector measured from the origin. + * NOTE(brenneman): Leaving out the square root is not a significant + * optimization in JavaScript. + * @return {number} The length of the vector, squared. + */ +goog.math.Vec2.prototype.squaredMagnitude = function() { + return this.x * this.x + this.y * this.y; +}; - if (!goog.global[goog.net.Jsonp.CALLBACKS]) { - goog.global[goog.net.Jsonp.CALLBACKS] = {}; - } - // Create a new Uri object onto which this payload will be added - var uri = this.uri_.clone(); - if (payload) { - goog.net.Jsonp.addPayloadToUri_(payload, uri); - } +/** + * @return {!goog.math.Vec2} This coordinate after scaling. + * @override + */ +goog.math.Vec2.prototype.scale = + /** @type {function(number, number=):!goog.math.Vec2} */ + (goog.math.Coordinate.prototype.scale); - if (opt_replyCallback) { - var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback); - goog.global[goog.net.Jsonp.CALLBACKS][id] = reply; - uri.setParameterValues(this.callbackParamName_, - goog.net.Jsonp.CALLBACKS + '.' + id); - } +/** + * Reverses the sign of the vector. Equivalent to scaling the vector by -1. + * @return {!goog.math.Vec2} The inverted vector. + */ +goog.math.Vec2.prototype.invert = function() { + this.x = -this.x; + this.y = -this.y; + return this; +}; - var deferred = goog.net.jsloader.load(uri.toString(), - {timeout: this.timeout_, cleanupWhenDone: true}); - var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback); - deferred.addErrback(error); - return {id_: id, deferred_: deferred}; +/** + * Normalizes the current vector to have a magnitude of 1. + * @return {!goog.math.Vec2} The normalized vector. + */ +goog.math.Vec2.prototype.normalize = function() { + return this.scale(1 / this.magnitude()); }; /** - * Cancels a given request. The request must be exactly the object returned by - * the send method. - * - * @param {Object} request The request object returned by the send method. + * Adds another vector to this vector in-place. + * @param {!goog.math.Coordinate} b The vector to add. + * @return {!goog.math.Vec2} This vector with {@code b} added. */ -goog.net.Jsonp.prototype.cancel = function(request) { - if (request) { - if (request.deferred_) { - request.deferred_.cancel(); - } - if (request.id_) { - goog.net.Jsonp.cleanup_(request.id_, false); - } - } +goog.math.Vec2.prototype.add = function(b) { + this.x += b.x; + this.y += b.y; + return this; }; /** - * Creates a timeout callback that calls the given timeoutCallback with the - * original payload. - * - * @param {string} id The id of the script node. - * @param {Object} payload The payload that was sent to the server. - * @param {Function=} opt_errorCallback The function called on timeout. - * @return {!Function} A zero argument function that handles callback duties. - * @private + * Subtracts another vector from this vector in-place. + * @param {!goog.math.Coordinate} b The vector to subtract. + * @return {!goog.math.Vec2} This vector with {@code b} subtracted. */ -goog.net.Jsonp.newErrorHandler_ = function(id, - payload, - opt_errorCallback) { - /** - * When we call across domains with a request, this function is the - * timeout handler. Once it's done executing the user-specified - * error-handler, it removes the script node and original function. - */ - return function() { - goog.net.Jsonp.cleanup_(id, false); - if (opt_errorCallback) { - opt_errorCallback(payload); - } - }; +goog.math.Vec2.prototype.subtract = function(b) { + this.x -= b.x; + this.y -= b.y; + return this; }; /** - * Creates a reply callback that calls the given replyCallback with data - * returned by the server. - * - * @param {string} id The id of the script node. - * @param {Function} replyCallback The function called on reply. - * @return {!Function} A reply callback function. - * @private + * Rotates this vector in-place by a given angle, specified in radians. + * @param {number} angle The angle, in radians. + * @return {!goog.math.Vec2} This vector rotated {@code angle} radians. */ -goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) { - /** - * This function is the handler for the all-is-well response. It - * clears the error timeout handler, calls the user's handler, then - * removes the script node and itself. - * - * @param {...Object} var_args The response data sent from the server. - */ - var handler = function(var_args) { - goog.net.Jsonp.cleanup_(id, true); - replyCallback.apply(undefined, arguments); - }; - return handler; +goog.math.Vec2.prototype.rotate = function(angle) { + var cos = Math.cos(angle); + var sin = Math.sin(angle); + var newX = this.x * cos - this.y * sin; + var newY = this.y * cos + this.x * sin; + this.x = newX; + this.y = newY; + return this; }; /** - * Removes the script node and reply handler with the given id. - * - * @param {string} id The id of the script node to be removed. - * @param {boolean} deleteReplyHandler If true, delete the reply handler - * instead of setting it to nullFunction (if we know the callback could - * never be called again). - * @private + * Rotates a vector by a given angle, specified in radians, relative to a given + * axis rotation point. The returned vector is a newly created instance - no + * in-place changes are done. + * @param {!goog.math.Vec2} v A vector. + * @param {!goog.math.Vec2} axisPoint The rotation axis point. + * @param {number} angle The angle, in radians. + * @return {!goog.math.Vec2} The rotated vector in a newly created instance. */ -goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) { - if (goog.global[goog.net.Jsonp.CALLBACKS][id]) { - if (deleteReplyHandler) { - delete goog.global[goog.net.Jsonp.CALLBACKS][id]; - } else { - // Removing the script tag doesn't necessarily prevent the script - // from firing, so we make the callback a noop. - goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction; - } - } +goog.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) { + var res = v.clone(); + return res.subtract(axisPoint).rotate(angle).add(axisPoint); }; /** - * Returns URL encoded payload. The payload should be a map of name-value - * pairs, in the form {"foo": 1, "bar": true, ...}. If the map is empty, - * the URI will be unchanged. - * - * <p>The method uses hasOwnProperty() to assure the properties are on the - * object, not on its prototype. - * - * @param {!Object} payload A map of value name pairs to be encoded. - * A value may be specified as an array, in which case a query parameter - * will be created for each value, e.g.: - * {"foo": [1,2]} will encode to "foo=1&foo=2". - * - * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs - * will be encoded. - * - * @return {!goog.Uri} A reference to the Uri sent as a parameter. - * @private + * Compares this vector with another for equality. + * @param {!goog.math.Vec2} b The other vector. + * @return {boolean} Whether this vector has the same x and y as the given + * vector. */ -goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) { - for (var name in payload) { - // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that - // case, we iterate over all properties as a very lame workaround. - if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) { - uri.setParameterValues(name, payload[name]); - } - } - return uri; +goog.math.Vec2.prototype.equals = function(b) { + return this == b || !!b && this.x == b.x && this.y == b.y; }; -// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING -// -// This class allows us (Google) to send data from non-Google and thus -// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return -// anything sensitive, such as session or cookie specific data. Return -// only data that you want parties external to Google to have. Also -// NEVER use this method to send data from web pages to untrusted -// servers, or redirects to unknown servers (www.google.com/cache, -// /q=xx&btnl, /url, www.googlepages.com, etc.) -// -// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +/** + * Returns the distance between two vectors. + * @param {!goog.math.Coordinate} a The first vector. + * @param {!goog.math.Coordinate} b The second vector. + * @return {number} The distance. + */ +goog.math.Vec2.distance = goog.math.Coordinate.distance; -goog.provide('ol.TileUrlFunction'); -goog.provide('ol.TileUrlFunctionType'); -goog.require('goog.array'); -goog.require('goog.math'); -goog.require('ol.TileCoord'); -goog.require('ol.tilecoord'); +/** + * Returns the squared distance between two vectors. + * @param {!goog.math.Coordinate} a The first vector. + * @param {!goog.math.Coordinate} b The second vector. + * @return {number} The squared distance. + */ +goog.math.Vec2.squaredDistance = goog.math.Coordinate.squaredDistance; /** - * A function that takes an {@link ol.TileCoord} for the tile coordinate, - * a `{number}` representing the pixel ratio and an {@link ol.proj.Projection} - * for the projection as arguments and returns a `{string}` or - * undefined representing the tile URL. - * - * @typedef {function(ol.TileCoord, number, - * ol.proj.Projection): (string|undefined)} - * @api + * Compares vectors for equality. + * @param {!goog.math.Coordinate} a The first vector. + * @param {!goog.math.Coordinate} b The second vector. + * @return {boolean} Whether the vectors have the same x and y coordinates. */ -ol.TileUrlFunctionType; +goog.math.Vec2.equals = goog.math.Coordinate.equals; /** - * @typedef {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=): - * ol.TileCoord} + * Returns the sum of two vectors as a new Vec2. + * @param {!goog.math.Coordinate} a The first vector. + * @param {!goog.math.Coordinate} b The second vector. + * @return {!goog.math.Vec2} The sum vector. */ -ol.TileCoordTransformType; +goog.math.Vec2.sum = function(a, b) { + return new goog.math.Vec2(a.x + b.x, a.y + b.y); +}; /** - * @param {string} template Template. - * @return {ol.TileUrlFunctionType} Tile URL function. + * Returns the difference between two vectors as a new Vec2. + * @param {!goog.math.Coordinate} a The first vector. + * @param {!goog.math.Coordinate} b The second vector. + * @return {!goog.math.Vec2} The difference vector. */ -ol.TileUrlFunction.createFromTemplate = function(template) { - var zRegEx = /\{z\}/g; - var xRegEx = /\{x\}/g; - var yRegEx = /\{y\}/g; - var dashYRegEx = /\{-y\}/g; - return ( - /** - * @param {ol.TileCoord} tileCoord Tile Coordinate. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} Tile URL. - */ - function(tileCoord, pixelRatio, projection) { - if (goog.isNull(tileCoord)) { - return undefined; - } else { - return template.replace(zRegEx, tileCoord[0].toString()) - .replace(xRegEx, tileCoord[1].toString()) - .replace(yRegEx, tileCoord[2].toString()) - .replace(dashYRegEx, function() { - var y = (1 << tileCoord[0]) - tileCoord[2] - 1; - return y.toString(); - }); - } - }); +goog.math.Vec2.difference = function(a, b) { + return new goog.math.Vec2(a.x - b.x, a.y - b.y); +}; + + +/** + * Returns the dot-product of two vectors. + * @param {!goog.math.Coordinate} a The first vector. + * @param {!goog.math.Coordinate} b The second vector. + * @return {number} The dot-product of the two vectors. + */ +goog.math.Vec2.dot = function(a, b) { + return a.x * b.x + a.y * b.y; +}; + + +/** + * Returns a new Vec2 that is the linear interpolant between vectors a and b at + * scale-value x. + * @param {!goog.math.Coordinate} a Vector a. + * @param {!goog.math.Coordinate} b Vector b. + * @param {number} x The proportion between a and b. + * @return {!goog.math.Vec2} The interpolated vector. + */ +goog.math.Vec2.lerp = function(a, b, x) { + return new goog.math.Vec2(goog.math.lerp(a.x, b.x, x), + goog.math.lerp(a.y, b.y, x)); +}; + +goog.provide('ol.interaction.DragRotateAndZoom'); + +goog.require('goog.math.Vec2'); +goog.require('ol.ViewHint'); +goog.require('ol.events.ConditionType'); +goog.require('ol.events.condition'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.interaction.Pointer'); + + + +/** + * @classdesc + * Allows the user to zoom and rotate the map by clicking and dragging + * on the map. By default, this interaction is limited to when the shift + * key is held down. + * + * This interaction is only supported for mouse devices. + * + * And this interaction is not included in the default interactions. + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options. + * @api stable + */ +ol.interaction.DragRotateAndZoom = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + goog.base(this, { + handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_, + handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_, + handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_ + }); + + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.shiftKeyOnly; + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.lastMagnitude_ = undefined; + + /** + * @private + * @type {number} + */ + this.lastScaleDelta_ = 0; + + /** + * @private + * @type {number} + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 400; + +}; +goog.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer); + + +/** + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @this {ol.interaction.DragRotateAndZoom} + * @private + */ +ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return; + } + + var map = mapBrowserEvent.map; + var size = map.getSize(); + var offset = mapBrowserEvent.pixel; + var delta = new goog.math.Vec2( + offset[0] - size[0] / 2, + size[1] / 2 - offset[1]); + var theta = Math.atan2(delta.y, delta.x); + var magnitude = delta.magnitude(); + var view = map.getView(); + var viewState = view.getState(); + map.render(); + if (goog.isDef(this.lastAngle_)) { + var angleDelta = theta - this.lastAngle_; + ol.interaction.Interaction.rotateWithoutConstraints( + map, view, viewState.rotation - angleDelta); + } + this.lastAngle_ = theta; + if (goog.isDef(this.lastMagnitude_)) { + var resolution = this.lastMagnitude_ * (viewState.resolution / magnitude); + ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution); + } + if (goog.isDef(this.lastMagnitude_)) { + this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; + } + this.lastMagnitude_ = magnitude; }; /** - * @param {Array.<string>} templates Templates. - * @return {ol.TileUrlFunctionType} Tile URL function. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.DragRotateAndZoom} + * @private */ -ol.TileUrlFunction.createFromTemplates = function(templates) { - return ol.TileUrlFunction.createFromTileUrlFunctions( - goog.array.map(templates, ol.TileUrlFunction.createFromTemplate)); +ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return true; + } + + var map = mapBrowserEvent.map; + var view = map.getView(); + view.setHint(ol.ViewHint.INTERACTING, -1); + var viewState = view.getState(); + var direction = this.lastScaleDelta_ - 1; + ol.interaction.Interaction.rotate(map, view, viewState.rotation); + ol.interaction.Interaction.zoom(map, view, viewState.resolution, + undefined, this.duration_, direction); + this.lastScaleDelta_ = 0; + return false; }; /** - * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions. - * @return {ol.TileUrlFunctionType} Tile URL function. + * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.DragRotateAndZoom} + * @private */ -ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) { - if (tileUrlFunctions.length === 1) { - return tileUrlFunctions[0]; +ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) { + if (!ol.events.condition.mouseOnly(mapBrowserEvent)) { + return false; + } + + if (this.condition_(mapBrowserEvent)) { + mapBrowserEvent.map.getView().setHint(ol.ViewHint.INTERACTING, 1); + this.lastAngle_ = undefined; + this.lastMagnitude_ = undefined; + return true; + } else { + return false; } - return ( - /** - * @param {ol.TileCoord} tileCoord Tile Coordinate. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} Tile URL. - */ - function(tileCoord, pixelRatio, projection) { - if (goog.isNull(tileCoord)) { - return undefined; - } else { - var h = ol.tilecoord.hash(tileCoord); - var index = goog.math.modulo(h, tileUrlFunctions.length); - return tileUrlFunctions[index](tileCoord, pixelRatio, projection); - } - }); }; +goog.provide('ol.interaction.Draw'); +goog.provide('ol.interaction.DrawEvent'); +goog.provide('ol.interaction.DrawEventType'); +goog.provide('ol.interaction.DrawGeometryFunctionType'); +goog.provide('ol.interaction.DrawMode'); -/** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} Tile URL. - */ -ol.TileUrlFunction.nullTileUrlFunction = - function(tileCoord, pixelRatio, projection) { - return undefined; -}; +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('ol.Collection'); +goog.require('ol.Coordinate'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.Object'); +goog.require('ol.coordinate'); +goog.require('ol.events.condition'); +goog.require('ol.geom.Circle'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.SimpleGeometry'); +goog.require('ol.interaction.InteractionProperty'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); /** - * @param {ol.TileCoordTransformType} transformFn Transform function. - * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. - * @return {ol.TileUrlFunctionType} Tile URL function. + * @enum {string} */ -ol.TileUrlFunction.withTileCoordTransform = - function(transformFn, tileUrlFunction) { - var tmpTileCoord = [0, 0, 0]; - return ( - /** - * @param {ol.TileCoord} tileCoord Tile Coordinate. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} Tile URL. - */ - function(tileCoord, pixelRatio, projection) { - if (goog.isNull(tileCoord)) { - return undefined; - } else { - return tileUrlFunction( - transformFn(tileCoord, projection, tmpTileCoord), - pixelRatio, - projection); - } - }); +ol.interaction.DrawEventType = { + /** + * Triggered upon feature draw start + * @event ol.interaction.DrawEvent#drawstart + * @api stable + */ + DRAWSTART: 'drawstart', + /** + * Triggered upon feature draw end + * @event ol.interaction.DrawEvent#drawend + * @api stable + */ + DRAWEND: 'drawend' }; + /** - * @param {string} url URL. - * @return {Array.<string>} Array of urls. + * @classdesc + * Events emitted by {@link ol.interaction.Draw} instances are instances of + * this type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.DrawEvent} + * @param {ol.interaction.DrawEventType} type Type. + * @param {ol.Feature} feature The feature drawn. */ -ol.TileUrlFunction.expandUrl = function(url) { - var urls = []; - var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url); - if (match) { - var startCharCode = match[1].charCodeAt(0); - var stopCharCode = match[2].charCodeAt(0); - var charCode; - for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) { - urls.push(url.replace(match[0], String.fromCharCode(charCode))); - } - } else { - urls.push(url); - } - return urls; -}; +ol.interaction.DrawEvent = function(type, feature) { -goog.provide('ol.TileCache'); + goog.base(this, type); -goog.require('goog.asserts'); -goog.require('ol'); -goog.require('ol.TileRange'); -goog.require('ol.structs.LRUCache'); -goog.require('ol.tilecoord'); + /** + * The feature being drawn. + * @type {ol.Feature} + * @api stable + */ + this.feature = feature; + +}; +goog.inherits(ol.interaction.DrawEvent, goog.events.Event); /** + * @classdesc + * Interaction for drawing feature geometries. + * * @constructor - * @extends {ol.structs.LRUCache.<ol.Tile>} - * @param {number=} opt_highWaterMark High water mark. - * @struct + * @extends {ol.interaction.Pointer} + * @fires ol.interaction.DrawEvent + * @param {olx.interaction.DrawOptions} options Options. + * @api stable */ -ol.TileCache = function(opt_highWaterMark) { +ol.interaction.Draw = function(options) { - goog.base(this); + goog.base(this, { + handleDownEvent: ol.interaction.Draw.handleDownEvent_, + handleEvent: ol.interaction.Draw.handleEvent, + handleUpEvent: ol.interaction.Draw.handleUpEvent_ + }); /** + * @type {ol.Pixel} * @private - * @type {number} */ - this.highWaterMark_ = goog.isDef(opt_highWaterMark) ? - opt_highWaterMark : ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK; + this.downPx_ = null; -}; -goog.inherits(ol.TileCache, ol.structs.LRUCache); + /** + * @type {boolean} + * @private + */ + this.freehand_ = false; + /** + * Target source for drawn features. + * @type {ol.source.Vector} + * @private + */ + this.source_ = goog.isDef(options.source) ? options.source : null; -/** - * @return {boolean} Can expire cache. - */ -ol.TileCache.prototype.canExpireCache = function() { - return this.getCount() > this.highWaterMark_; -}; + /** + * Target collection for drawn features. + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = goog.isDef(options.features) ? options.features : null; + /** + * Pixel distance for snapping. + * @type {number} + * @private + */ + this.snapTolerance_ = goog.isDef(options.snapTolerance) ? + options.snapTolerance : 12; -/** - * @param {Object.<string, ol.TileRange>} usedTiles Used tiles. - */ -ol.TileCache.prototype.expireCache = function(usedTiles) { - var tile, zKey; - while (this.canExpireCache()) { - tile = /** @type {ol.Tile} */ (this.peekLast()); - zKey = tile.tileCoord[0].toString(); - if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) { - break; - } else { - this.pop().dispose(); - } - } -}; + /** + * Geometry type. + * @type {ol.geom.GeometryType} + * @private + */ + this.type_ = options.type; + /** + * Drawing mode (derived from geometry type. + * @type {ol.interaction.DrawMode} + * @private + */ + this.mode_ = ol.interaction.Draw.getMode_(this.type_); -/** - * Remove a tile range from the cache, e.g. to invalidate tiles. - * @param {ol.TileRange} tileRange The tile range to prune. - */ -ol.TileCache.prototype.pruneTileRange = function(tileRange) { - var i = this.getCount(), - key; - while (i--) { - key = this.peekLastKey(); - if (tileRange.contains(ol.tilecoord.createFromString(key))) { - this.pop().dispose(); + /** + * The number of points that must be drawn before a polygon ring or line + * string can be finished. The default is 3 for polygon rings and 2 for + * line strings. + * @type {number} + * @private + */ + this.minPoints_ = goog.isDef(options.minPoints) ? + options.minPoints : + (this.mode_ === ol.interaction.DrawMode.POLYGON ? 3 : 2); + + /** + * The number of points that can be drawn before a polygon ring or line string + * is finished. The default is no restriction. + * @type {number} + * @private + */ + this.maxPoints_ = goog.isDef(options.maxPoints) ? + options.maxPoints : Infinity; + + var geometryFunction = options.geometryFunction; + if (!goog.isDef(geometryFunction)) { + if (this.type_ === ol.geom.GeometryType.CIRCLE) { + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + geometryFunction = function(coordinates, opt_geometry) { + var circle = goog.isDef(opt_geometry) ? opt_geometry : + new ol.geom.Circle([NaN, NaN]); + goog.asserts.assertInstanceof(circle, ol.geom.Circle, + 'geometry must be an ol.geom.Circle'); + var squaredLength = ol.coordinate.squaredDistance( + coordinates[0], coordinates[1]); + circle.setCenterAndRadius(coordinates[0], Math.sqrt(squaredLength)); + return circle; + }; } else { - this.get(key); + var Constructor; + var mode = this.mode_; + if (mode === ol.interaction.DrawMode.POINT) { + Constructor = ol.geom.Point; + } else if (mode === ol.interaction.DrawMode.LINE_STRING) { + Constructor = ol.geom.LineString; + } else if (mode === ol.interaction.DrawMode.POLYGON) { + Constructor = ol.geom.Polygon; + } + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + geometryFunction = function(coordinates, opt_geometry) { + var geometry = opt_geometry; + if (goog.isDef(geometry)) { + geometry.setCoordinates(coordinates); + } else { + geometry = new Constructor(coordinates); + } + return geometry; + }; } } -}; - -goog.provide('ol.source.TileImage'); - -goog.require('goog.asserts'); -goog.require('ol.ImageTile'); -goog.require('ol.TileCache'); -goog.require('ol.TileCoord'); -goog.require('ol.TileLoadFunctionType'); -goog.require('ol.TileState'); -goog.require('ol.TileUrlFunction'); -goog.require('ol.TileUrlFunctionType'); -goog.require('ol.source.Tile'); + /** + * @type {ol.interaction.DrawGeometryFunctionType} + * @private + */ + this.geometryFunction_ = geometryFunction; + /** + * Finish coordinate for the feature (first point for polygons, last point for + * linestrings). + * @type {ol.Coordinate} + * @private + */ + this.finishCoordinate_ = null; -/** - * @classdesc - * Base class for sources providing images divided into a tile grid. - * - * @constructor - * @extends {ol.source.Tile} - * @param {olx.source.TileImageOptions} options Image tile options. - * @api - */ -ol.source.TileImage = function(options) { + /** + * Sketch feature. + * @type {ol.Feature} + * @private + */ + this.sketchFeature_ = null; - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - logo: options.logo, - opaque: options.opaque, - projection: options.projection, - state: goog.isDef(options.state) ? - /** @type {ol.source.State} */ (options.state) : undefined, - tileGrid: options.tileGrid, - tilePixelRatio: options.tilePixelRatio - }); + /** + * Sketch point. + * @type {ol.Feature} + * @private + */ + this.sketchPoint_ = null; /** - * @protected - * @type {ol.TileUrlFunctionType} + * Sketch coordinates. Used when drawing a line or polygon. + * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} + * @private */ - this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ? - options.tileUrlFunction : - ol.TileUrlFunction.nullTileUrlFunction; + this.sketchCoords_ = null; /** - * @protected - * @type {?string} + * Sketch line. Used when drawing polygon. + * @type {ol.Feature} + * @private */ - this.crossOrigin = - goog.isDef(options.crossOrigin) ? options.crossOrigin : null; + this.sketchLine_ = null; /** - * @protected - * @type {ol.TileCache} + * Sketch line coordinates. Used when drawing a polygon or circle. + * @type {Array.<ol.Coordinate>} + * @private */ - this.tileCache = new ol.TileCache(); + this.sketchLineCoords_ = null; /** - * @protected - * @type {ol.TileLoadFunctionType} + * Squared tolerance for handling up events. If the squared distance + * between a down and up event is greater than this tolerance, up events + * will not be handled. + * @type {number} + * @private */ - this.tileLoadFunction = goog.isDef(options.tileLoadFunction) ? - options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction; + this.squaredClickTolerance_ = goog.isDef(options.clickTolerance) ? + options.clickTolerance * options.clickTolerance : 36; /** - * @protected - * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string, - * ?string, ol.TileLoadFunctionType)} + * Draw overlay where our sketch features are drawn. + * @type {ol.layer.Vector} + * @private */ - this.tileClass = goog.isDef(options.tileClass) ? - options.tileClass : ol.ImageTile; + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : false + }), + style: goog.isDef(options.style) ? + options.style : ol.interaction.Draw.getDefaultStyleFunction() + }); -}; -goog.inherits(ol.source.TileImage, ol.source.Tile); + /** + * Name of the geometry attribute for newly created features. + * @type {string|undefined} + * @private + */ + this.geometryName_ = options.geometryName; + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.noModifierKeys; -/** - * @param {ol.ImageTile} imageTile Image tile. - * @param {string} src Source. - */ -ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) { - imageTile.getImage().src = src; -}; + /** + * @private + * @type {ol.events.ConditionType} + */ + this.freehandCondition_ = goog.isDef(options.freehandCondition) ? + options.freehandCondition : ol.events.condition.shiftKeyOnly; + goog.events.listen(this, + ol.Object.getChangeEventType(ol.interaction.InteractionProperty.ACTIVE), + this.updateState_, false, this); -/** - * @inheritDoc - */ -ol.source.TileImage.prototype.canExpireCache = function() { - return this.tileCache.canExpireCache(); }; +goog.inherits(ol.interaction.Draw, ol.interaction.Pointer); /** - * @inheritDoc + * @return {ol.style.StyleFunction} Styles. */ -ol.source.TileImage.prototype.expireCache = function(usedTiles) { - this.tileCache.expireCache(usedTiles); +ol.interaction.Draw.getDefaultStyleFunction = function() { + var styles = ol.style.createDefaultEditingStyles(); + return function(feature, resolution) { + return styles[feature.getGeometry().getType()]; + }; }; /** * @inheritDoc */ -ol.source.TileImage.prototype.getTile = - function(z, x, y, pixelRatio, projection) { - var tileCoordKey = this.getKeyZXY(z, x, y); - if (this.tileCache.containsKey(tileCoordKey)) { - return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); - } else { - goog.asserts.assert(projection); - var tileCoord = [z, x, y]; - var tileUrl = this.tileUrlFunction(tileCoord, pixelRatio, projection); - var tile = new this.tileClass( - tileCoord, - goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY, - goog.isDef(tileUrl) ? tileUrl : '', - this.crossOrigin, - this.tileLoadFunction); - this.tileCache.set(tileCoordKey, tile); - return tile; - } +ol.interaction.Draw.prototype.setMap = function(map) { + goog.base(this, 'setMap', map); + this.updateState_(); }; /** - * @return {ol.TileLoadFunctionType} TileLoadFunction + * Handles the {@link ol.MapBrowserEvent map browser event} and may actually + * draw or finish the drawing. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Draw} * @api */ -ol.source.TileImage.prototype.getTileLoadFunction = function() { - return this.tileLoadFunction; +ol.interaction.Draw.handleEvent = function(mapBrowserEvent) { + var pass = !this.freehand_; + if (this.freehand_ && + mapBrowserEvent.type === ol.MapBrowserEvent.EventType.POINTERDRAG) { + this.addToDrawing_(mapBrowserEvent); + pass = false; + } else if (mapBrowserEvent.type === + ol.MapBrowserEvent.EventType.POINTERMOVE) { + pass = this.handlePointerMove_(mapBrowserEvent); + } else if (mapBrowserEvent.type === ol.MapBrowserEvent.EventType.DBLCLICK) { + pass = false; + } + return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && pass; }; /** - * @return {ol.TileUrlFunctionType} TileUrlFunction - * @api + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Draw} + * @private */ -ol.source.TileImage.prototype.getTileUrlFunction = function() { - return this.tileUrlFunction; +ol.interaction.Draw.handleDownEvent_ = function(event) { + if (this.condition_(event)) { + this.downPx_ = event.pixel; + return true; + } else if ((this.mode_ === ol.interaction.DrawMode.LINE_STRING || + this.mode_ === ol.interaction.DrawMode.POLYGON) && + this.freehandCondition_(event)) { + this.downPx_ = event.pixel; + this.freehand_ = true; + if (goog.isNull(this.finishCoordinate_)) { + this.startDrawing_(event); + } + return true; + } else { + return false; + } }; /** - * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. - * @api + * @param {ol.MapBrowserPointerEvent} event Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Draw} + * @private */ -ol.source.TileImage.prototype.setTileLoadFunction = function(tileLoadFunction) { - this.tileCache.clear(); - this.tileLoadFunction = tileLoadFunction; - this.changed(); +ol.interaction.Draw.handleUpEvent_ = function(event) { + this.freehand_ = false; + var downPx = this.downPx_; + var clickPx = event.pixel; + var dx = downPx[0] - clickPx[0]; + var dy = downPx[1] - clickPx[1]; + var squaredDistance = dx * dx + dy * dy; + var pass = true; + if (squaredDistance <= this.squaredClickTolerance_) { + this.handlePointerMove_(event); + if (goog.isNull(this.finishCoordinate_)) { + this.startDrawing_(event); + if (this.mode_ === ol.interaction.DrawMode.POINT) { + this.finishDrawing(); + } + } else if (this.mode_ === ol.interaction.DrawMode.CIRCLE) { + this.finishDrawing(); + } else if (this.atFinish_(event)) { + this.finishDrawing(); + } else { + this.addToDrawing_(event); + } + pass = false; + } + return pass; }; /** - * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. - * @api + * Handle move events. + * @param {ol.MapBrowserEvent} event A move event. + * @return {boolean} Pass the event to other interactions. + * @private */ -ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) { - // FIXME It should be possible to be more intelligent and avoid clearing the - // FIXME cache. The tile URL function would need to be incorporated into the - // FIXME cache key somehow. - this.tileCache.clear(); - this.tileUrlFunction = tileUrlFunction; - this.changed(); +ol.interaction.Draw.prototype.handlePointerMove_ = function(event) { + if (!goog.isNull(this.finishCoordinate_)) { + this.modifyDrawing_(event); + } else { + this.createOrUpdateSketchPoint_(event); + } + return true; }; /** - * @inheritDoc + * Determine if an event is within the snapping tolerance of the start coord. + * @param {ol.MapBrowserEvent} event Event. + * @return {boolean} The event is within the snapping tolerance of the start. + * @private */ -ol.source.TileImage.prototype.useTile = function(z, x, y) { - var tileCoordKey = this.getKeyZXY(z, x, y); - if (this.tileCache.containsKey(tileCoordKey)) { - this.tileCache.get(tileCoordKey); +ol.interaction.Draw.prototype.atFinish_ = function(event) { + var at = false; + if (!goog.isNull(this.sketchFeature_)) { + var potentiallyDone = false; + var potentiallyFinishCoordinates = [this.finishCoordinate_]; + if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { + potentiallyDone = this.sketchCoords_.length > this.minPoints_; + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + potentiallyDone = this.sketchCoords_[0].length > + this.minPoints_; + potentiallyFinishCoordinates = [this.sketchCoords_[0][0], + this.sketchCoords_[0][this.sketchCoords_[0].length - 2]]; + } + if (potentiallyDone) { + var map = event.map; + for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) { + var finishCoordinate = potentiallyFinishCoordinates[i]; + var finishPixel = map.getPixelFromCoordinate(finishCoordinate); + var pixel = event.pixel; + var dx = pixel[0] - finishPixel[0]; + var dy = pixel[1] - finishPixel[1]; + var freehand = this.freehand_ && this.freehandCondition_(event); + var snapTolerance = freehand ? 1 : this.snapTolerance_; + at = Math.sqrt(dx * dx + dy * dy) <= snapTolerance; + if (at) { + this.finishCoordinate_ = finishCoordinate; + break; + } + } + } } + return at; }; -goog.provide('ol.tilegrid.XYZ'); - -goog.require('goog.math'); -goog.require('ol'); -goog.require('ol.TileCoord'); -goog.require('ol.TileRange'); -goog.require('ol.extent'); -goog.require('ol.extent.Corner'); -goog.require('ol.proj'); -goog.require('ol.proj.EPSG3857'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); - - /** - * @classdesc - * Set the grid pattern for sources accessing XYZ tiled-image servers. - * - * @constructor - * @extends {ol.tilegrid.TileGrid} - * @param {olx.tilegrid.XYZOptions} options XYZ options. - * @struct - * @api + * @param {ol.MapBrowserEvent} event Event. + * @private */ -ol.tilegrid.XYZ = function(options) { - var extent = goog.isDef(options.extent) ? - options.extent : ol.proj.EPSG3857.EXTENT; - var resolutions = ol.tilegrid.resolutionsFromExtent( - extent, options.maxZoom, options.tileSize); - - goog.base(this, { - minZoom: options.minZoom, - origin: ol.extent.getCorner(extent, ol.extent.Corner.TOP_LEFT), - resolutions: resolutions, - tileSize: options.tileSize - }); - +ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) { + var coordinates = event.coordinate.slice(); + if (goog.isNull(this.sketchPoint_)) { + this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates)); + this.updateSketchFeatures_(); + } else { + var sketchPointGeom = this.sketchPoint_.getGeometry(); + goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point, + 'sketchPointGeom should be an ol.geom.Point'); + sketchPointGeom.setCoordinates(coordinates); + } }; -goog.inherits(ol.tilegrid.XYZ, ol.tilegrid.TileGrid); /** - * @inheritDoc + * Start the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private */ -ol.tilegrid.XYZ.prototype.createTileCoordTransform = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - var minZ = this.minZoom; - var maxZ = this.maxZoom; - var wrapX = goog.isDef(options.wrapX) ? options.wrapX : true; - /** @type {Array.<ol.TileRange>} */ - var tileRangeByZ = null; - if (goog.isDef(options.extent)) { - tileRangeByZ = new Array(maxZ + 1); - var z; - for (z = 0; z <= maxZ; ++z) { - if (z < minZ) { - tileRangeByZ[z] = null; - } else { - tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z); - } +ol.interaction.Draw.prototype.startDrawing_ = function(event) { + var start = event.coordinate; + this.finishCoordinate_ = start; + if (this.mode_ === ol.interaction.DrawMode.POINT) { + this.sketchCoords_ = start.slice(); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + this.sketchCoords_ = [[start.slice(), start.slice()]]; + this.sketchLineCoords_ = this.sketchCoords_[0]; + } else { + this.sketchCoords_ = [start.slice(), start.slice()]; + if (this.mode_ === ol.interaction.DrawMode.CIRCLE) { + this.sketchLineCoords_ = this.sketchCoords_; } } - return ( - /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate. - * @return {ol.TileCoord} Tile coordinate. - */ - function(tileCoord, projection, opt_tileCoord) { - var z = tileCoord[0]; - if (z < minZ || maxZ < z) { - return null; - } - var n = Math.pow(2, z); - var x = tileCoord[1]; - if (wrapX) { - x = goog.math.modulo(x, n); - } else if (x < 0 || n <= x) { - return null; - } - var y = tileCoord[2]; - if (y < -n || -1 < y) { - return null; - } - if (!goog.isNull(tileRangeByZ)) { - if (!tileRangeByZ[z].containsXY(x, y)) { - return null; - } - } - return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord); - }); + if (!goog.isNull(this.sketchLineCoords_)) { + this.sketchLine_ = new ol.Feature( + new ol.geom.LineString(this.sketchLineCoords_)); + } + var geometry = this.geometryFunction_(this.sketchCoords_); + goog.asserts.assert(goog.isDef(geometry), 'geometry should be defined'); + this.sketchFeature_ = new ol.Feature(); + if (goog.isDef(this.geometryName_)) { + this.sketchFeature_.setGeometryName(this.geometryName_); + } + this.sketchFeature_.setGeometry(geometry); + this.updateSketchFeatures_(); + this.dispatchEvent(new ol.interaction.DrawEvent( + ol.interaction.DrawEventType.DRAWSTART, this.sketchFeature_)); }; /** - * @inheritDoc + * Modify the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private */ -ol.tilegrid.XYZ.prototype.getTileCoordChildTileRange = - function(tileCoord, opt_tileRange) { - if (tileCoord[0] < this.maxZoom) { - var doubleX = 2 * tileCoord[1]; - var doubleY = 2 * tileCoord[2]; - return ol.TileRange.createOrUpdate( - doubleX, doubleX + 1, - doubleY, doubleY + 1, - opt_tileRange); +ol.interaction.Draw.prototype.modifyDrawing_ = function(event) { + var coordinate = event.coordinate; + var geometry = this.sketchFeature_.getGeometry(); + goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry, + 'geometry should be ol.geom.SimpleGeometry or subclass'); + var coordinates, last; + if (this.mode_ === ol.interaction.DrawMode.POINT) { + last = this.sketchCoords_; + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + coordinates = this.sketchCoords_[0]; + last = coordinates[coordinates.length - 1]; + if (this.atFinish_(event)) { + // snap to finish + coordinate = this.finishCoordinate_.slice(); + } } else { - return null; + coordinates = this.sketchCoords_; + last = coordinates[coordinates.length - 1]; + } + last[0] = coordinate[0]; + last[1] = coordinate[1]; + goog.asserts.assert(!goog.isNull(this.sketchCoords_), + 'sketchCoords_ must not be null'); + this.geometryFunction_(this.sketchCoords_, geometry); + if (!goog.isNull(this.sketchPoint_)) { + var sketchPointGeom = this.sketchPoint_.getGeometry(); + goog.asserts.assertInstanceof(sketchPointGeom, ol.geom.Point, + 'sketchPointGeom should be an ol.geom.Point'); + sketchPointGeom.setCoordinates(coordinate); + } + var sketchLineGeom; + if (geometry instanceof ol.geom.Polygon && + this.mode_ !== ol.interaction.DrawMode.POLYGON) { + if (goog.isNull(this.sketchLine_)) { + this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null)); + } + var ring = geometry.getLinearRing(0); + sketchLineGeom = this.sketchLine_.getGeometry(); + goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString, + 'sketchLineGeom must be an ol.geom.LineString'); + sketchLineGeom.setFlatCoordinates( + ring.getLayout(), ring.getFlatCoordinates()); + } else if (!goog.isNull(this.sketchLineCoords_)) { + sketchLineGeom = this.sketchLine_.getGeometry(); + goog.asserts.assertInstanceof(sketchLineGeom, ol.geom.LineString, + 'sketchLineGeom must be an ol.geom.LineString'); + sketchLineGeom.setCoordinates(this.sketchLineCoords_); } + this.updateSketchFeatures_(); }; /** - * @inheritDoc + * Add a new coordinate to the drawing. + * @param {ol.MapBrowserEvent} event Event. + * @private */ -ol.tilegrid.XYZ.prototype.forEachTileCoordParentTileRange = - function(tileCoord, callback, opt_this, opt_tileRange) { - var tileRange = ol.TileRange.createOrUpdate( - 0, tileCoord[1], 0, tileCoord[2], opt_tileRange); - var z; - for (z = tileCoord[0] - 1; z >= this.minZoom; --z) { - tileRange.minX = tileRange.maxX >>= 1; - tileRange.minY = tileRange.maxY >>= 1; - if (callback.call(opt_this, z, tileRange)) { - return true; +ol.interaction.Draw.prototype.addToDrawing_ = function(event) { + var coordinate = event.coordinate; + var geometry = this.sketchFeature_.getGeometry(); + goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry, + 'geometry must be an ol.geom.SimpleGeometry'); + var done; + var coordinates; + if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { + this.finishCoordinate_ = coordinate.slice(); + coordinates = this.sketchCoords_; + coordinates.push(coordinate.slice()); + done = coordinates.length > this.maxPoints_; + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + coordinates = this.sketchCoords_[0]; + coordinates.push(coordinate.slice()); + done = coordinates.length > this.maxPoints_; + if (done) { + this.finishCoordinate_ = coordinates[0]; } + this.geometryFunction_(this.sketchCoords_, geometry); + } + this.updateSketchFeatures_(); + if (done) { + this.finishDrawing(); } - return false; }; -goog.provide('ol.source.BingMaps'); - -goog.require('goog.Uri'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.net.Jsonp'); -goog.require('ol'); -goog.require('ol.Attribution'); -goog.require('ol.TileRange'); -goog.require('ol.TileUrlFunction'); -goog.require('ol.extent'); -goog.require('ol.proj'); -goog.require('ol.source.State'); -goog.require('ol.source.TileImage'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.XYZ'); - - /** - * @classdesc - * Layer source for Bing Maps tile data. - * - * @constructor - * @extends {ol.source.TileImage} - * @param {olx.source.BingMapsOptions} options Bing Maps options. - * @api stable + * Stop drawing and add the sketch feature to the target layer. + * The {@link ol.interaction.DrawEventType.DRAWEND} event is dispatched before + * inserting the feature. + * @api */ -ol.source.BingMaps = function(options) { - - goog.base(this, { - crossOrigin: 'anonymous', - opaque: true, - projection: ol.proj.get('EPSG:3857'), - state: ol.source.State.LOADING, - tileLoadFunction: options.tileLoadFunction - }); +ol.interaction.Draw.prototype.finishDrawing = function() { + var sketchFeature = this.abortDrawing_(); + goog.asserts.assert(!goog.isNull(sketchFeature), + 'sketchFeature should not be null'); + var coordinates = this.sketchCoords_; + var geometry = sketchFeature.getGeometry(); + goog.asserts.assertInstanceof(geometry, ol.geom.SimpleGeometry, + 'geometry must be an ol.geom.SimpleGeometry'); + if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) { + // remove the redundant last point + coordinates.pop(); + this.geometryFunction_(coordinates, geometry); + } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) { + // When we finish drawing a polygon on the last point, + // the last coordinate is duplicated as for LineString + // we force the replacement by the first point + coordinates[0].pop(); + coordinates[0].push(coordinates[0][0]); + this.geometryFunction_(coordinates, geometry); + } - /** - * @private - * @type {string} - */ - this.culture_ = goog.isDef(options.culture) ? options.culture : 'en-us'; + // cast multi-part geometries + if (this.type_ === ol.geom.GeometryType.MULTI_POINT) { + sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates])); + } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) { + sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates])); + } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) { + sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates])); + } - /** - * @private - * @type {number} - */ - this.maxZoom_ = goog.isDef(options.maxZoom) ? options.maxZoom : -1; + // First dispatch event to allow full set up of feature + this.dispatchEvent(new ol.interaction.DrawEvent( + ol.interaction.DrawEventType.DRAWEND, sketchFeature)); - var protocol = ol.IS_HTTPS ? 'https:' : 'http:'; - var uri = new goog.Uri( - protocol + '//dev.virtualearth.net/REST/v1/Imagery/Metadata/' + - options.imagerySet); + // Then insert feature + if (!goog.isNull(this.features_)) { + this.features_.push(sketchFeature); + } + if (!goog.isNull(this.source_)) { + this.source_.addFeature(sketchFeature); + } +}; - var jsonp = new goog.net.Jsonp(uri, 'jsonp'); - jsonp.send({ - 'include': 'ImageryProviders', - 'uriScheme': ol.IS_HTTPS ? 'https' : 'http', - 'key': options.key - }, goog.bind(this.handleImageryMetadataResponse, this)); +/** + * Stop drawing without adding the sketch feature to the target layer. + * @return {ol.Feature} The sketch feature (or null if none). + * @private + */ +ol.interaction.Draw.prototype.abortDrawing_ = function() { + this.finishCoordinate_ = null; + var sketchFeature = this.sketchFeature_; + if (!goog.isNull(sketchFeature)) { + this.sketchFeature_ = null; + this.sketchPoint_ = null; + this.sketchLine_ = null; + this.overlay_.getSource().clear(true); + } + return sketchFeature; }; -goog.inherits(ol.source.BingMaps, ol.source.TileImage); /** - * @const - * @type {ol.Attribution} - * @api + * @inheritDoc */ -ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({ - html: '<a class="ol-attribution-bing-tos" ' + - 'href="http://www.microsoft.com/maps/product/terms.html">' + - 'Terms of Use</a>' -}); +ol.interaction.Draw.prototype.shouldStopEvent = goog.functions.FALSE; /** - * @param {BingMapsImageryMetadataResponse} response Response. + * Redraw the sketch features. + * @private */ -ol.source.BingMaps.prototype.handleImageryMetadataResponse = - function(response) { - - if (response.statusCode != 200 || - response.statusDescription != 'OK' || - response.authenticationResultCode != 'ValidCredentials' || - response.resourceSets.length != 1 || - response.resourceSets[0].resources.length != 1) { - this.setState(ol.source.State.ERROR); - return; +ol.interaction.Draw.prototype.updateSketchFeatures_ = function() { + var sketchFeatures = []; + if (!goog.isNull(this.sketchFeature_)) { + sketchFeatures.push(this.sketchFeature_); } - - var brandLogoUri = response.brandLogoUri; - //var copyright = response.copyright; // FIXME do we need to display this? - var resource = response.resourceSets[0].resources[0]; - goog.asserts.assert(resource.imageWidth == resource.imageHeight); - var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_; - - var sourceProjection = this.getProjection(); - var tileGrid = new ol.tilegrid.XYZ({ - extent: ol.tilegrid.extentFromProjection(sourceProjection), - minZoom: resource.zoomMin, - maxZoom: maxZoom, - tileSize: resource.imageWidth - }); - this.tileGrid = tileGrid; - - var culture = this.culture_; - this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform( - tileGrid.createTileCoordTransform(), - ol.TileUrlFunction.createFromTileUrlFunctions( - goog.array.map( - resource.imageUrlSubdomains, - function(subdomain) { - var imageUrl = resource.imageUrl - .replace('{subdomain}', subdomain) - .replace('{culture}', culture); - return ( - /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} Tile URL. - */ - function(tileCoord, pixelRatio, projection) { - goog.asserts.assert(ol.proj.equivalent( - projection, sourceProjection)); - if (goog.isNull(tileCoord)) { - return undefined; - } else { - return imageUrl.replace( - '{quadkey}', ol.tilecoord.quadKey(tileCoord)); - } - }); - }))); - - if (resource.imageryProviders) { - var transform = ol.proj.getTransformFromProjections( - ol.proj.get('EPSG:4326'), this.getProjection()); - - var attributions = goog.array.map( - resource.imageryProviders, - function(imageryProvider) { - var html = imageryProvider.attribution; - /** @type {Object.<string, Array.<ol.TileRange>>} */ - var tileRanges = {}; - goog.array.forEach( - imageryProvider.coverageAreas, - function(coverageArea) { - var minZ = coverageArea.zoomMin; - var maxZ = Math.min(coverageArea.zoomMax, maxZoom); - var bbox = coverageArea.bbox; - var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]]; - var extent = ol.extent.applyTransform( - epsg4326Extent, transform); - var tileRange, z, zKey; - for (z = minZ; z <= maxZ; ++z) { - zKey = z.toString(); - tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); - if (zKey in tileRanges) { - tileRanges[zKey].push(tileRange); - } else { - tileRanges[zKey] = [tileRange]; - } - } - }); - return new ol.Attribution({html: html, tileRanges: tileRanges}); - }); - attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION); - this.setAttributions(attributions); + if (!goog.isNull(this.sketchLine_)) { + sketchFeatures.push(this.sketchLine_); } - - this.setLogo(brandLogoUri); - - this.setState(ol.source.State.READY); - + if (!goog.isNull(this.sketchPoint_)) { + sketchFeatures.push(this.sketchPoint_); + } + var overlaySource = this.overlay_.getSource(); + overlaySource.clear(true); + overlaySource.addFeatures(sketchFeatures); }; -// FIXME keep cluster cache by resolution ? -// FIXME distance not respected because of the centroid - -goog.provide('ol.source.Cluster'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events.EventType'); -goog.require('goog.object'); -goog.require('ol.Feature'); -goog.require('ol.coordinate'); -goog.require('ol.extent'); -goog.require('ol.geom.Point'); -goog.require('ol.source.Vector'); +/** + * @private + */ +ol.interaction.Draw.prototype.updateState_ = function() { + var map = this.getMap(); + var active = this.getActive(); + if (goog.isNull(map) || !active) { + this.abortDrawing_(); + } + this.overlay_.setMap(active ? map : null); +}; /** - * @constructor - * @param {olx.source.ClusterOptions} options - * @extends {ol.source.Vector} + * Create a `geometryFunction` for `mode: 'Circle'` that will create a regular + * polygon with a user specified number of sides and start angle instead of an + * `ol.geom.Circle` geometry. + * @param {number=} opt_sides Number of sides of the regular polygon. Default is + * 32. + * @param {number=} opt_angle Angle of the first point in radians. 0 means East. + * Default is the angle defined by the heading from the center of the + * regular polygon to the current pointer position. + * @return {ol.interaction.DrawGeometryFunctionType} Function that draws a + * polygon. * @api */ -ol.source.Cluster = function(options) { - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - logo: options.logo, - projection: options.projection - }); - - /** - * @type {number|undefined} - * @private - */ - this.resolution_ = undefined; - - /** - * @type {number} - * @private - */ - this.distance_ = goog.isDef(options.distance) ? options.distance : 20; - - /** - * @type {Array.<ol.Feature>} - * @private - */ - this.features_ = []; - - /** - * @type {ol.source.Vector} - * @private - */ - this.source_ = options.source; - - this.source_.on(goog.events.EventType.CHANGE, - ol.source.Cluster.prototype.onSourceChange_, this); +ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) { + return ( + /** + * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates + * @param {ol.geom.SimpleGeometry=} opt_geometry + * @return {ol.geom.SimpleGeometry} + */ + function(coordinates, opt_geometry) { + var center = coordinates[0]; + var end = coordinates[1]; + var radius = Math.sqrt( + ol.coordinate.squaredDistance(center, end)); + var geometry = goog.isDef(opt_geometry) ? opt_geometry : + ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides); + goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, + 'geometry must be a polygon'); + var angle = goog.isDef(opt_angle) ? opt_angle : + Math.atan((end[1] - center[1]) / (end[0] - center[0])); + ol.geom.Polygon.makeRegular(geometry, center, radius, angle); + return geometry; + } + ); }; -goog.inherits(ol.source.Cluster, ol.source.Vector); /** - * @inheritDoc + * Get the drawing mode. The mode for mult-part geometries is the same as for + * their single-part cousins. + * @param {ol.geom.GeometryType} type Geometry type. + * @return {ol.interaction.DrawMode} Drawing mode. + * @private */ -ol.source.Cluster.prototype.loadFeatures = function(extent, resolution, - projection) { - if (resolution !== this.resolution_) { - this.clear(); - this.resolution_ = resolution; - this.source_.loadFeatures(extent, resolution, projection); - this.cluster_(); - this.addFeatures(this.features_); +ol.interaction.Draw.getMode_ = function(type) { + var mode; + if (type === ol.geom.GeometryType.POINT || + type === ol.geom.GeometryType.MULTI_POINT) { + mode = ol.interaction.DrawMode.POINT; + } else if (type === ol.geom.GeometryType.LINE_STRING || + type === ol.geom.GeometryType.MULTI_LINE_STRING) { + mode = ol.interaction.DrawMode.LINE_STRING; + } else if (type === ol.geom.GeometryType.POLYGON || + type === ol.geom.GeometryType.MULTI_POLYGON) { + mode = ol.interaction.DrawMode.POLYGON; + } else if (type === ol.geom.GeometryType.CIRCLE) { + mode = ol.interaction.DrawMode.CIRCLE; } + goog.asserts.assert(goog.isDef(mode), 'mode should be defined'); + return mode; }; /** - * handle the source changing - * @private + * Function that takes coordinates and an optional existing geometry as + * arguments, and returns a geometry. The optional existing geometry is the + * geometry that is returned when the function is called without a second + * argument. + * @typedef {function(!(ol.Coordinate|Array.<ol.Coordinate>| + * Array.<Array.<ol.Coordinate>>), ol.geom.SimpleGeometry=): + * ol.geom.SimpleGeometry} + * @api */ -ol.source.Cluster.prototype.onSourceChange_ = function() { - this.clear(); - this.cluster_(); - this.addFeatures(this.features_); - this.changed(); -}; +ol.interaction.DrawGeometryFunctionType; /** - * @private + * Draw mode. This collapses multi-part geometry types with their single-part + * cousins. + * @enum {string} */ -ol.source.Cluster.prototype.cluster_ = function() { - if (!goog.isDef(this.resolution_)) { - return; - } - goog.array.clear(this.features_); - var extent = ol.extent.createEmpty(); - var mapDistance = this.distance_ * this.resolution_; - var features = this.source_.getFeatures(); - - /** - * @type {Object.<string, boolean>} - */ - var clustered = {}; +ol.interaction.DrawMode = { + POINT: 'Point', + LINE_STRING: 'LineString', + POLYGON: 'Polygon', + CIRCLE: 'Circle' +}; - for (var i = 0, ii = features.length; i < ii; i++) { - var feature = features[i]; - if (!goog.object.containsKey(clustered, goog.getUid(feature).toString())) { - var geometry = feature.getGeometry(); - goog.asserts.assert(geometry instanceof ol.geom.Point); - var coordinates = geometry.getCoordinates(); - ol.extent.createOrUpdateFromCoordinate(coordinates, extent); - ol.extent.buffer(extent, mapDistance, extent); +goog.provide('ol.interaction.Modify'); - var neighbors = this.source_.getFeaturesInExtent(extent); - goog.asserts.assert(neighbors.length >= 1); - neighbors = goog.array.filter(neighbors, function(neighbor) { - var uid = goog.getUid(neighbor).toString(); - if (!goog.object.containsKey(clustered, uid)) { - goog.object.set(clustered, uid, true); - return true; - } else { - return false; - } - }); - this.features_.push(this.createCluster_(neighbors)); - } - } - goog.asserts.assert( - goog.object.getCount(clustered) == this.source_.getFeatures().length); -}; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.functions'); +goog.require('ol.Collection'); +goog.require('ol.CollectionEventType'); +goog.require('ol.Feature'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.ViewHint'); +goog.require('ol.coordinate'); +goog.require('ol.events.condition'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.structs.RBush'); +goog.require('ol.style.Style'); /** - * @param {Array.<ol.Feature>} features Features - * @return {ol.Feature} - * @private + * @enum {string} */ -ol.source.Cluster.prototype.createCluster_ = function(features) { - var length = features.length; - var centroid = [0, 0]; - for (var i = 0; i < length; i++) { - var geometry = features[i].getGeometry(); - goog.asserts.assert(geometry instanceof ol.geom.Point); - var coordinates = geometry.getCoordinates(); - ol.coordinate.add(centroid, coordinates); - } - ol.coordinate.scale(centroid, 1 / length); - - var cluster = new ol.Feature(new ol.geom.Point(centroid)); - cluster.set('features', features); - return cluster; +ol.ModifyEventType = { + /** + * Triggered upon feature modification start + * @event ol.ModifyEvent#modifystart + * @api + */ + MODIFYSTART: 'modifystart', + /** + * Triggered upon feature modification end + * @event ol.ModifyEvent#modifyend + * @api + */ + MODIFYEND: 'modifyend' }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** - * @fileoverview Common events for the network classes. + * @classdesc + * Events emitted by {@link ol.interaction.Modify} instances are instances of + * this type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.ModifyEvent} + * @param {ol.ModifyEventType} type Type. + * @param {ol.Collection.<ol.Feature>} features The features modified. + * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated + * {@link ol.MapBrowserPointerEvent}. */ +ol.ModifyEvent = function(type, features, mapBrowserPointerEvent) { + goog.base(this, type); -goog.provide('goog.net.EventType'); - + /** + * The features being modified. + * @type {ol.Collection.<ol.Feature>} + * @api + */ + this.features = features; -/** - * Event names for network events - * @enum {string} - */ -goog.net.EventType = { - COMPLETE: 'complete', - SUCCESS: 'success', - ERROR: 'error', - ABORT: 'abort', - READY: 'ready', - READY_STATE_CHANGE: 'readystatechange', - TIMEOUT: 'timeout', - INCREMENTAL_DATA: 'incrementaldata', - PROGRESS: 'progress' + /** + * Associated {@link ol.MapBrowserPointerEvent}. + * @type {ol.MapBrowserPointerEvent} + * @api + */ + this.mapBrowserPointerEvent = mapBrowserPointerEvent; }; +goog.inherits(ol.ModifyEvent, goog.events.Event); -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview A timer class to which other classes and objects can - * listen on. This is only an abstraction above setInterval. - * - * @see ../demos/timers.html + * @typedef {{depth: (Array.<number>|undefined), + * feature: ol.Feature, + * geometry: ol.geom.SimpleGeometry, + * index: (number|undefined), + * segment: Array.<ol.Extent>}} */ - -goog.provide('goog.Timer'); - -goog.require('goog.Promise'); -goog.require('goog.events.EventTarget'); +ol.interaction.SegmentDataType; /** - * Class for handling timing events. + * @classdesc + * Interaction for modifying feature geometries. * - * @param {number=} opt_interval Number of ms between ticks (Default: 1ms). - * @param {Object=} opt_timerObject An object that has setTimeout, setInterval, - * clearTimeout and clearInterval (eg Window). * @constructor - * @extends {goog.events.EventTarget} + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.ModifyOptions} options Options. + * @fires ol.ModifyEvent + * @api */ -goog.Timer = function(opt_interval, opt_timerObject) { - goog.events.EventTarget.call(this); +ol.interaction.Modify = function(options) { + + goog.base(this, { + handleDownEvent: ol.interaction.Modify.handleDownEvent_, + handleDragEvent: ol.interaction.Modify.handleDragEvent_, + handleEvent: ol.interaction.Modify.handleEvent, + handleUpEvent: ol.interaction.Modify.handleUpEvent_ + }); /** - * Number of ms between ticks - * @type {number} + * @type {ol.events.ConditionType} * @private */ - this.interval_ = opt_interval || 1; + this.deleteCondition_ = goog.isDef(options.deleteCondition) ? + options.deleteCondition : + /** @type {ol.events.ConditionType} */ (goog.functions.and( + ol.events.condition.noModifierKeys, + ol.events.condition.singleClick)); /** - * An object that implements setTimeout, setInterval, clearTimeout and - * clearInterval. We default to the window object. Changing this on - * goog.Timer.prototype changes the object for all timer instances which can - * be useful if your environment has some other implementation of timers than - * the window object. - * @type {Object} + * Editing vertex. + * @type {ol.Feature} * @private */ - this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject; + this.vertexFeature_ = null; /** - * Cached tick_ bound to the object for later use in the timer. - * @type {Function} + * Segments intersecting {@link this.vertexFeature_} by segment uid. + * @type {Object.<string, boolean>} * @private */ - this.boundTick_ = goog.bind(this.tick_, this); + this.vertexSegments_ = null; /** - * Firefox browser often fires the timer event sooner - * (sometimes MUCH sooner) than the requested timeout. So we - * compare the time to when the event was last fired, and - * reschedule if appropriate. See also goog.Timer.intervalScale - * @type {number} + * @type {ol.Pixel} * @private */ - this.last_ = goog.now(); -}; -goog.inherits(goog.Timer, goog.events.EventTarget); + this.lastPixel_ = [0, 0]; + /** + * Tracks if the next `singleclick` event should be ignored to prevent + * accidental deletion right after vertex creation. + * @type {boolean} + * @private + */ + this.ignoreNextSingleClick_ = false; -/** - * Maximum timeout value. - * - * Timeout values too big to fit into a signed 32-bit integer may cause - * overflow in FF, Safari, and Chrome, resulting in the timeout being - * scheduled immediately. It makes more sense simply not to schedule these - * timeouts, since 24.8 days is beyond a reasonable expectation for the - * browser to stay open. - * - * @type {number} - * @private - */ -goog.Timer.MAX_TIMEOUT_ = 2147483647; + /** + * Segment RTree for each layer + * @type {Object.<*, ol.structs.RBush>} + * @private + */ + this.rBush_ = new ol.structs.RBush(); + /** + * @type {number} + * @private + */ + this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ? + options.pixelTolerance : 10; -/** - * A timer ID that cannot be returned by any known implmentation of - * Window.setTimeout. Passing this value to window.clearTimeout should - * therefore be a no-op. - * - * @const {number} - * @private - */ -goog.Timer.INVALID_TIMEOUT_ID_ = -1; + /** + * @type {boolean} + * @private + */ + this.snappedToVertex_ = false; + /** + * @type {Array} + * @private + */ + this.dragSegments_ = null; -/** - * Whether this timer is enabled - * @type {boolean} - */ -goog.Timer.prototype.enabled = false; + /** + * Draw overlay where sketch features are drawn. + * @type {ol.layer.Vector} + * @private + */ + this.overlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : false + }), + style: goog.isDef(options.style) ? options.style : + ol.interaction.Modify.getDefaultStyleFunction(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + /** + * @const + * @private + * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)> } + */ + this.SEGMENT_WRITERS_ = { + 'Point': this.writePointGeometry_, + 'LineString': this.writeLineStringGeometry_, + 'LinearRing': this.writeLineStringGeometry_, + 'Polygon': this.writePolygonGeometry_, + 'MultiPoint': this.writeMultiPointGeometry_, + 'MultiLineString': this.writeMultiLineStringGeometry_, + 'MultiPolygon': this.writeMultiPolygonGeometry_, + 'GeometryCollection': this.writeGeometryCollectionGeometry_ + }; -/** - * An object that implements setTimout, setInterval, clearTimeout and - * clearInterval. We default to the global object. Changing - * goog.Timer.defaultTimerObject changes the object for all timer instances - * which can be useful if your environment has some other implementation of - * timers you'd like to use. - * @type {Object} - */ -goog.Timer.defaultTimerObject = goog.global; + /** + * @type {ol.Collection.<ol.Feature>} + * @private + */ + this.features_ = options.features; + this.features_.forEach(this.addFeature_, this); + goog.events.listen(this.features_, ol.CollectionEventType.ADD, + this.handleFeatureAdd_, false, this); + goog.events.listen(this.features_, ol.CollectionEventType.REMOVE, + this.handleFeatureRemove_, false, this); -/** - * A variable that controls the timer error correction. If the - * timer is called before the requested interval times - * intervalScale, which often happens on mozilla, the timer is - * rescheduled. See also this.last_ - * @type {number} - */ -goog.Timer.intervalScale = 0.8; +}; +goog.inherits(ol.interaction.Modify, ol.interaction.Pointer); /** - * Variable for storing the result of setInterval - * @type {?number} + * @param {ol.Feature} feature Feature. * @private */ -goog.Timer.prototype.timer_ = null; +ol.interaction.Modify.prototype.addFeature_ = function(feature) { + var geometry = feature.getGeometry(); + if (goog.isDef(this.SEGMENT_WRITERS_[geometry.getType()])) { + this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry); + } + var map = this.getMap(); + if (!goog.isNull(map)) { + this.handlePointerAtPixel_(this.lastPixel_, map); + } +}; /** - * Gets the interval of the timer. - * @return {number} interval Number of ms between ticks. + * @inheritDoc */ -goog.Timer.prototype.getInterval = function() { - return this.interval_; +ol.interaction.Modify.prototype.setMap = function(map) { + this.overlay_.setMap(map); + goog.base(this, 'setMap', map); }; /** - * Sets the interval of the timer. - * @param {number} interval Number of ms between ticks. + * @param {ol.CollectionEvent} evt Event. + * @private */ -goog.Timer.prototype.setInterval = function(interval) { - this.interval_ = interval; - if (this.timer_ && this.enabled) { - // Stop and then start the timer to reset the interval. - this.stop(); - this.start(); - } else if (this.timer_) { - this.stop(); - } +ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) { + var feature = evt.element; + goog.asserts.assertInstanceof(feature, ol.Feature, + 'feature should be an ol.Feature'); + this.addFeature_(feature); }; /** - * Callback for the setTimeout used by the timer + * @param {ol.CollectionEvent} evt Event. * @private */ -goog.Timer.prototype.tick_ = function() { - if (this.enabled) { - var elapsed = goog.now() - this.last_; - if (elapsed > 0 && - elapsed < this.interval_ * goog.Timer.intervalScale) { - this.timer_ = this.timerObject_.setTimeout(this.boundTick_, - this.interval_ - elapsed); - return; - } - - // Prevents setInterval from registering a duplicate timeout when called - // in the timer event handler. - if (this.timer_) { - this.timerObject_.clearTimeout(this.timer_); - this.timer_ = null; - } - - this.dispatchTick(); - // The timer could be stopped in the timer event handler. - if (this.enabled) { - this.timer_ = this.timerObject_.setTimeout(this.boundTick_, - this.interval_); - this.last_ = goog.now(); +ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) { + var feature = evt.element; + var rBush = this.rBush_; + var i, nodesToRemove = []; + rBush.forEachInExtent(feature.getGeometry().getExtent(), function(node) { + if (feature === node.feature) { + nodesToRemove.push(node); } + }); + for (i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); + } + // There remains only vertexFeature… + if (!goog.isNull(this.vertexFeature_) && + this.features_.getLength() === 0) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; } }; /** - * Dispatches the TICK event. This is its own method so subclasses can override. + * @param {ol.Feature} feature Feature + * @param {ol.geom.Point} geometry Geometry. + * @private */ -goog.Timer.prototype.dispatchTick = function() { - this.dispatchEvent(goog.Timer.TICK); +ol.interaction.Modify.prototype.writePointGeometry_ = + function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + feature: feature, + geometry: geometry, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); }; /** - * Starts the timer. + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPoint} geometry Geometry. + * @private */ -goog.Timer.prototype.start = function() { - this.enabled = true; - - // If there is no interval already registered, start it now - if (!this.timer_) { - // IMPORTANT! - // window.setInterval in FireFox has a bug - it fires based on - // absolute time, rather than on relative time. What this means - // is that if a computer is sleeping/hibernating for 24 hours - // and the timer interval was configured to fire every 1000ms, - // then after the PC wakes up the timer will fire, in rapid - // succession, 3600*24 times. - // This bug is described here and is already fixed, but it will - // take time to propagate, so for now I am switching this over - // to setTimeout logic. - // https://bugzilla.mozilla.org/show_bug.cgi?id=376643 - // - this.timer_ = this.timerObject_.setTimeout(this.boundTick_, - this.interval_); - this.last_ = goog.now(); +ol.interaction.Modify.prototype.writeMultiPointGeometry_ = + function(feature, geometry) { + var points = geometry.getCoordinates(); + var coordinates, i, ii, segmentData; + for (i = 0, ii = points.length; i < ii; ++i) { + coordinates = points[i]; + segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [i], + index: i, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); } }; /** - * Stops the timer. + * @param {ol.Feature} feature Feature + * @param {ol.geom.LineString} geometry Geometry. + * @private */ -goog.Timer.prototype.stop = function() { - this.enabled = false; - if (this.timer_) { - this.timerObject_.clearTimeout(this.timer_); - this.timer_ = null; +ol.interaction.Modify.prototype.writeLineStringGeometry_ = + function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + feature: feature, + geometry: geometry, + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); } }; -/** @override */ -goog.Timer.prototype.disposeInternal = function() { - goog.Timer.superClass_.disposeInternal.call(this); - this.stop(); - delete this.timerObject_; -}; - - /** - * Constant for the timer's event type - * @type {string} + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiLineString} geometry Geometry. + * @private */ -goog.Timer.TICK = 'tick'; +ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ = + function(feature, geometry) { + var lines = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = lines.length; j < jj; ++j) { + coordinates = lines[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; /** - * Calls the given function once, after the optional pause. - * - * The function is always called asynchronously, even if the delay is 0. This - * is a common trick to schedule a function to run after a batch of browser - * event processing. - * - * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function - * or object that has a handleEvent method. - * @param {number=} opt_delay Milliseconds to wait; default is 0. - * @param {SCOPE=} opt_handler Object in whose scope to call the listener. - * @return {number} A handle to the timer ID. - * @template SCOPE + * @param {ol.Feature} feature Feature + * @param {ol.geom.Polygon} geometry Geometry. + * @private */ -goog.Timer.callOnce = function(listener, opt_delay, opt_handler) { - if (goog.isFunction(listener)) { - if (opt_handler) { - listener = goog.bind(listener, opt_handler); +ol.interaction.Modify.prototype.writePolygonGeometry_ = + function(feature, geometry) { + var rings = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); } - } else if (listener && typeof listener.handleEvent == 'function') { - // using typeof to prevent strict js warning - listener = goog.bind(listener.handleEvent, listener); - } else { - throw Error('Invalid listener argument'); } +}; - if (opt_delay > goog.Timer.MAX_TIMEOUT_) { - // Timeouts greater than MAX_INT return immediately due to integer - // overflow in many browsers. Since MAX_INT is 24.8 days, just don't - // schedule anything at all. - return goog.Timer.INVALID_TIMEOUT_ID_; - } else { - return goog.Timer.defaultTimerObject.setTimeout( - listener, opt_delay || 0); + +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @private + */ +ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = + function(feature, geometry) { + var polygons = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; + for (k = 0, kk = polygons.length; k < kk; ++k) { + rings = polygons[k]; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + feature: feature, + geometry: geometry, + depth: [j, k], + index: i, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } } }; /** - * Clears a timeout initiated by callOnce - * @param {?number} timerId a timer ID. + * @param {ol.Feature} feature Feature + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @private */ -goog.Timer.clear = function(timerId) { - goog.Timer.defaultTimerObject.clearTimeout(timerId); +ol.interaction.Modify.prototype.writeGeometryCollectionGeometry_ = + function(feature, geometry) { + var i, geometries = geometry.getGeometriesArray(); + for (i = 0; i < geometries.length; ++i) { + this.SEGMENT_WRITERS_[geometries[i].getType()].call( + this, feature, geometries[i]); + } }; /** - * @param {number} delay Milliseconds to wait. - * @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value - * with which the promise will be resolved. - * @return {!goog.Promise<RESULT>} A promise that will be resolved after - * the specified delay, unless it is canceled first. - * @template RESULT + * @param {ol.Coordinate} coordinates Coordinates. + * @return {ol.Feature} Vertex feature. + * @private */ -goog.Timer.promise = function(delay, opt_result) { - var timerKey = null; - return new goog.Promise(function(resolve, reject) { - timerKey = goog.Timer.callOnce(function() { - resolve(opt_result); - }, delay); - if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) { - reject(new Error('Failed to schedule timer.')); - } - }).thenCatch(function(error) { - // Clear the timer. The most likely reason is "cancel" signal. - goog.Timer.clear(timerKey); - throw error; - }); +ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = + function(coordinates) { + var vertexFeature = this.vertexFeature_; + if (goog.isNull(vertexFeature)) { + vertexFeature = new ol.Feature(new ol.geom.Point(coordinates)); + this.vertexFeature_ = vertexFeature; + this.overlay_.getSource().addFeature(vertexFeature); + } else { + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + geometry.setCoordinates(coordinates); + } + return vertexFeature; }; -// Copyright 2007 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Error codes shared between goog.net.IframeIo and - * goog.net.XhrIo. + * @param {ol.interaction.SegmentDataType} a + * @param {ol.interaction.SegmentDataType} b + * @return {number} + * @private */ - -goog.provide('goog.net.ErrorCode'); +ol.interaction.Modify.compareIndexes_ = function(a, b) { + return a.index - b.index; +}; /** - * Error codes - * @enum {number} + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Start drag sequence? + * @this {ol.interaction.Modify} + * @private */ -goog.net.ErrorCode = { - - /** - * There is no error condition. - */ - NO_ERROR: 0, - - /** - * The most common error from iframeio, unfortunately, is that the browser - * responded with an error page that is classed as a different domain. The - * situations, are when a browser error page is shown -- 404, access denied, - * DNS failure, connection reset etc.) - * - */ - ACCESS_DENIED: 1, - - /** - * Currently the only case where file not found will be caused is when the - * code is running on the local file system and a non-IE browser makes a - * request to a file that doesn't exist. - */ - FILE_NOT_FOUND: 2, - - /** - * If Firefox shows a browser error page, such as a connection reset by - * server or access denied, then it will fail silently without the error or - * load handlers firing. - */ - FF_SILENT_ERROR: 3, - - /** - * Custom error provided by the client through the error check hook. - */ - CUSTOM_ERROR: 4, - - /** - * Exception was thrown while processing the request. - */ - EXCEPTION: 5, - - /** - * The Http response returned a non-successful http status code. - */ - HTTP_ERROR: 6, - - /** - * The request was aborted. - */ - ABORT: 7, - - /** - * The request timed out. - */ - TIMEOUT: 8, +ol.interaction.Modify.handleDownEvent_ = function(evt) { + this.handlePointerAtPixel_(evt.pixel, evt.map); + this.dragSegments_ = []; + var vertexFeature = this.vertexFeature_; + if (!goog.isNull(vertexFeature)) { + var insertVertices = []; + var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry()); + var vertex = geometry.getCoordinates(); + var vertexExtent = ol.extent.boundingExtent([vertex]); + var segmentDataMatches = this.rBush_.getInExtent(vertexExtent); + var componentSegments = {}; + segmentDataMatches.sort(ol.interaction.Modify.compareIndexes_); + for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) { + var segmentDataMatch = segmentDataMatches[i]; + var segment = segmentDataMatch.segment; + var uid = goog.getUid(segmentDataMatch.feature); + var depth = segmentDataMatch.depth; + if (depth) { + uid += '-' + depth.join('-'); // separate feature components + } + if (!componentSegments[uid]) { + componentSegments[uid] = new Array(2); + } + if (ol.coordinate.equals(segment[0], vertex) && + !componentSegments[uid][0]) { + this.dragSegments_.push([segmentDataMatch, 0]); + componentSegments[uid][0] = segmentDataMatch; + } else if (ol.coordinate.equals(segment[1], vertex) && + !componentSegments[uid][1]) { + + // prevent dragging closed linestrings by the connecting node + if ((segmentDataMatch.geometry.getType() === + ol.geom.GeometryType.LINE_STRING || + segmentDataMatch.geometry.getType() === + ol.geom.GeometryType.MULTI_LINE_STRING) && + componentSegments[uid][0] && + componentSegments[uid][0].index === 0) { + continue; + } - /** - * The resource is not available offline. - */ - OFFLINE: 9 + this.dragSegments_.push([segmentDataMatch, 1]); + componentSegments[uid][1] = segmentDataMatch; + } else if (goog.getUid(segment) in this.vertexSegments_ && + (!componentSegments[uid][0] && !componentSegments[uid][1])) { + insertVertices.push([segmentDataMatch, vertex]); + } + } + for (i = insertVertices.length - 1; i >= 0; --i) { + this.insertVertex_.apply(this, insertVertices[i]); + } + this.dispatchEvent(new ol.ModifyEvent(ol.ModifyEventType.MODIFYSTART, + this.features_, evt)); + } + return !goog.isNull(this.vertexFeature_); }; /** - * Returns a friendly error message for an error code. These messages are for - * debugging and are not localized. - * @param {goog.net.ErrorCode} errorCode An error code. - * @return {string} A message for debugging. + * @param {ol.MapBrowserPointerEvent} evt Event. + * @this {ol.interaction.Modify} + * @private */ -goog.net.ErrorCode.getDebugMessage = function(errorCode) { - switch (errorCode) { - case goog.net.ErrorCode.NO_ERROR: - return 'No Error'; - - case goog.net.ErrorCode.ACCESS_DENIED: - return 'Access denied to content document'; - - case goog.net.ErrorCode.FILE_NOT_FOUND: - return 'File not found'; - - case goog.net.ErrorCode.FF_SILENT_ERROR: - return 'Firefox silently errored'; - - case goog.net.ErrorCode.CUSTOM_ERROR: - return 'Application custom error'; - - case goog.net.ErrorCode.EXCEPTION: - return 'An exception occurred'; - - case goog.net.ErrorCode.HTTP_ERROR: - return 'Http response at 400 or 500 level'; +ol.interaction.Modify.handleDragEvent_ = function(evt) { + this.ignoreNextSingleClick_ = false; - case goog.net.ErrorCode.ABORT: - return 'Request was aborted'; + var vertex = evt.coordinate; + for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) { + var dragSegment = this.dragSegments_[i]; + var segmentData = dragSegment[0]; + var depth = segmentData.depth; + var geometry = segmentData.geometry; + var coordinates = geometry.getCoordinates(); + var segment = segmentData.segment; + var index = dragSegment[1]; - case goog.net.ErrorCode.TIMEOUT: - return 'Request timed out'; + while (vertex.length < geometry.getStride()) { + vertex.push(0); + } - case goog.net.ErrorCode.OFFLINE: - return 'The resource is not available offline'; + switch (geometry.getType()) { + case ol.geom.GeometryType.POINT: + coordinates = vertex; + segment[0] = segment[1] = vertex; + break; + case ol.geom.GeometryType.MULTI_POINT: + coordinates[segmentData.index] = vertex; + segment[0] = segment[1] = vertex; + break; + case ol.geom.GeometryType.LINE_STRING: + coordinates[segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.POLYGON: + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case ol.geom.GeometryType.MULTI_POLYGON: + coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + } - default: - return 'Unrecognized error code'; + geometry.setCoordinates(coordinates); } + this.createOrUpdateVertexFeature_(vertex); }; -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Constants for HTTP status codes. + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Modify} + * @private */ - -goog.provide('goog.net.HttpStatus'); +ol.interaction.Modify.handleUpEvent_ = function(evt) { + var segmentData; + for (var i = this.dragSegments_.length - 1; i >= 0; --i) { + segmentData = this.dragSegments_[i][0]; + this.rBush_.update(ol.extent.boundingExtent(segmentData.segment), + segmentData); + } + this.dispatchEvent(new ol.ModifyEvent(ol.ModifyEventType.MODIFYEND, + this.features_, evt)); + return false; +}; /** - * HTTP Status Codes defined in RFC 2616 and RFC 6585. - * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - * @see http://tools.ietf.org/html/rfc6585 - * @enum {number} + * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the + * geometry. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Modify} + * @api */ -goog.net.HttpStatus = { - // Informational 1xx - CONTINUE: 100, - SWITCHING_PROTOCOLS: 101, - - // Successful 2xx - OK: 200, - CREATED: 201, - ACCEPTED: 202, - NON_AUTHORITATIVE_INFORMATION: 203, - NO_CONTENT: 204, - RESET_CONTENT: 205, - PARTIAL_CONTENT: 206, +ol.interaction.Modify.handleEvent = function(mapBrowserEvent) { + var handled; + if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] && + mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE && + !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + if (!goog.isNull(this.vertexFeature_) && + this.deleteCondition_(mapBrowserEvent)) { + if (mapBrowserEvent.type != ol.MapBrowserEvent.EventType.SINGLECLICK || + !this.ignoreNextSingleClick_) { + var geometry = this.vertexFeature_.getGeometry(); + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + handled = this.removeVertex_(); + } else { + handled = true; + } + } - // Redirection 3xx - MULTIPLE_CHOICES: 300, - MOVED_PERMANENTLY: 301, - FOUND: 302, - SEE_OTHER: 303, - NOT_MODIFIED: 304, - USE_PROXY: 305, - TEMPORARY_REDIRECT: 307, + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) { + this.ignoreNextSingleClick_ = false; + } - // Client Error 4xx - BAD_REQUEST: 400, - UNAUTHORIZED: 401, - PAYMENT_REQUIRED: 402, - FORBIDDEN: 403, - NOT_FOUND: 404, - METHOD_NOT_ALLOWED: 405, - NOT_ACCEPTABLE: 406, - PROXY_AUTHENTICATION_REQUIRED: 407, - REQUEST_TIMEOUT: 408, - CONFLICT: 409, - GONE: 410, - LENGTH_REQUIRED: 411, - PRECONDITION_FAILED: 412, - REQUEST_ENTITY_TOO_LARGE: 413, - REQUEST_URI_TOO_LONG: 414, - UNSUPPORTED_MEDIA_TYPE: 415, - REQUEST_RANGE_NOT_SATISFIABLE: 416, - EXPECTATION_FAILED: 417, - PRECONDITION_REQUIRED: 428, - TOO_MANY_REQUESTS: 429, - REQUEST_HEADER_FIELDS_TOO_LARGE: 431, + return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && + !handled; +}; - // Server Error 5xx - INTERNAL_SERVER_ERROR: 500, - NOT_IMPLEMENTED: 501, - BAD_GATEWAY: 502, - SERVICE_UNAVAILABLE: 503, - GATEWAY_TIMEOUT: 504, - HTTP_VERSION_NOT_SUPPORTED: 505, - NETWORK_AUTHENTICATION_REQUIRED: 511, - /* - * IE returns this code for 204 due to its use of URLMon, which returns this - * code for 'Operation Aborted'. The status text is 'Unknown', the response - * headers are ''. Known to occur on IE 6 on XP through IE9 on Win7. - */ - QUIRK_IE_NO_CONTENT: 1223 +/** + * @param {ol.MapBrowserEvent} evt Event. + * @private + */ +ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) { + this.lastPixel_ = evt.pixel; + this.handlePointerAtPixel_(evt.pixel, evt.map); }; /** - * Returns whether the given status should be considered successful. - * - * Successful codes are OK (200), CREATED (201), ACCEPTED (202), - * NO CONTENT (204), PARTIAL CONTENT (206), NOT MODIFIED (304), - * and IE's no content code (1223). - * - * @param {number} status The status code to test. - * @return {boolean} Whether the status code should be considered successful. + * @param {ol.Pixel} pixel Pixel + * @param {ol.Map} map Map. + * @private */ -goog.net.HttpStatus.isSuccess = function(status) { - switch (status) { - case goog.net.HttpStatus.OK: - case goog.net.HttpStatus.CREATED: - case goog.net.HttpStatus.ACCEPTED: - case goog.net.HttpStatus.NO_CONTENT: - case goog.net.HttpStatus.PARTIAL_CONTENT: - case goog.net.HttpStatus.NOT_MODIFIED: - case goog.net.HttpStatus.QUIRK_IE_NO_CONTENT: - return true; +ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) { + var pixelCoordinate = map.getCoordinateFromPixel(pixel); + var sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) - + ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment); + }; - default: - return false; + var lowerLeft = map.getCoordinateFromPixel( + [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]); + var upperRight = map.getCoordinateFromPixel( + [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]); + var box = ol.extent.boundingExtent([lowerLeft, upperRight]); + + var rBush = this.rBush_; + var nodes = rBush.getInExtent(box); + if (nodes.length > 0) { + nodes.sort(sortByDistance); + var node = nodes[0]; + var closestSegment = node.segment; + var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + var vertexPixel = map.getPixelFromCoordinate(vertex); + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + this.snappedToVertex_ = dist <= this.pixelTolerance_; + if (this.snappedToVertex_) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + } + this.createOrUpdateVertexFeature_(vertex); + var vertexSegments = {}; + vertexSegments[goog.getUid(closestSegment)] = true; + var segment; + for (var i = 1, ii = nodes.length; i < ii; ++i) { + segment = nodes[i].segment; + if ((ol.coordinate.equals(closestSegment[0], segment[0]) && + ol.coordinate.equals(closestSegment[1], segment[1]) || + (ol.coordinate.equals(closestSegment[0], segment[1]) && + ol.coordinate.equals(closestSegment[1], segment[0])))) { + vertexSegments[goog.getUid(segment)] = true; + } else { + break; + } + } + this.vertexSegments_ = vertexSegments; + return; + } + } + if (!goog.isNull(this.vertexFeature_)) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; } }; -// Copyright 2013 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -goog.provide('goog.net.XhrLike'); +/** + * @param {ol.interaction.SegmentDataType} segmentData Segment data. + * @param {ol.Coordinate} vertex Vertex. + * @private + */ +ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) { + var segment = segmentData.segment; + var feature = segmentData.feature; + var geometry = segmentData.geometry; + var depth = segmentData.depth; + var index = segmentData.index; + var coordinates; + while (vertex.length < geometry.getStride()) { + vertex.push(0); + } + switch (geometry.getType()) { + case ol.geom.GeometryType.MULTI_LINE_STRING: + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, + 'geometry should be an ol.geom.MultiLineString'); + coordinates = geometry.getCoordinates(); + coordinates[depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.POLYGON: + goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, + 'geometry should be an ol.geom.Polygon'); + coordinates = geometry.getCoordinates(); + coordinates[depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon, + 'geometry should be an ol.geom.MultiPolygon'); + coordinates = geometry.getCoordinates(); + coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex); + break; + case ol.geom.GeometryType.LINE_STRING: + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + coordinates = geometry.getCoordinates(); + coordinates.splice(index + 1, 0, vertex); + break; + default: + return; + } -/** - * Interface for the common parts of XMLHttpRequest. - * - * Mostly copied from externs/w3c_xml.js. - * - * @interface - * @see http://www.w3.org/TR/XMLHttpRequest/ - */ -goog.net.XhrLike = function() {}; + geometry.setCoordinates(coordinates); + var rTree = this.rBush_; + goog.asserts.assert(goog.isDef(segment), 'segment should be defined'); + rTree.remove(segmentData); + goog.asserts.assert(goog.isDef(index), 'index should be defined'); + this.updateSegmentIndices_(geometry, index, depth, 1); + var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + segment: [segment[0], vertex], + feature: feature, + geometry: geometry, + depth: depth, + index: index + }); + rTree.insert(ol.extent.boundingExtent(newSegmentData.segment), + newSegmentData); + this.dragSegments_.push([newSegmentData, 1]); + + var newSegmentData2 = /** @type {ol.interaction.SegmentDataType} */ ({ + segment: [vertex, segment[1]], + feature: feature, + geometry: geometry, + depth: depth, + index: index + 1 + }); + rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment), + newSegmentData2); + this.dragSegments_.push([newSegmentData2, 0]); + this.ignoreNextSingleClick_ = true; +}; /** - * Typedef that refers to either native or custom-implemented XHR objects. - * @typedef {!goog.net.XhrLike|!XMLHttpRequest} + * Removes a vertex from all matching features. + * @return {boolean} True when a vertex was removed. + * @private */ -goog.net.XhrLike.OrNative; +ol.interaction.Modify.prototype.removeVertex_ = function() { + var dragSegments = this.dragSegments_; + var segmentsByFeature = {}; + var component, coordinates, dragSegment, geometry, i, index, left; + var newIndex, newSegment, right, segmentData, uid, deleted; + for (i = dragSegments.length - 1; i >= 0; --i) { + dragSegment = dragSegments[i]; + segmentData = dragSegment[0]; + geometry = segmentData.geometry; + coordinates = geometry.getCoordinates(); + uid = goog.getUid(segmentData.feature); + if (segmentData.depth) { + // separate feature components + uid += '-' + segmentData.depth.join('-'); + } + left = right = index = undefined; + if (dragSegment[1] === 0) { + right = segmentData; + index = segmentData.index; + } else if (dragSegment[1] == 1) { + left = segmentData; + index = segmentData.index + 1; + } + if (!(uid in segmentsByFeature)) { + segmentsByFeature[uid] = [left, right, index]; + } + newSegment = segmentsByFeature[uid]; + if (goog.isDef(left)) { + newSegment[0] = left; + } + if (goog.isDef(right)) { + newSegment[1] = right; + } + if (goog.isDef(newSegment[0]) && goog.isDef(newSegment[1])) { + component = coordinates; + deleted = false; + newIndex = index - 1; + switch (geometry.getType()) { + case ol.geom.GeometryType.MULTI_LINE_STRING: + coordinates[segmentData.depth[0]].splice(index, 1); + deleted = true; + break; + case ol.geom.GeometryType.LINE_STRING: + coordinates.splice(index, 1); + deleted = true; + break; + case ol.geom.GeometryType.MULTI_POLYGON: + component = component[segmentData.depth[1]]; + /* falls through */ + case ol.geom.GeometryType.POLYGON: + component = component[segmentData.depth[0]]; + if (component.length > 4) { + if (index == component.length - 1) { + index = 0; + } + component.splice(index, 1); + deleted = true; + if (index === 0) { + // close the ring again + component.pop(); + component.push(component[0]); + newIndex = component.length - 1; + } + } + break; + } + if (deleted) { + this.rBush_.remove(newSegment[0]); + this.rBush_.remove(newSegment[1]); + geometry.setCoordinates(coordinates); + goog.asserts.assert(newIndex >= 0, 'newIndex should be larger than 0'); + var newSegmentData = /** @type {ol.interaction.SegmentDataType} */ ({ + depth: segmentData.depth, + feature: segmentData.feature, + geometry: segmentData.geometry, + index: newIndex, + segment: [newSegment[0].segment[0], newSegment[1].segment[1]] + }); + this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment), + newSegmentData); + this.updateSegmentIndices_(geometry, index, segmentData.depth, -1); -/** - * @type {function()|null|undefined} - * @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange - */ -goog.net.XhrLike.prototype.onreadystatechange; + if (!goog.isNull(this.vertexFeature_)) { + this.overlay_.getSource().removeFeature(this.vertexFeature_); + this.vertexFeature_ = null; + } + } + } + } + return true; +}; /** - * @type {string} - * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute + * @param {ol.geom.SimpleGeometry} geometry Geometry. + * @param {number} index Index. + * @param {Array.<number>|undefined} depth Depth. + * @param {number} delta Delta (1 or -1). + * @private */ -goog.net.XhrLike.prototype.responseText; +ol.interaction.Modify.prototype.updateSegmentIndices_ = function( + geometry, index, depth, delta) { + this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) { + if (segmentDataMatch.geometry === geometry && + (!goog.isDef(depth) || + goog.array.equals(segmentDataMatch.depth, depth)) && + segmentDataMatch.index > index) { + segmentDataMatch.index += delta; + } + }); +}; /** - * @type {Document} - * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute + * @return {ol.style.StyleFunction} Styles. */ -goog.net.XhrLike.prototype.responseXML; +ol.interaction.Modify.getDefaultStyleFunction = function() { + var style = ol.style.createDefaultEditingStyles(); + return function(feature, resolution) { + return style[ol.geom.GeometryType.POINT]; + }; +}; +goog.provide('ol.interaction.Select'); +goog.provide('ol.interaction.SelectFilterFunction'); -/** - * @type {number} - * @see http://www.w3.org/TR/XMLHttpRequest/#readystate - */ -goog.net.XhrLike.prototype.readyState; +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.functions'); +goog.require('ol.CollectionEventType'); +goog.require('ol.Feature'); +goog.require('ol.events.condition'); +goog.require('ol.geom.GeometryType'); +goog.require('ol.interaction.Interaction'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); +goog.require('ol.style.Style'); /** - * @type {number} - * @see http://www.w3.org/TR/XMLHttpRequest/#status + * @enum {string} */ -goog.net.XhrLike.prototype.status; +ol.SelectEventType = { + /** + * Triggered when feature(s) has been (de)selected. + * @event ol.SelectEvent#select + * @api + */ + SELECT: 'select' +}; /** - * @type {string} - * @see http://www.w3.org/TR/XMLHttpRequest/#statustext + * A function that takes an {@link ol.Feature} and an {@link ol.layer.Layer} + * and returns `true` if the feature may be selected or `false` otherwise. + * @typedef {function(ol.Feature, ol.layer.Layer): boolean} + * @api */ -goog.net.XhrLike.prototype.statusText; +ol.interaction.SelectFilterFunction; -/** - * @param {string} method - * @param {string} url - * @param {?boolean=} opt_async - * @param {?string=} opt_user - * @param {?string=} opt_password - * @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method - */ -goog.net.XhrLike.prototype.open = function(method, url, opt_async, opt_user, - opt_password) {}; - /** - * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} opt_data - * @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method + * @classdesc + * Events emitted by {@link ol.interaction.Select} instances are instances of + * this type. + * + * @param {string} type The event type. + * @param {Array.<ol.Feature>} selected Selected features. + * @param {Array.<ol.Feature>} deselected Deselected features. + * @param {ol.MapBrowserEvent} mapBrowserEvent Associated + * {@link ol.MapBrowserEvent}. + * @implements {oli.SelectEvent} + * @extends {goog.events.Event} + * @constructor */ -goog.net.XhrLike.prototype.send = function(opt_data) {}; +ol.SelectEvent = function(type, selected, deselected, mapBrowserEvent) { + goog.base(this, type); + /** + * Selected features array. + * @type {Array.<ol.Feature>} + * @api + */ + this.selected = selected; -/** - * @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method - */ -goog.net.XhrLike.prototype.abort = function() {}; + /** + * Deselected features array. + * @type {Array.<ol.Feature>} + * @api + */ + this.deselected = deselected; + /** + * Associated {@link ol.MapBrowserEvent}. + * @type {ol.MapBrowserEvent} + * @api + */ + this.mapBrowserEvent = mapBrowserEvent; +}; +goog.inherits(ol.SelectEvent, goog.events.Event); -/** - * @param {string} header - * @param {string} value - * @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method - */ -goog.net.XhrLike.prototype.setRequestHeader = function(header, value) {}; /** - * @param {string} header - * @return {string} - * @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method + * @classdesc + * Interaction for selecting vector features. By default, selected features are + * styled differently, so this interaction can be used for visual highlighting, + * as well as selecting features for other actions, such as modification or + * output. There are three ways of controlling which features are selected: + * using the browser event as defined by the `condition` and optionally the + * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a + * further feature filter using the `filter` option. + * + * @constructor + * @extends {ol.interaction.Interaction} + * @param {olx.interaction.SelectOptions=} opt_options Options. + * @fires ol.SelectEvent + * @api stable */ -goog.net.XhrLike.prototype.getResponseHeader = function(header) {}; +ol.interaction.Select = function(opt_options) { + goog.base(this, { + handleEvent: ol.interaction.Select.handleEvent + }); -/** - * @return {string} - * @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method - */ -goog.net.XhrLike.prototype.getAllResponseHeaders = function() {}; + var options = goog.isDef(opt_options) ? opt_options : {}; -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** + * @private + * @type {ol.events.ConditionType} + */ + this.condition_ = goog.isDef(options.condition) ? + options.condition : ol.events.condition.singleClick; -/** - * @fileoverview Interface for a factory for creating XMLHttpRequest objects - * and metadata about them. - * @author dbk@google.com (David Barrett-Kahn) - */ + /** + * @private + * @type {ol.events.ConditionType} + */ + this.addCondition_ = goog.isDef(options.addCondition) ? + options.addCondition : ol.events.condition.never; -goog.provide('goog.net.XmlHttpFactory'); + /** + * @private + * @type {ol.events.ConditionType} + */ + this.removeCondition_ = goog.isDef(options.removeCondition) ? + options.removeCondition : ol.events.condition.never; -/** @suppress {extraRequire} Typedef. */ -goog.require('goog.net.XhrLike'); + /** + * @private + * @type {ol.events.ConditionType} + */ + this.toggleCondition_ = goog.isDef(options.toggleCondition) ? + options.toggleCondition : ol.events.condition.shiftKeyOnly; + /** + * @private + * @type {boolean} + */ + this.multi_ = goog.isDef(options.multi) ? options.multi : false; + /** + * @private + * @type {ol.interaction.SelectFilterFunction} + */ + this.filter_ = goog.isDef(options.filter) ? options.filter : + goog.functions.TRUE; -/** - * Abstract base class for an XmlHttpRequest factory. - * @constructor - */ -goog.net.XmlHttpFactory = function() { -}; + var layerFilter; + if (goog.isDef(options.layers)) { + if (goog.isFunction(options.layers)) { + layerFilter = options.layers; + } else { + var layers = options.layers; + layerFilter = + /** + * @param {ol.layer.Layer} layer Layer. + * @return {boolean} Include. + */ + function(layer) { + return goog.array.contains(layers, layer); + }; + } + } else { + layerFilter = goog.functions.TRUE; + } + /** + * @private + * @type {function(ol.layer.Layer): boolean} + */ + this.layerFilter_ = layerFilter; -/** - * Cache of options - we only actually call internalGetOptions once. - * @type {Object} - * @private - */ -goog.net.XmlHttpFactory.prototype.cachedOptions_ = null; + /** + * @private + * @type {ol.layer.Vector} + */ + this.featureOverlay_ = new ol.layer.Vector({ + source: new ol.source.Vector({ + useSpatialIndex: false, + wrapX: options.wrapX + }), + style: goog.isDef(options.style) ? options.style : + ol.interaction.Select.getDefaultStyleFunction(), + updateWhileAnimating: true, + updateWhileInteracting: true + }); + var features = this.featureOverlay_.getSource().getFeaturesCollection(); + goog.events.listen(features, ol.CollectionEventType.ADD, + this.addFeature_, false, this); + goog.events.listen(features, ol.CollectionEventType.REMOVE, + this.removeFeature_, false, this); -/** - * @return {!goog.net.XhrLike.OrNative} A new XhrLike instance. - */ -goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod; +}; +goog.inherits(ol.interaction.Select, ol.interaction.Interaction); /** - * @return {Object} Options describing how xhr objects obtained from this - * factory should be used. + * Get the selected features. + * @return {ol.Collection.<ol.Feature>} Features collection. + * @api stable */ -goog.net.XmlHttpFactory.prototype.getOptions = function() { - return this.cachedOptions_ || - (this.cachedOptions_ = this.internalGetOptions()); +ol.interaction.Select.prototype.getFeatures = function() { + return this.featureOverlay_.getSource().getFeaturesCollection(); }; /** - * Override this method in subclasses to preserve the caching offered by - * getOptions(). - * @return {Object} Options describing how xhr objects obtained from this - * factory should be used. - * @protected + * Handles the {@link ol.MapBrowserEvent map browser event} and may change the + * selected state of features. + * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @this {ol.interaction.Select} + * @api */ -goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod; +ol.interaction.Select.handleEvent = function(mapBrowserEvent) { + if (!this.condition_(mapBrowserEvent)) { + return true; + } + var add = this.addCondition_(mapBrowserEvent); + var remove = this.removeCondition_(mapBrowserEvent); + var toggle = this.toggleCondition_(mapBrowserEvent); + var set = !add && !remove && !toggle; + var map = mapBrowserEvent.map; + var features = this.featureOverlay_.getSource().getFeaturesCollection(); + var /** @type {!Array.<ol.Feature>} */ deselected = []; + var /** @type {!Array.<ol.Feature>} */ selected = []; + var change = false; + if (set) { + // Replace the currently selected feature(s) with the feature(s) at the + // pixel, or clear the selected feature(s) if there is no feature at + // the pixel. + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, + /** + * @param {ol.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + */ + function(feature, layer) { + if (this.filter_(feature, layer)) { + selected.push(feature); + return !this.multi_; + } + }, this, this.layerFilter_); + if (selected.length > 0 && features.getLength() == 1 && + features.item(0) == selected[0]) { + // No change + } else { + change = true; + if (features.getLength() !== 0) { + deselected = Array.prototype.concat(features.getArray()); + features.clear(); + } + features.extend(selected); + } + } else { + // Modify the currently selected feature(s). + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, + /** + * @param {ol.Feature} feature Feature. + * @param {ol.layer.Layer} layer Layer. + */ + function(feature, layer) { + var index = goog.array.indexOf(features.getArray(), feature); + if (index == -1) { + if (add || toggle) { + if (this.filter_(feature, layer)) { + selected.push(feature); + } + } + } else { + if (remove || toggle) { + deselected.push(feature); + } + } + }, this, this.layerFilter_); + var i; + for (i = deselected.length - 1; i >= 0; --i) { + features.remove(deselected[i]); + } + features.extend(selected); + if (selected.length > 0 || deselected.length > 0) { + change = true; + } + } + if (change) { + this.dispatchEvent( + new ol.SelectEvent(ol.SelectEventType.SELECT, selected, deselected, + mapBrowserEvent)); + } + return ol.events.condition.pointerMove(mapBrowserEvent); +}; -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. /** - * @fileoverview Implementation of XmlHttpFactory which allows construction from - * simple factory methods. - * @author dbk@google.com (David Barrett-Kahn) + * Remove the interaction from its current map, if any, and attach it to a new + * map, if any. Pass `null` to just remove the interaction from the current map. + * @param {ol.Map} map Map. + * @api stable */ - -goog.provide('goog.net.WrapperXmlHttpFactory'); - -/** @suppress {extraRequire} Typedef. */ -goog.require('goog.net.XhrLike'); -goog.require('goog.net.XmlHttpFactory'); - +ol.interaction.Select.prototype.setMap = function(map) { + var currentMap = this.getMap(); + var selectedFeatures = + this.featureOverlay_.getSource().getFeaturesCollection(); + if (!goog.isNull(currentMap)) { + selectedFeatures.forEach(currentMap.unskipFeature, currentMap); + } + goog.base(this, 'setMap', map); + this.featureOverlay_.setMap(map); + if (!goog.isNull(map)) { + selectedFeatures.forEach(map.skipFeature, map); + } +}; /** - * An xhr factory subclass which can be constructed using two factory methods. - * This exists partly to allow the preservation of goog.net.XmlHttp.setFactory() - * with an unchanged signature. - * @param {function():!goog.net.XhrLike.OrNative} xhrFactory - * A function which returns a new XHR object. - * @param {function():!Object} optionsFactory A function which returns the - * options associated with xhr objects from this factory. - * @extends {goog.net.XmlHttpFactory} - * @constructor - * @final + * @return {ol.style.StyleFunction} Styles. */ -goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) { - goog.net.XmlHttpFactory.call(this); - - /** - * XHR factory method. - * @type {function() : !goog.net.XhrLike.OrNative} - * @private - */ - this.xhrFactory_ = xhrFactory; - - /** - * Options factory method. - * @type {function() : !Object} - * @private - */ - this.optionsFactory_ = optionsFactory; -}; -goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory); - - -/** @override */ -goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() { - return this.xhrFactory_(); -}; - +ol.interaction.Select.getDefaultStyleFunction = function() { + var styles = ol.style.createDefaultEditingStyles(); + goog.array.extend(styles[ol.geom.GeometryType.POLYGON], + styles[ol.geom.GeometryType.LINE_STRING]); + goog.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION], + styles[ol.geom.GeometryType.LINE_STRING]); -/** @override */ -goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() { - return this.optionsFactory_(); + return function(feature, resolution) { + return styles[feature.getGeometry().getType()]; + }; }; -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - /** - * @fileoverview Low level handling of XMLHttpRequest. - * @author arv@google.com (Erik Arvidsson) - * @author dbk@google.com (David Barrett-Kahn) + * @param {ol.CollectionEvent} evt Event. + * @private */ - -goog.provide('goog.net.DefaultXmlHttpFactory'); -goog.provide('goog.net.XmlHttp'); -goog.provide('goog.net.XmlHttp.OptionType'); -goog.provide('goog.net.XmlHttp.ReadyState'); -goog.provide('goog.net.XmlHttpDefines'); - -goog.require('goog.asserts'); -goog.require('goog.net.WrapperXmlHttpFactory'); -goog.require('goog.net.XmlHttpFactory'); +ol.interaction.Select.prototype.addFeature_ = function(evt) { + var feature = evt.element; + var map = this.getMap(); + goog.asserts.assertInstanceof(feature, ol.Feature, + 'feature should be an ol.Feature'); + if (!goog.isNull(map)) { + map.skipFeature(feature); + } +}; /** - * Static class for creating XMLHttpRequest objects. - * @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object. + * @param {ol.CollectionEvent} evt Event. + * @private */ -goog.net.XmlHttp = function() { - return goog.net.XmlHttp.factory_.createInstance(); +ol.interaction.Select.prototype.removeFeature_ = function(evt) { + var feature = evt.element; + var map = this.getMap(); + goog.asserts.assertInstanceof(feature, ol.Feature, + 'feature should be an ol.Feature'); + if (!goog.isNull(map)) { + map.unskipFeature(feature); + } }; +goog.provide('ol.interaction.Snap'); +goog.provide('ol.interaction.SnapProperty'); -/** - * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to - * true bypasses the ActiveX probing code. - * NOTE(ruilopes): Due to the way JSCompiler works, this define *will not* strip - * out the ActiveX probing code from binaries. To achieve this, use - * {@code goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR} instead. - * TODO(ruilopes): Collapse both defines. - */ -goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false); - +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.object'); +goog.require('ol.Collection'); +goog.require('ol.CollectionEvent'); +goog.require('ol.CollectionEventType'); +goog.require('ol.Extent'); +goog.require('ol.Feature'); +goog.require('ol.Object'); +goog.require('ol.Observable'); +goog.require('ol.coordinate'); +goog.require('ol.extent'); +goog.require('ol.geom.Geometry'); +goog.require('ol.interaction.Pointer'); +goog.require('ol.source.Vector'); +goog.require('ol.source.VectorEvent'); +goog.require('ol.source.VectorEventType'); +goog.require('ol.structs.RBush'); -/** @const */ -goog.net.XmlHttpDefines = {}; /** - * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to - * true eliminates the ActiveX probing code. + * @classdesc + * Handles snapping of vector features while modifying or drawing them. The + * features can come from a {@link ol.source.Vector} or {@link ol.Collection} + * Any interaction object that allows the user to interact + * with the features using the mouse can benefit from the snapping, as long + * as it is added before. + * + * The snap interaction modifies map browser event `coordinate` and `pixel` + * properties to force the snap to occur to any interaction that them. + * + * Example: + * + * var snap = new ol.interaction.Snap({ + * source: source + * }); + * + * @constructor + * @extends {ol.interaction.Pointer} + * @param {olx.interaction.SnapOptions=} opt_options Options. + * @api */ -goog.define('goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR', false); +ol.interaction.Snap = function(opt_options) { + goog.base(this, { + handleEvent: ol.interaction.Snap.handleEvent_, + handleDownEvent: goog.functions.TRUE, + handleUpEvent: ol.interaction.Snap.handleUpEvent_ + }); -/** - * Gets the options to use with the XMLHttpRequest objects obtained using - * the static methods. - * @return {Object} The options. - */ -goog.net.XmlHttp.getOptions = function() { - return goog.net.XmlHttp.factory_.getOptions(); -}; + var options = goog.isDef(opt_options) ? opt_options : {}; + /** + * @type {ol.source.Vector} + * @private + */ + this.source_ = goog.isDef(options.source) ? options.source : null; -/** - * Type of options that an XmlHttp object can have. - * @enum {number} - */ -goog.net.XmlHttp.OptionType = { /** - * Whether a goog.nullFunction should be used to clear the onreadystatechange - * handler instead of null. + * @type {ol.Collection.<ol.Feature>} + * @private */ - USE_NULL_FUNCTION: 0, + this.features_ = goog.isDef(options.features) ? options.features : null; /** - * NOTE(user): In IE if send() errors on a *local* request the readystate - * is still changed to COMPLETE. We need to ignore it and allow the - * try/catch around send() to pick up the error. + * @type {Array.<goog.events.Key>} + * @private */ - LOCAL_REQUEST_ERROR: 1 -}; + this.featuresListenerKeys_ = []; + /** + * @type {Object.<number, goog.events.Key>} + * @private + */ + this.geometryChangeListenerKeys_ = {}; -/** - * Status constants for XMLHTTP, matches: - * http://msdn.microsoft.com/library/default.asp?url=/library/ - * en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp - * @enum {number} - */ -goog.net.XmlHttp.ReadyState = { /** - * Constant for when xmlhttprequest.readyState is uninitialized + * @type {Object.<number, goog.events.Key>} + * @private */ - UNINITIALIZED: 0, + this.geometryModifyListenerKeys_ = {}; /** - * Constant for when xmlhttprequest.readyState is loading. + * Extents are preserved so indexed segment can be quickly removed + * when its feature geometry changes + * @type {Object.<number, ol.Extent>} + * @private */ - LOADING: 1, + this.indexedFeaturesExtents_ = {}; /** - * Constant for when xmlhttprequest.readyState is loaded. + * If a feature geometry changes while a pointer drag|move event occurs, the + * feature doesn't get updated right away. It will be at the next 'pointerup' + * event fired. + * @type {Object.<number, ol.Feature>} + * @private */ - LOADED: 2, + this.pendingFeatures_ = {}; /** - * Constant for when xmlhttprequest.readyState is in an interactive state. + * Used for distance sorting in sortByDistance_ + * @type {ol.Coordinate} + * @private */ - INTERACTIVE: 3, + this.pixelCoordinate_ = null; /** - * Constant for when xmlhttprequest.readyState is completed + * @type {number} + * @private */ - COMPLETE: 4 + this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ? + options.pixelTolerance : 10; + + /** + * @type {function(ol.interaction.Snap.SegmentDataType, ol.interaction.Snap.SegmentDataType): number} + * @private + */ + this.sortByDistance_ = goog.bind(ol.interaction.Snap.sortByDistance, this); + + + /** + * Segment RTree for each layer + * @type {ol.structs.RBush.<ol.interaction.Snap.SegmentDataType>} + * @private + */ + this.rBush_ = new ol.structs.RBush(); + + + /** + * @const + * @private + * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)> } + */ + this.SEGMENT_WRITERS_ = { + 'Point': this.writePointGeometry_, + 'LineString': this.writeLineStringGeometry_, + 'LinearRing': this.writeLineStringGeometry_, + 'Polygon': this.writePolygonGeometry_, + 'MultiPoint': this.writeMultiPointGeometry_, + 'MultiLineString': this.writeMultiLineStringGeometry_, + 'MultiPolygon': this.writeMultiPolygonGeometry_, + 'GeometryCollection': this.writeGeometryCollectionGeometry_ + }; }; +goog.inherits(ol.interaction.Snap, ol.interaction.Pointer); /** - * The global factory instance for creating XMLHttpRequest objects. - * @type {goog.net.XmlHttpFactory} - * @private + * Add a feature to the collection of features that we may snap to. + * @param {ol.Feature} feature Feature. + * @param {boolean=} opt_listen Whether to listen to the geometry change or not + * Defaults to `true`. + * @api */ -goog.net.XmlHttp.factory_; +ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) { + var listen = goog.isDef(opt_listen) ? opt_listen : true; + var geometry = feature.getGeometry(); + var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()]; + if (goog.isDef(segmentWriter)) { + var feature_uid = goog.getUid(feature); + this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent( + ol.extent.createEmpty()); + segmentWriter.call(this, feature, geometry); + + if (listen) { + this.geometryModifyListenerKeys_[feature_uid] = geometry.on( + goog.events.EventType.CHANGE, + goog.bind(this.handleGeometryModify_, this, feature), + this); + this.geometryChangeListenerKeys_[feature_uid] = feature.on( + ol.Object.getChangeEventType(feature.getGeometryName()), + this.handleGeometryChange_, this); + } + } +}; /** - * Sets the factories for creating XMLHttpRequest objects and their options. - * @param {Function} factory The factory for XMLHttpRequest objects. - * @param {Function} optionsFactory The factory for options. - * @deprecated Use setGlobalFactory instead. + * @param {ol.Feature} feature Feature. + * @private */ -goog.net.XmlHttp.setFactory = function(factory, optionsFactory) { - goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory( - goog.asserts.assert(factory), - goog.asserts.assert(optionsFactory))); +ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) { + this.addFeature(feature); }; /** - * Sets the global factory object. - * @param {!goog.net.XmlHttpFactory} factory New global factory object. + * @param {ol.Feature} feature Feature. + * @private */ -goog.net.XmlHttp.setGlobalFactory = function(factory) { - goog.net.XmlHttp.factory_ = factory; +ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) { + this.removeFeature(feature); }; - /** - * Default factory to use when creating xhr objects. You probably shouldn't be - * instantiating this directly, but rather using it via goog.net.XmlHttp. - * @extends {goog.net.XmlHttpFactory} - * @constructor + * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} + * @private */ -goog.net.DefaultXmlHttpFactory = function() { - goog.net.XmlHttpFactory.call(this); +ol.interaction.Snap.prototype.getFeatures_ = function() { + var features; + if (!goog.isNull(this.features_)) { + features = this.features_; + } else if (!goog.isNull(this.source_)) { + features = this.source_.getFeatures(); + } + goog.asserts.assert(goog.isDef(features), 'features should be defined'); + return features; }; -goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory); -/** @override */ -goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() { - var progId = this.getProgId_(); - if (progId) { - return new ActiveXObject(progId); - } else { - return new XMLHttpRequest(); +/** + * @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) { + var feature; + if (evt instanceof ol.source.VectorEvent) { + feature = evt.feature; + } else if (evt instanceof ol.CollectionEvent) { + feature = evt.element; } + goog.asserts.assertInstanceof(feature, ol.Feature, + 'feature should be an ol.Feature'); + this.addFeature(feature); }; -/** @override */ -goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() { - var progId = this.getProgId_(); - var options = {}; - if (progId) { - options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true; - options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true; +/** + * @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event. + * @private + */ +ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) { + var feature; + if (evt instanceof ol.source.VectorEvent) { + feature = evt.feature; + } else if (evt instanceof ol.CollectionEvent) { + feature = evt.element; } - return options; + goog.asserts.assertInstanceof(feature, ol.Feature, + 'feature should be an ol.Feature'); + this.removeFeature(feature); }; /** - * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized. - * @type {string|undefined} + * @param {goog.events.Event} evt Event. * @private */ -goog.net.DefaultXmlHttpFactory.prototype.ieProgId_; +ol.interaction.Snap.prototype.handleGeometryChange_ = function(evt) { + var feature = evt.currentTarget; + goog.asserts.assertInstanceof(feature, ol.Feature); + this.removeFeature(feature, true); + this.addFeature(feature, true); +}; /** - * Initialize the private state used by other functions. - * @return {string} The ActiveX PROG ID string to use to create xhr's in IE. + * @param {ol.Feature} feature Feature which geometry was modified. + * @param {goog.events.Event} evt Event. * @private */ -goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() { - if (goog.net.XmlHttp.ASSUME_NATIVE_XHR || - goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) { - return ''; +ol.interaction.Snap.prototype.handleGeometryModify_ = function(feature, evt) { + if (this.handlingDownUpSequence) { + var uid = goog.getUid(feature); + if (!(uid in this.pendingFeatures_)) { + this.pendingFeatures_[uid] = feature; + } + } else { + this.updateFeature_(feature); } +}; - // The following blog post describes what PROG IDs to use to create the - // XMLHTTP object in Internet Explorer: - // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx - // However we do not (yet) fully trust that this will be OK for old versions - // of IE on Win9x so we therefore keep the last 2. - if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' && - typeof ActiveXObject != 'undefined') { - // Candidate Active X types. - var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', - 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; - for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) { - var candidate = ACTIVE_X_IDENTS[i]; - /** @preserveTry */ - try { - new ActiveXObject(candidate); - // NOTE(user): cannot assign progid and return candidate in one line - // because JSCompiler complaings: BUG 658126 - this.ieProgId_ = candidate; - return candidate; - } catch (e) { - // do nothing; try next choice + +/** + * Remove a feature from the collection of features that we may snap to. + * @param {ol.Feature} feature Feature + * @param {boolean=} opt_unlisten Whether to unlisten to the geometry change + * or not. Defaults to `true`. + * @api + */ +ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) { + var unlisten = goog.isDef(opt_unlisten) ? opt_unlisten : true; + var feature_uid = goog.getUid(feature); + var extent = this.indexedFeaturesExtents_[feature_uid]; + if (extent) { + var rBush = this.rBush_; + var i, nodesToRemove = []; + rBush.forEachInExtent(extent, function(node) { + if (feature === node.feature) { + nodesToRemove.push(node); } + }); + for (i = nodesToRemove.length - 1; i >= 0; --i) { + rBush.remove(nodesToRemove[i]); } + if (unlisten) { + ol.Observable.unByKey(this.geometryModifyListenerKeys_[feature_uid]); + delete this.geometryModifyListenerKeys_[feature_uid]; - // couldn't find any matches - throw Error('Could not create ActiveXObject. ActiveX might be disabled,' + - ' or MSXML might not be installed'); + ol.Observable.unByKey(this.geometryChangeListenerKeys_[feature_uid]); + delete this.geometryChangeListenerKeys_[feature_uid]; + } } - - return /** @type {string} */ (this.ieProgId_); }; -//Set the global factory to an instance of the default factory. -goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory()); - -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - /** - * @fileoverview Wrapper class for handling XmlHttpRequests. - * - * One off requests can be sent through goog.net.XhrIo.send() or an - * instance can be created to send multiple requests. Each request uses its - * own XmlHttpRequest object and handles clearing of the event callback to - * ensure no leaks. - * - * XhrIo is event based, it dispatches events when a request finishes, fails or - * succeeds or when the ready-state changes. The ready-state or timeout event - * fires first, followed by a generic completed event. Then the abort, error, - * or success event is fired as appropriate. Lastly, the ready event will fire - * to indicate that the object may be used to make another request. - * - * The error event may also be called before completed and - * ready-state-change if the XmlHttpRequest.open() or .send() methods throw. - * - * This class does not support multiple requests, queuing, or prioritization. - * - * Tested = IE6, FF1.5, Safari, Opera 8.5 - * - * TODO(user): Error cases aren't playing nicely in Safari. - * + * @inheritDoc */ +ol.interaction.Snap.prototype.setMap = function(map) { + var currentMap = this.getMap(); + var keys = this.featuresListenerKeys_; + var features = this.getFeatures_(); + if (currentMap) { + goog.array.forEach(keys, ol.Observable.unByKey); + keys.length = 0; + features.forEach(this.forEachFeatureRemove_, this); + } -goog.provide('goog.net.XhrIo'); -goog.provide('goog.net.XhrIo.ResponseType'); - -goog.require('goog.Timer'); -goog.require('goog.array'); -goog.require('goog.debug.entryPointRegistry'); -goog.require('goog.events.EventTarget'); -goog.require('goog.json'); -goog.require('goog.log'); -goog.require('goog.net.ErrorCode'); -goog.require('goog.net.EventType'); -goog.require('goog.net.HttpStatus'); -goog.require('goog.net.XmlHttp'); -goog.require('goog.object'); -goog.require('goog.string'); -goog.require('goog.structs'); -goog.require('goog.structs.Map'); -goog.require('goog.uri.utils'); -goog.require('goog.userAgent'); - -goog.forwardDeclare('goog.Uri'); + goog.base(this, 'setMap', map); + if (map) { + if (!goog.isNull(this.features_)) { + keys.push(this.features_.on(ol.CollectionEventType.ADD, + this.handleFeatureAdd_, this)); + keys.push(this.features_.on(ol.CollectionEventType.REMOVE, + this.handleFeatureRemove_, this)); + } else if (!goog.isNull(this.source_)) { + keys.push(this.source_.on(ol.source.VectorEventType.ADDFEATURE, + this.handleFeatureAdd_, this)); + keys.push(this.source_.on(ol.source.VectorEventType.REMOVEFEATURE, + this.handleFeatureRemove_, this)); + } + features.forEach(this.forEachFeatureAdd_, this); + } +}; /** - * Basic class for handling XMLHttpRequests. - * @param {goog.net.XmlHttpFactory=} opt_xmlHttpFactory Factory to use when - * creating XMLHttpRequest objects. - * @constructor - * @extends {goog.events.EventTarget} + * @inheritDoc */ -goog.net.XhrIo = function(opt_xmlHttpFactory) { - goog.net.XhrIo.base(this, 'constructor'); - - /** - * Map of default headers to add to every request, use: - * XhrIo.headers.set(name, value) - * @type {!goog.structs.Map} - */ - this.headers = new goog.structs.Map(); - - /** - * Optional XmlHttpFactory - * @private {goog.net.XmlHttpFactory} - */ - this.xmlHttpFactory_ = opt_xmlHttpFactory || null; - - /** - * Whether XMLHttpRequest is active. A request is active from the time send() - * is called until onReadyStateChange() is complete, or error() or abort() - * is called. - * @private {boolean} - */ - this.active_ = false; +ol.interaction.Snap.prototype.shouldStopEvent = goog.functions.FALSE; - /** - * The XMLHttpRequest object that is being used for the transfer. - * @private {?goog.net.XhrLike.OrNative} - */ - this.xhr_ = null; - - /** - * The options to use with the current XMLHttpRequest object. - * @private {Object} - */ - this.xhrOptions_ = null; - - /** - * Last URL that was requested. - * @private {string|goog.Uri} - */ - this.lastUri_ = ''; - - /** - * Method for the last request. - * @private {string} - */ - this.lastMethod_ = ''; - /** - * Last error code. - * @private {!goog.net.ErrorCode} - */ - this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR; +/** + * @param {ol.Pixel} pixel Pixel + * @param {ol.Coordinate} pixelCoordinate Coordinate + * @param {ol.Map} map Map. + * @return {ol.interaction.Snap.ResultType} Snap result + */ +ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) { - /** - * Last error message. - * @private {Error|string} - */ - this.lastError_ = ''; + var lowerLeft = map.getCoordinateFromPixel( + [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]); + var upperRight = map.getCoordinateFromPixel( + [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]); + var box = ol.extent.boundingExtent([lowerLeft, upperRight]); - /** - * Used to ensure that we don't dispatch an multiple ERROR events. This can - * happen in IE when it does a synchronous load and one error is handled in - * the ready statte change and one is handled due to send() throwing an - * exception. - * @private {boolean} - */ - this.errorDispatched_ = false; + var segments = this.rBush_.getInExtent(box); + var snappedToVertex = false; + var snapped = false; + var vertex = null; + var vertexPixel = null; + if (segments.length > 0) { + this.pixelCoordinate_ = pixelCoordinate; + segments.sort(this.sortByDistance_); + var closestSegment = segments[0].segment; + vertex = (ol.coordinate.closestOnSegment(pixelCoordinate, + closestSegment)); + vertexPixel = map.getPixelFromCoordinate(vertex); + if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <= + this.pixelTolerance_) { + snapped = true; + var pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + var pixel2 = map.getPixelFromCoordinate(closestSegment[1]); + var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); + var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); + var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); + snappedToVertex = dist <= this.pixelTolerance_; + if (snappedToVertex) { + vertex = squaredDist1 > squaredDist2 ? + closestSegment[1] : closestSegment[0]; + vertexPixel = map.getPixelFromCoordinate(vertex); + vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])]; + } + } + } + return /** @type {ol.interaction.Snap.ResultType} */ ({ + snapped: snapped, + vertex: vertex, + vertexPixel: vertexPixel + }); +}; - /** - * Used to make sure we don't fire the complete event from inside a send call. - * @private {boolean} - */ - this.inSend_ = false; - /** - * Used in determining if a call to {@link #onReadyStateChange_} is from - * within a call to this.xhr_.open. - * @private {boolean} - */ - this.inOpen_ = false; +/** + * @param {ol.Feature} feature Feature + * @private + */ +ol.interaction.Snap.prototype.updateFeature_ = function(feature) { + this.removeFeature(feature, false); + this.addFeature(feature, false); +}; - /** - * Used in determining if a call to {@link #onReadyStateChange_} is from - * within a call to this.xhr_.abort. - * @private {boolean} - */ - this.inAbort_ = false; - /** - * Number of milliseconds after which an incomplete request will be aborted - * and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no timeout - * is set. - * @private {number} - */ - this.timeoutInterval_ = 0; +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.GeometryCollection} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ = + function(feature, geometry) { + var i, geometries = geometry.getGeometriesArray(); + for (i = 0; i < geometries.length; ++i) { + this.SEGMENT_WRITERS_[geometries[i].getType()].call( + this, feature, geometries[i]); + } +}; - /** - * Timer to track request timeout. - * @private {?number} - */ - this.timeoutId_ = null; - /** - * The requested type for the response. The empty string means use the default - * XHR behavior. - * @private {goog.net.XhrIo.ResponseType} - */ - this.responseType_ = goog.net.XhrIo.ResponseType.DEFAULT; +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.LineString} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeLineStringGeometry_ = + function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var i, ii, segment, segmentData; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } +}; - /** - * Whether a "credentialed" request is to be sent (one that is aware of - * cookies and authentication). This is applicable only for cross-domain - * requests and more recent browsers that support this part of the HTTP Access - * Control standard. - * - * @see http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute - * - * @private {boolean} - */ - this.withCredentials_ = false; - /** - * True if we can use XMLHttpRequest's timeout directly. - * @private {boolean} - */ - this.useXhr2Timeout_ = false; +/** + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiLineString} geometry Geometry. + * @private + */ +ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ = + function(feature, geometry) { + var lines = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = lines.length; j < jj; ++j) { + coordinates = lines[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } }; -goog.inherits(goog.net.XhrIo, goog.events.EventTarget); /** - * Response types that may be requested for XMLHttpRequests. - * @enum {string} - * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetype-attribute + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPoint} geometry Geometry. + * @private */ -goog.net.XhrIo.ResponseType = { - DEFAULT: '', - TEXT: 'text', - DOCUMENT: 'document', - // Not supported as of Chrome 10.0.612.1 dev - BLOB: 'blob', - ARRAY_BUFFER: 'arraybuffer' +ol.interaction.Snap.prototype.writeMultiPointGeometry_ = + function(feature, geometry) { + var points = geometry.getCoordinates(); + var coordinates, i, ii, segmentData; + for (i = 0, ii = points.length; i < ii; ++i) { + coordinates = points[i]; + segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({ + feature: feature, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); + } }; /** - * A reference to the XhrIo logger - * @private {goog.debug.Logger} - * @const + * @param {ol.Feature} feature Feature + * @param {ol.geom.MultiPolygon} geometry Geometry. + * @private */ -goog.net.XhrIo.prototype.logger_ = - goog.log.getLogger('goog.net.XhrIo'); +ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ = + function(feature, geometry) { + var polygons = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData; + for (k = 0, kk = polygons.length; k < kk; ++k) { + rings = polygons[k]; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } + } +}; /** - * The Content-Type HTTP header name - * @type {string} + * @param {ol.Feature} feature Feature + * @param {ol.geom.Point} geometry Geometry. + * @private */ -goog.net.XhrIo.CONTENT_TYPE_HEADER = 'Content-Type'; +ol.interaction.Snap.prototype.writePointGeometry_ = + function(feature, geometry) { + var coordinates = geometry.getCoordinates(); + var segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({ + feature: feature, + segment: [coordinates, coordinates] + }); + this.rBush_.insert(geometry.getExtent(), segmentData); +}; /** - * The pattern matching the 'http' and 'https' URI schemes - * @type {!RegExp} + * @param {ol.Feature} feature Feature + * @param {ol.geom.Polygon} geometry Geometry. + * @private */ -goog.net.XhrIo.HTTP_SCHEME_PATTERN = /^https?$/i; +ol.interaction.Snap.prototype.writePolygonGeometry_ = + function(feature, geometry) { + var rings = geometry.getCoordinates(); + var coordinates, i, ii, j, jj, segment, segmentData; + for (j = 0, jj = rings.length; j < jj; ++j) { + coordinates = rings[j]; + for (i = 0, ii = coordinates.length - 1; i < ii; ++i) { + segment = coordinates.slice(i, i + 2); + segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({ + feature: feature, + segment: segment + }); + this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData); + } + } +}; /** - * The methods that typically come along with form data. We set different - * headers depending on whether the HTTP action is one of these. + * @typedef {{ + * snapped: {boolean}, + * vertex: (ol.Coordinate|null), + * vertexPixel: (ol.Pixel|null) + * }} */ -goog.net.XhrIo.METHODS_WITH_FORM_DATA = ['POST', 'PUT']; +ol.interaction.Snap.ResultType; /** - * The Content-Type HTTP header value for a url-encoded form - * @type {string} + * @typedef {{ + * feature: ol.Feature, + * segment: Array.<ol.Coordinate> + * }} */ -goog.net.XhrIo.FORM_CONTENT_TYPE = - 'application/x-www-form-urlencoded;charset=utf-8'; +ol.interaction.Snap.SegmentDataType; /** - * The XMLHttpRequest Level two timeout delay ms property name. - * - * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute - * - * @private {string} - * @const + * Handle all pointer events events. + * @param {ol.MapBrowserEvent} evt A move event. + * @return {boolean} Pass the event to other interactions. + * @this {ol.interaction.Snap} + * @private */ -goog.net.XhrIo.XHR2_TIMEOUT_ = 'timeout'; +ol.interaction.Snap.handleEvent_ = function(evt) { + var result = this.snapTo(evt.pixel, evt.coordinate, evt.map); + if (result.snapped) { + evt.coordinate = result.vertex.slice(0, 2); + evt.pixel = result.vertexPixel; + } + return ol.interaction.Pointer.handleEvent.call(this, evt); +}; /** - * The XMLHttpRequest Level two ontimeout handler property name. - * - * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute - * - * @private {string} - * @const + * @param {ol.MapBrowserPointerEvent} evt Event. + * @return {boolean} Stop drag sequence? + * @this {ol.interaction.Snap} + * @private */ -goog.net.XhrIo.XHR2_ON_TIMEOUT_ = 'ontimeout'; +ol.interaction.Snap.handleUpEvent_ = function(evt) { + var featuresToUpdate = goog.object.getValues(this.pendingFeatures_); + if (featuresToUpdate.length) { + goog.array.forEach(featuresToUpdate, this.updateFeature_, this); + this.pendingFeatures_ = {}; + } + return false; +}; /** - * All non-disposed instances of goog.net.XhrIo created - * by {@link goog.net.XhrIo.send} are in this Array. - * @see goog.net.XhrIo.cleanup - * @private {!Array<!goog.net.XhrIo>} + * Sort segments by distance, helper function + * @param {ol.interaction.Snap.SegmentDataType} a + * @param {ol.interaction.Snap.SegmentDataType} b + * @return {number} + * @this {ol.interaction.Snap} */ -goog.net.XhrIo.sendInstances_ = []; +ol.interaction.Snap.sortByDistance = function(a, b) { + return ol.coordinate.squaredDistanceToSegment( + this.pixelCoordinate_, a.segment) - + ol.coordinate.squaredDistanceToSegment( + this.pixelCoordinate_, b.segment); +}; + +goog.provide('ol.layer.Heatmap'); + +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.math'); +goog.require('goog.object'); +goog.require('ol.Object'); +goog.require('ol.dom'); +goog.require('ol.layer.Vector'); +goog.require('ol.render.EventType'); +goog.require('ol.style.Icon'); +goog.require('ol.style.Style'); /** - * Static send that creates a short lived instance of XhrIo to send the - * request. - * @see goog.net.XhrIo.cleanup - * @param {string|goog.Uri} url Uri to make request to. - * @param {?function(this:goog.net.XhrIo, ?)=} opt_callback Callback function - * for when request is complete. - * @param {string=} opt_method Send method, default: GET. - * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} - * opt_content Body data. - * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the - * request. - * @param {number=} opt_timeoutInterval Number of milliseconds after which an - * incomplete request will be aborted; 0 means no timeout is set. - * @param {boolean=} opt_withCredentials Whether to send credentials with the - * request. Default to false. See {@link goog.net.XhrIo#setWithCredentials}. - * @return {!goog.net.XhrIo} The sent XhrIo. + * @enum {string} */ -goog.net.XhrIo.send = function(url, opt_callback, opt_method, opt_content, - opt_headers, opt_timeoutInterval, - opt_withCredentials) { - var x = new goog.net.XhrIo(); - goog.net.XhrIo.sendInstances_.push(x); - if (opt_callback) { - x.listen(goog.net.EventType.COMPLETE, opt_callback); - } - x.listenOnce(goog.net.EventType.READY, x.cleanupSend_); - if (opt_timeoutInterval) { - x.setTimeoutInterval(opt_timeoutInterval); - } - if (opt_withCredentials) { - x.setWithCredentials(opt_withCredentials); - } - x.send(url, opt_method, opt_content, opt_headers); - return x; +ol.layer.HeatmapLayerProperty = { + BLUR: 'blur', + GRADIENT: 'gradient', + RADIUS: 'radius' }; + /** - * Disposes all non-disposed instances of goog.net.XhrIo created by - * {@link goog.net.XhrIo.send}. - * {@link goog.net.XhrIo.send} cleans up the goog.net.XhrIo instance - * it creates when the request completes or fails. However, if - * the request never completes, then the goog.net.XhrIo is not disposed. - * This can occur if the window is unloaded before the request completes. - * We could have {@link goog.net.XhrIo.send} return the goog.net.XhrIo - * it creates and make the client of {@link goog.net.XhrIo.send} be - * responsible for disposing it in this case. However, this makes things - * significantly more complicated for the client, and the whole point - * of {@link goog.net.XhrIo.send} is that it's simple and easy to use. - * Clients of {@link goog.net.XhrIo.send} should call - * {@link goog.net.XhrIo.cleanup} when doing final - * cleanup on window unload. + * @classdesc + * Layer for rendering vector data as a heatmap. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Vector} + * @fires ol.render.Event + * @param {olx.layer.HeatmapOptions=} opt_options Options. + * @api */ -goog.net.XhrIo.cleanup = function() { - var instances = goog.net.XhrIo.sendInstances_; - while (instances.length) { - instances.pop().dispose(); +ol.layer.Heatmap = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; + + var baseOptions = goog.object.clone(options); + + delete baseOptions.gradient; + delete baseOptions.radius; + delete baseOptions.blur; + delete baseOptions.shadow; + delete baseOptions.weight; + goog.base(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); + + /** + * @private + * @type {Uint8ClampedArray} + */ + this.gradient_ = null; + + /** + * @private + * @type {number} + */ + this.shadow_ = goog.isDef(options.shadow) ? options.shadow : 250; + + /** + * @private + * @type {string|undefined} + */ + this.circleImage_ = undefined; + + /** + * @private + * @type {Array.<Array.<ol.style.Style>>} + */ + this.styleCache_ = null; + + goog.events.listen(this, + ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.GRADIENT), + this.handleGradientChanged_, false, this); + + this.setGradient(goog.isDef(options.gradient) ? + options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT); + + this.setBlur(goog.isDef(options.blur) ? options.blur : 15); + + this.setRadius(goog.isDef(options.radius) ? options.radius : 8); + + goog.events.listen(this, [ + ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.BLUR), + ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.RADIUS) + ], this.handleStyleChanged_, false, this); + + this.handleStyleChanged_(); + + var weight = goog.isDef(options.weight) ? options.weight : 'weight'; + var weightFunction; + if (goog.isString(weight)) { + weightFunction = function(feature) { + return feature.get(weight); + }; + } else { + weightFunction = weight; } + goog.asserts.assert(goog.isFunction(weightFunction), + 'weightFunction should be a function'); + + this.setStyle(goog.bind(function(feature, resolution) { + goog.asserts.assert(!goog.isNull(this.styleCache_), + 'this.styleCache_ should not be null'); + goog.asserts.assert(goog.isDef(this.circleImage_), + 'this.circleImage_ should be defined'); + var weight = weightFunction(feature); + var opacity = goog.isDef(weight) ? goog.math.clamp(weight, 0, 1) : 1; + // cast to 8 bits + var index = (255 * opacity) | 0; + var style = this.styleCache_[index]; + if (!goog.isDef(style)) { + style = [ + new ol.style.Style({ + image: new ol.style.Icon({ + opacity: opacity, + src: this.circleImage_ + }) + }) + ]; + this.styleCache_[index] = style; + } + return style; + }, this)); + + // For performance reasons, don't sort the features before rendering. + // The render order is not relevant for a heatmap representation. + this.setRenderOrder(null); + + goog.events.listen(this, ol.render.EventType.RENDER, + this.handleRender_, false, this); + }; +goog.inherits(ol.layer.Heatmap, ol.layer.Vector); /** - * Installs exception protection for all entry point introduced by - * goog.net.XhrIo instances which are not protected by - * {@link goog.debug.ErrorHandler#protectWindowSetTimeout}, - * {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or - * {@link goog.events.protectBrowserEventEntryPoint}. - * - * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to - * protect the entry point(s). + * @const + * @type {Array.<string>} */ -goog.net.XhrIo.protectEntryPoints = function(errorHandler) { - goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = - errorHandler.protectEntryPoint( - goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_); -}; +ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00']; /** - * Disposes of the specified goog.net.XhrIo created by - * {@link goog.net.XhrIo.send} and removes it from - * {@link goog.net.XhrIo.pendingStaticSendInstances_}. + * @param {Array.<string>} colors + * @return {Uint8ClampedArray} * @private */ -goog.net.XhrIo.prototype.cleanupSend_ = function() { - this.dispose(); - goog.array.remove(goog.net.XhrIo.sendInstances_, this); +ol.layer.Heatmap.createGradient_ = function(colors) { + var width = 1; + var height = 256; + var context = ol.dom.createCanvasContext2D(width, height); + + var gradient = context.createLinearGradient(0, 0, width, height); + var step = 1 / (colors.length - 1); + for (var i = 0, ii = colors.length; i < ii; ++i) { + gradient.addColorStop(i * step, colors[i]); + } + + context.fillStyle = gradient; + context.fillRect(0, 0, width, height); + + return context.getImageData(0, 0, width, height).data; }; /** - * Returns the number of milliseconds after which an incomplete request will be - * aborted, or 0 if no timeout is set. - * @return {number} Timeout interval in milliseconds. + * @return {string} + * @private */ -goog.net.XhrIo.prototype.getTimeoutInterval = function() { - return this.timeoutInterval_; +ol.layer.Heatmap.prototype.createCircle_ = function() { + var radius = this.getRadius(); + var blur = this.getBlur(); + goog.asserts.assert(goog.isDef(radius) && goog.isDef(blur), + 'radius and blur should be defined'); + var halfSize = radius + blur + 1; + var size = 2 * halfSize; + var context = ol.dom.createCanvasContext2D(size, size); + context.shadowOffsetX = context.shadowOffsetY = this.shadow_; + context.shadowBlur = blur; + context.shadowColor = '#000'; + context.beginPath(); + var center = halfSize - this.shadow_; + context.arc(center, center, radius, 0, Math.PI * 2, true); + context.fill(); + return context.canvas.toDataURL(); }; /** - * Sets the number of milliseconds after which an incomplete request will be - * aborted and a {@link goog.net.EventType.TIMEOUT} event raised; 0 means no - * timeout is set. - * @param {number} ms Timeout interval in milliseconds; 0 means none. + * Return the blur size in pixels. + * @return {number} Blur size in pixels. + * @api + * @observable */ -goog.net.XhrIo.prototype.setTimeoutInterval = function(ms) { - this.timeoutInterval_ = Math.max(0, ms); +ol.layer.Heatmap.prototype.getBlur = function() { + return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.BLUR)); }; /** - * Sets the desired type for the response. At time of writing, this is only - * supported in very recent versions of WebKit (10.0.612.1 dev and later). - * - * If this is used, the response may only be accessed via {@link #getResponse}. - * - * @param {goog.net.XhrIo.ResponseType} type The desired type for the response. + * Return the gradient colors as array of strings. + * @return {Array.<string>} Colors. + * @api + * @observable */ -goog.net.XhrIo.prototype.setResponseType = function(type) { - this.responseType_ = type; +ol.layer.Heatmap.prototype.getGradient = function() { + return /** @type {Array.<string>} */ ( + this.get(ol.layer.HeatmapLayerProperty.GRADIENT)); }; /** - * Gets the desired type for the response. - * @return {goog.net.XhrIo.ResponseType} The desired type for the response. + * Return the size of the radius in pixels. + * @return {number} Radius size in pixel. + * @api + * @observable */ -goog.net.XhrIo.prototype.getResponseType = function() { - return this.responseType_; +ol.layer.Heatmap.prototype.getRadius = function() { + return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.RADIUS)); }; /** - * Sets whether a "credentialed" request that is aware of cookie and - * authentication information should be made. This option is only supported by - * browsers that support HTTP Access Control. As of this writing, this option - * is not supported in IE. - * - * @param {boolean} withCredentials Whether this should be a "credentialed" - * request. + * @private */ -goog.net.XhrIo.prototype.setWithCredentials = function(withCredentials) { - this.withCredentials_ = withCredentials; +ol.layer.Heatmap.prototype.handleGradientChanged_ = function() { + this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient()); }; /** - * Gets whether a "credentialed" request is to be sent. - * @return {boolean} The desired type for the response. + * @private */ -goog.net.XhrIo.prototype.getWithCredentials = function() { - return this.withCredentials_; +ol.layer.Heatmap.prototype.handleStyleChanged_ = function() { + this.circleImage_ = this.createCircle_(); + this.styleCache_ = new Array(256); + this.changed(); }; /** - * Instance send that actually uses XMLHttpRequest to make a server call. - * @param {string|goog.Uri} url Uri to make request to. - * @param {string=} opt_method Send method, default: GET. - * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} - * opt_content Body data. - * @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the - * request. + * @param {ol.render.Event} event Post compose event + * @private */ -goog.net.XhrIo.prototype.send = function(url, opt_method, opt_content, - opt_headers) { - if (this.xhr_) { - throw Error('[goog.net.XhrIo] Object is active with another request=' + - this.lastUri_ + '; newUri=' + url); - } - - var method = opt_method ? opt_method.toUpperCase() : 'GET'; - - this.lastUri_ = url; - this.lastError_ = ''; - this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR; - this.lastMethod_ = method; - this.errorDispatched_ = false; - this.active_ = true; - - // Use the factory to create the XHR object and options - this.xhr_ = this.createXhr(); - this.xhrOptions_ = this.xmlHttpFactory_ ? - this.xmlHttpFactory_.getOptions() : goog.net.XmlHttp.getOptions(); - - // Set up the onreadystatechange callback - this.xhr_.onreadystatechange = goog.bind(this.onReadyStateChange_, this); - - /** - * Try to open the XMLHttpRequest (always async), if an error occurs here it - * is generally permission denied - * @preserveTry - */ - try { - goog.log.fine(this.logger_, this.formatMsg_('Opening Xhr')); - this.inOpen_ = true; - this.xhr_.open(method, String(url), true); // Always async! - this.inOpen_ = false; - } catch (err) { - goog.log.fine(this.logger_, - this.formatMsg_('Error opening Xhr: ' + err.message)); - this.error_(goog.net.ErrorCode.EXCEPTION, err); - return; - } - - // We can't use null since this won't allow requests with form data to have a - // content length specified which will cause some proxies to return a 411 - // error. - var content = opt_content || ''; - - var headers = this.headers.clone(); - - // Add headers specific to this request - if (opt_headers) { - goog.structs.forEach(opt_headers, function(value, key) { - headers.set(key, value); - }); - } - - // Find whether a content type header is set, ignoring case. - // HTTP header names are case-insensitive. See: - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - var contentTypeKey = goog.array.find(headers.getKeys(), - goog.net.XhrIo.isContentTypeHeader_); - - var contentIsFormData = (goog.global['FormData'] && - (content instanceof goog.global['FormData'])); - if (goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) && - !contentTypeKey && !contentIsFormData) { - // For requests typically with form data, default to the url-encoded form - // content type unless this is a FormData request. For FormData, - // the browser will automatically add a multipart/form-data content type - // with an appropriate multipart boundary. - headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER, - goog.net.XhrIo.FORM_CONTENT_TYPE); - } - - // Add the headers to the Xhr object - headers.forEach(function(value, key) { - this.xhr_.setRequestHeader(key, value); - }, this); - - if (this.responseType_) { - this.xhr_.responseType = this.responseType_; - } - - if (goog.object.containsKey(this.xhr_, 'withCredentials')) { - this.xhr_.withCredentials = this.withCredentials_; - } - - /** - * Try to send the request, or other wise report an error (404 not found). - * @preserveTry - */ - try { - this.cleanUpTimeoutTimer_(); // Paranoid, should never be running. - if (this.timeoutInterval_ > 0) { - this.useXhr2Timeout_ = goog.net.XhrIo.shouldUseXhr2Timeout_(this.xhr_); - goog.log.fine(this.logger_, this.formatMsg_('Will abort after ' + - this.timeoutInterval_ + 'ms if incomplete, xhr2 ' + - this.useXhr2Timeout_)); - if (this.useXhr2Timeout_) { - this.xhr_[goog.net.XhrIo.XHR2_TIMEOUT_] = this.timeoutInterval_; - this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = - goog.bind(this.timeout_, this); - } else { - this.timeoutId_ = goog.Timer.callOnce(this.timeout_, - this.timeoutInterval_, this); - } - } - goog.log.fine(this.logger_, this.formatMsg_('Sending request')); - this.inSend_ = true; - this.xhr_.send(content); - this.inSend_ = false; - - } catch (err) { - goog.log.fine(this.logger_, this.formatMsg_('Send error: ' + err.message)); - this.error_(goog.net.ErrorCode.EXCEPTION, err); +ol.layer.Heatmap.prototype.handleRender_ = function(event) { + goog.asserts.assert(event.type == ol.render.EventType.RENDER, + 'event.type should be RENDER'); + goog.asserts.assert(!goog.isNull(this.gradient_), + 'this.gradient_ should not be null'); + var context = event.context; + var canvas = context.canvas; + var image = context.getImageData(0, 0, canvas.width, canvas.height); + var view8 = image.data; + var i, ii, alpha, offset; + for (i = 0, ii = view8.length; i < ii; i += 4) { + alpha = view8[i + 3] * 4; + if (alpha) { + view8[i] = this.gradient_[alpha]; + view8[i + 1] = this.gradient_[alpha + 1]; + view8[i + 2] = this.gradient_[alpha + 2]; + } } + context.putImageData(image, 0, 0); }; /** - * Determines if the argument is an XMLHttpRequest that supports the level 2 - * timeout value and event. - * - * Currently, FF 21.0 OS X has the fields but won't actually call the timeout - * handler. Perhaps the confusion in the bug referenced below hasn't - * entirely been resolved. - * - * @see http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute - * @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816 - * - * @param {!goog.net.XhrLike.OrNative} xhr The request. - * @return {boolean} True if the request supports level 2 timeout. - * @private + * Set the blur size in pixels. + * @param {number} blur Blur size in pixels. + * @api + * @observable */ -goog.net.XhrIo.shouldUseXhr2Timeout_ = function(xhr) { - return goog.userAgent.IE && - goog.userAgent.isVersionOrHigher(9) && - goog.isNumber(xhr[goog.net.XhrIo.XHR2_TIMEOUT_]) && - goog.isDef(xhr[goog.net.XhrIo.XHR2_ON_TIMEOUT_]); +ol.layer.Heatmap.prototype.setBlur = function(blur) { + this.set(ol.layer.HeatmapLayerProperty.BLUR, blur); }; /** - * @param {string} header An HTTP header key. - * @return {boolean} Whether the key is a content type header (ignoring - * case. - * @private + * Set the gradient colors as array of strings. + * @param {Array.<string>} colors Gradient. + * @api + * @observable */ -goog.net.XhrIo.isContentTypeHeader_ = function(header) { - return goog.string.caseInsensitiveEquals( - goog.net.XhrIo.CONTENT_TYPE_HEADER, header); +ol.layer.Heatmap.prototype.setGradient = function(colors) { + this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors); }; /** - * Creates a new XHR object. - * @return {!goog.net.XhrLike.OrNative} The newly created XHR object. - * @protected + * Set the size of the radius in pixels. + * @param {number} radius Radius size in pixel. + * @api + * @observable */ -goog.net.XhrIo.prototype.createXhr = function() { - return this.xmlHttpFactory_ ? - this.xmlHttpFactory_.createInstance() : goog.net.XmlHttp(); +ol.layer.Heatmap.prototype.setRadius = function(radius) { + this.set(ol.layer.HeatmapLayerProperty.RADIUS, radius); }; +goog.provide('ol.raster.Operation'); +goog.provide('ol.raster.OperationType'); + /** - * The request didn't complete after {@link goog.net.XhrIo#timeoutInterval_} - * milliseconds; raises a {@link goog.net.EventType.TIMEOUT} event and aborts - * the request. - * @private + * Raster operation type. Supported values are `'pixel'` and `'image'`. + * @enum {string} + * @api */ -goog.net.XhrIo.prototype.timeout_ = function() { - if (typeof goog == 'undefined') { - // If goog is undefined then the callback has occurred as the application - // is unloading and will error. Thus we let it silently fail. - } else if (this.xhr_) { - this.lastError_ = 'Timed out after ' + this.timeoutInterval_ + - 'ms, aborting'; - this.lastErrorCode_ = goog.net.ErrorCode.TIMEOUT; - goog.log.fine(this.logger_, this.formatMsg_(this.lastError_)); - this.dispatchEvent(goog.net.EventType.TIMEOUT); - this.abort(goog.net.ErrorCode.TIMEOUT); - } +ol.raster.OperationType = { + PIXEL: 'pixel', + IMAGE: 'image' }; /** - * Something errorred, so inactivate, fire error callback and clean up - * @param {goog.net.ErrorCode} errorCode The error code. - * @param {Error} err The error object. - * @private + * A function that takes an array of input data, performs some operation, and + * returns an array of ouput data. For `'pixel'` type operations, functions + * will be called with an array of {@link ol.raster.Pixel} data and should + * return an array of the same. For `'image'` type operations, functions will + * be called with an array of {@link ImageData + * https://developer.mozilla.org/en-US/docs/Web/API/ImageData} and should return + * an array of the same. The operations are called with a second "data" + * argument, which can be used for storage. The data object is accessible + * from raster events, where it can be initialized in "beforeoperations" and + * accessed again in "afteroperations". + * + * @typedef {function((Array.<ol.raster.Pixel>|Array.<ImageData>), Object): + * (Array.<ol.raster.Pixel>|Array.<ImageData>)} + * @api */ -goog.net.XhrIo.prototype.error_ = function(errorCode, err) { - this.active_ = false; - if (this.xhr_) { - this.inAbort_ = true; - this.xhr_.abort(); // Ensures XHR isn't hung (FF) - this.inAbort_ = false; - } - this.lastError_ = err; - this.lastErrorCode_ = errorCode; - this.dispatchErrors_(); - this.cleanUpXhr_(); -}; +ol.raster.Operation; + +goog.provide('ol.raster.Pixel'); /** - * Dispatches COMPLETE and ERROR in case of an error. This ensures that we do - * not dispatch multiple error events. - * @private + * An array of numbers representing pixel values. + * @typedef {Array.<number>} ol.raster.Pixel + * @api */ -goog.net.XhrIo.prototype.dispatchErrors_ = function() { - if (!this.errorDispatched_) { - this.errorDispatched_ = true; - this.dispatchEvent(goog.net.EventType.COMPLETE); - this.dispatchEvent(goog.net.EventType.ERROR); - } -}; +ol.raster.Pixel; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. /** - * Abort the current XMLHttpRequest - * @param {goog.net.ErrorCode=} opt_failureCode Optional error code to use - - * defaults to ABORT. + * @fileoverview A utility to load JavaScript files via DOM script tags. + * Refactored from goog.net.Jsonp. Works cross-domain. + * */ -goog.net.XhrIo.prototype.abort = function(opt_failureCode) { - if (this.xhr_ && this.active_) { - goog.log.fine(this.logger_, this.formatMsg_('Aborting')); - this.active_ = false; - this.inAbort_ = true; - this.xhr_.abort(); - this.inAbort_ = false; - this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT; - this.dispatchEvent(goog.net.EventType.COMPLETE); - this.dispatchEvent(goog.net.EventType.ABORT); - this.cleanUpXhr_(); - } -}; + +goog.provide('goog.net.jsloader'); +goog.provide('goog.net.jsloader.Error'); +goog.provide('goog.net.jsloader.ErrorCode'); +goog.provide('goog.net.jsloader.Options'); + +goog.require('goog.array'); +goog.require('goog.async.Deferred'); +goog.require('goog.debug.Error'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); /** - * Nullifies all callbacks to reduce risks of leaks. - * @override - * @protected + * The name of the property of goog.global under which the JavaScript + * verification object is stored by the loaded script. + * @type {string} + * @private */ -goog.net.XhrIo.prototype.disposeInternal = function() { - if (this.xhr_) { - // We explicitly do not call xhr_.abort() unless active_ is still true. - // This is to avoid unnecessarily aborting a successful request when - // dispose() is called in a callback triggered by a complete response, but - // in which browser cleanup has not yet finished. - // (See http://b/issue?id=1684217.) - if (this.active_) { - this.active_ = false; - this.inAbort_ = true; - this.xhr_.abort(); - this.inAbort_ = false; - } - this.cleanUpXhr_(true); - } +goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification'; - goog.net.XhrIo.base(this, 'disposeInternal'); -}; + +/** + * The default length of time, in milliseconds, we are prepared to wait for a + * load request to complete. + * @type {number} + */ +goog.net.jsloader.DEFAULT_TIMEOUT = 5000; /** - * Internal handler for the XHR object's readystatechange event. This method - * checks the status and the readystate and fires the correct callbacks. - * If the request has ended, the handlers are cleaned up and the XHR object is - * nullified. + * Optional parameters for goog.net.jsloader.send. + * timeout: The length of time, in milliseconds, we are prepared to wait + * for a load request to complete. Default it 5 seconds. + * document: The HTML document under which to load the JavaScript. Default is + * the current document. + * cleanupWhenDone: If true clean up the script tag after script completes to + * load. This is important if you just want to read data from the JavaScript + * and then throw it away. Default is false. + * + * @typedef {{ + * timeout: (number|undefined), + * document: (HTMLDocument|undefined), + * cleanupWhenDone: (boolean|undefined) + * }} + */ +goog.net.jsloader.Options; + + +/** + * Scripts (URIs) waiting to be loaded. + * @type {Array<string>} * @private */ -goog.net.XhrIo.prototype.onReadyStateChange_ = function() { - if (this.isDisposed()) { - // This method is the target of an untracked goog.Timer.callOnce(). +goog.net.jsloader.scriptsToLoad_ = []; + + +/** + * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing + * the order of script loads. + * + * Because we have to load the scripts in serial (load script 1, exec script 1, + * load script 2, exec script 2, and so on), this will be slower than doing + * the network fetches in parallel. + * + * If you need to load a large number of scripts but dependency order doesn't + * matter, you should just call goog.net.jsloader.load N times. + * + * If you need to load a large number of scripts on the same domain, + * you may want to use goog.module.ModuleLoader. + * + * @param {Array<string>} uris The URIs to load. + * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See + * goog.net.jsloader.options documentation for details. + */ +goog.net.jsloader.loadMany = function(uris, opt_options) { + // Loading the scripts in serial introduces asynchronosity into the flow. + // Therefore, there are race conditions where client A can kick off the load + // sequence for client B, even though client A's scripts haven't all been + // loaded yet. + // + // To work around this issue, all module loads share a queue. + if (!uris.length) { return; } - if (!this.inOpen_ && !this.inSend_ && !this.inAbort_) { - // Were not being called from within a call to this.xhr_.send - // this.xhr_.abort, or this.xhr_.open, so this is an entry point - this.onReadyStateChangeEntryPoint_(); - } else { - this.onReadyStateChangeHelper_(); + + var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length; + goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris); + if (isAnotherModuleLoading) { + // jsloader is still loading some other scripts. + // In order to prevent the race condition noted above, we just add + // these URIs to the end of the scripts' queue and return. + return; } + + uris = goog.net.jsloader.scriptsToLoad_; + var popAndLoadNextScript = function() { + var uri = uris.shift(); + var deferred = goog.net.jsloader.load(uri, opt_options); + if (uris.length) { + deferred.addBoth(popAndLoadNextScript); + } + }; + popAndLoadNextScript(); }; /** - * Used to protect the onreadystatechange handler entry point. Necessary - * as {#onReadyStateChange_} maybe called from within send or abort, this - * method is only called when {#onReadyStateChange_} is called as an - * entry point. - * {@see #protectEntryPoints} - * @private + * Loads and evaluates a JavaScript file. + * When the script loads, a user callback is called. + * It is the client's responsibility to verify that the script ran successfully. + * + * @param {string} uri The URI of the JavaScript. + * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See + * goog.net.jsloader.Options documentation for details. + * @return {!goog.async.Deferred} The deferred result, that may be used to add + * callbacks and/or cancel the transmission. + * The error callback will be called with a single goog.net.jsloader.Error + * parameter. */ -goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = function() { - this.onReadyStateChangeHelper_(); +goog.net.jsloader.load = function(uri, opt_options) { + var options = opt_options || {}; + var doc = options.document || document; + + var script = goog.dom.createElement(goog.dom.TagName.SCRIPT); + var request = {script_: script, timeout_: undefined}; + var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request); + + // Set a timeout. + var timeout = null; + var timeoutDuration = goog.isDefAndNotNull(options.timeout) ? + options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT; + if (timeoutDuration > 0) { + timeout = window.setTimeout(function() { + goog.net.jsloader.cleanup_(script, true); + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.TIMEOUT, + 'Timeout reached for loading script ' + uri)); + }, timeoutDuration); + request.timeout_ = timeout; + } + + // Hang the user callback to be called when the script completes to load. + // NOTE(user): This callback will be called in IE even upon error. In any + // case it is the client's responsibility to verify that the script ran + // successfully. + script.onload = script.onreadystatechange = function() { + if (!script.readyState || script.readyState == 'loaded' || + script.readyState == 'complete') { + var removeScriptNode = options.cleanupWhenDone || false; + goog.net.jsloader.cleanup_(script, removeScriptNode, timeout); + deferred.callback(null); + } + }; + + // Add an error callback. + // NOTE(user): Not supported in IE. + script.onerror = function() { + goog.net.jsloader.cleanup_(script, true, timeout); + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.LOAD_ERROR, + 'Error while loading script ' + uri)); + }; + + // Add the script element to the document. + goog.dom.setProperties(script, { + 'type': 'text/javascript', + 'charset': 'UTF-8', + // NOTE(user): Safari never loads the script if we don't set + // the src attribute before appending. + 'src': uri + }); + var scriptParent = goog.net.jsloader.getScriptParentElement_(doc); + scriptParent.appendChild(script); + + return deferred; }; /** - * Helper for {@link #onReadyStateChange_}. This is used so that - * entry point calls to {@link #onReadyStateChange_} can be routed through - * {@link #onReadyStateChangeEntryPoint_}. - * @private + * Loads a JavaScript file and verifies it was evaluated successfully, using a + * verification object. + * The verification object is set by the loaded JavaScript at the end of the + * script. + * We verify this object was set and return its value in the success callback. + * If the object is not defined we trigger an error callback. + * + * @param {string} uri The URI of the JavaScript. + * @param {string} verificationObjName The name of the verification object that + * the loaded script should set. + * @param {goog.net.jsloader.Options} options Optional parameters. See + * goog.net.jsloader.Options documentation for details. + * @return {!goog.async.Deferred} The deferred result, that may be used to add + * callbacks and/or cancel the transmission. + * The success callback will be called with a single parameter containing + * the value of the verification object. + * The error callback will be called with a single goog.net.jsloader.Error + * parameter. */ -goog.net.XhrIo.prototype.onReadyStateChangeHelper_ = function() { - if (!this.active_) { - // can get called inside abort call - return; +goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) { + // Define the global objects variable. + if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) { + goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {}; } + var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]; - if (typeof goog == 'undefined') { - // NOTE(user): If goog is undefined then the callback has occurred as the - // application is unloading and will error. Thus we let it silently fail. + // Verify that the expected object does not exist yet. + if (goog.isDef(verifyObjs[verificationObjName])) { + // TODO(user): Error or reset variable? + return goog.async.Deferred.fail(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS, + 'Verification object ' + verificationObjName + ' already defined.')); + } - } else if ( - this.xhrOptions_[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] && - this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE && - this.getStatus() == 2) { - // NOTE(user): In IE if send() errors on a *local* request the readystate - // is still changed to COMPLETE. We need to ignore it and allow the - // try/catch around send() to pick up the error. - goog.log.fine(this.logger_, this.formatMsg_( - 'Local request error detected and ignored')); + // Send request to load the JavaScript. + var sendDeferred = goog.net.jsloader.load(uri, options); - } else { + // Create a deferred object wrapping the send result. + var deferred = new goog.async.Deferred( + goog.bind(sendDeferred.cancel, sendDeferred)); - // In IE when the response has been cached we sometimes get the callback - // from inside the send call and this usually breaks code that assumes that - // XhrIo is asynchronous. If that is the case we delay the callback - // using a timer. - if (this.inSend_ && - this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE) { - goog.Timer.callOnce(this.onReadyStateChange_, 0, this); - return; + // Call user back with object that was set by the script. + sendDeferred.addCallback(function() { + var result = verifyObjs[verificationObjName]; + if (goog.isDef(result)) { + deferred.callback(result); + delete verifyObjs[verificationObjName]; + } else { + // Error: script was not loaded properly. + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.VERIFY_ERROR, + 'Script ' + uri + ' loaded, but verification object ' + + verificationObjName + ' was not defined.')); } + }); - this.dispatchEvent(goog.net.EventType.READY_STATE_CHANGE); + // Pass error to new deferred object. + sendDeferred.addErrback(function(error) { + if (goog.isDef(verifyObjs[verificationObjName])) { + delete verifyObjs[verificationObjName]; + } + deferred.errback(error); + }); - // readyState indicates the transfer has finished - if (this.isComplete()) { - goog.log.fine(this.logger_, this.formatMsg_('Request complete')); + return deferred; +}; - this.active_ = false; - try { - // Call the specific callbacks for success or failure. Only call the - // success if the status is 200 (HTTP_OK) or 304 (HTTP_CACHED) - if (this.isSuccess()) { - this.dispatchEvent(goog.net.EventType.COMPLETE); - this.dispatchEvent(goog.net.EventType.SUCCESS); - } else { - this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR; - this.lastError_ = - this.getStatusText() + ' [' + this.getStatus() + ']'; - this.dispatchErrors_(); - } - } finally { - this.cleanUpXhr_(); - } - } +/** + * Gets the DOM element under which we should add new script elements. + * How? Take the first head element, and if not found take doc.documentElement, + * which always exists. + * + * @param {!HTMLDocument} doc The relevant document. + * @return {!Element} The script parent element. + * @private + */ +goog.net.jsloader.getScriptParentElement_ = function(doc) { + var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD); + if (!headElements || goog.array.isEmpty(headElements)) { + return doc.documentElement; + } else { + return headElements[0]; } }; /** - * Remove the listener to protect against leaks, and nullify the XMLHttpRequest - * object. - * @param {boolean=} opt_fromDispose If this is from the dispose (don't want to - * fire any events). + * Cancels a given request. + * @this {{script_: Element, timeout_: number}} The request context. * @private */ -goog.net.XhrIo.prototype.cleanUpXhr_ = function(opt_fromDispose) { - if (this.xhr_) { - // Cancel any pending timeout event handler. - this.cleanUpTimeoutTimer_(); +goog.net.jsloader.cancel_ = function() { + var request = this; + if (request && request.script_) { + var scriptNode = request.script_; + if (scriptNode && scriptNode.tagName == 'SCRIPT') { + goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_); + } + } +}; - // Save reference so we can mark it as closed after the READY event. The - // READY event may trigger another request, thus we must nullify this.xhr_ - var xhr = this.xhr_; - var clearedOnReadyStateChange = - this.xhrOptions_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] ? - goog.nullFunction : null; - this.xhr_ = null; - this.xhrOptions_ = null; - if (!opt_fromDispose) { - this.dispatchEvent(goog.net.EventType.READY); - } +/** + * Removes the script node and the timeout. + * + * @param {Node} scriptNode The node to be cleaned up. + * @param {boolean} removeScriptNode If true completely remove the script node. + * @param {?number=} opt_timeout The timeout handler to cleanup. + * @private + */ +goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode, + opt_timeout) { + if (goog.isDefAndNotNull(opt_timeout)) { + goog.global.clearTimeout(opt_timeout); + } - try { - // NOTE(user): Not nullifying in FireFox can still leak if the callbacks - // are defined in the same scope as the instance of XhrIo. But, IE doesn't - // allow you to set the onreadystatechange to NULL so nullFunction is - // used. - xhr.onreadystatechange = clearedOnReadyStateChange; - } catch (e) { - // This seems to occur with a Gears HTTP request. Delayed the setting of - // this onreadystatechange until after READY is sent out and catching the - // error to see if we can track down the problem. - goog.log.error(this.logger_, - 'Problem encountered resetting onreadystatechange: ' + e.message); - } + scriptNode.onload = goog.nullFunction; + scriptNode.onerror = goog.nullFunction; + scriptNode.onreadystatechange = goog.nullFunction; + + // Do this after a delay (removing the script node of a running script can + // confuse older IEs). + if (removeScriptNode) { + window.setTimeout(function() { + goog.dom.removeNode(scriptNode); + }, 0); } }; /** - * Make sure the timeout timer isn't running. - * @private + * Possible error codes for jsloader. + * @enum {number} */ -goog.net.XhrIo.prototype.cleanUpTimeoutTimer_ = function() { - if (this.xhr_ && this.useXhr2Timeout_) { - this.xhr_[goog.net.XhrIo.XHR2_ON_TIMEOUT_] = null; - } - if (goog.isNumber(this.timeoutId_)) { - goog.Timer.clear(this.timeoutId_); - this.timeoutId_ = null; - } +goog.net.jsloader.ErrorCode = { + LOAD_ERROR: 0, + TIMEOUT: 1, + VERIFY_ERROR: 2, + VERIFY_OBJECT_ALREADY_EXISTS: 3 }; + /** - * @return {boolean} Whether there is an active request. + * A jsloader error. + * + * @param {goog.net.jsloader.ErrorCode} code The error code. + * @param {string=} opt_message Additional message. + * @constructor + * @extends {goog.debug.Error} + * @final */ -goog.net.XhrIo.prototype.isActive = function() { - return !!this.xhr_; +goog.net.jsloader.Error = function(code, opt_message) { + var msg = 'Jsloader error (code #' + code + ')'; + if (opt_message) { + msg += ': ' + opt_message; + } + goog.net.jsloader.Error.base(this, 'constructor', msg); + + /** + * The code for this error. + * + * @type {goog.net.jsloader.ErrorCode} + */ + this.code = code; }; +goog.inherits(goog.net.jsloader.Error, goog.debug.Error); + +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// The original file lives here: http://go/cross_domain_channel.js /** - * @return {boolean} Whether the request has completed. + * @fileoverview Implements a cross-domain communication channel. A + * typical web page is prevented by browser security from sending + * request, such as a XMLHttpRequest, to other servers than the ones + * from which it came. The Jsonp class provides a workaround by + * using dynamically generated script tags. Typical usage:. + * + * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet')); + * var payload = { 'foo': 1, 'bar': true }; + * jsonp.send(payload, function(reply) { alert(reply) }); + * + * This script works in all browsers that are currently supported by + * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+, + * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+. + * */ -goog.net.XhrIo.prototype.isComplete = function() { - return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE; -}; + +goog.provide('goog.net.Jsonp'); + +goog.require('goog.Uri'); +goog.require('goog.net.jsloader'); + +// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +// +// This class allows us (Google) to send data from non-Google and thus +// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return +// anything sensitive, such as session or cookie specific data. Return +// only data that you want parties external to Google to have. Also +// NEVER use this method to send data from web pages to untrusted +// servers, or redirects to unknown servers (www.google.com/cache, +// /q=xx&btnl, /url, www.googlepages.com, etc.) +// +// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + /** - * @return {boolean} Whether the request completed with a success. + * Creates a new cross domain channel that sends data to the specified + * host URL. By default, if no reply arrives within 5s, the channel + * assumes the call failed to complete successfully. + * + * @param {goog.Uri|string} uri The Uri of the server side code that receives + * data posted through this channel (e.g., + * "http://maps.google.com/maps/geo"). + * + * @param {string=} opt_callbackParamName The parameter name that is used to + * specify the callback. Defaults to "callback". + * + * @constructor + * @final */ -goog.net.XhrIo.prototype.isSuccess = function() { - var status = this.getStatus(); - // A zero status code is considered successful for local files. - return goog.net.HttpStatus.isSuccess(status) || - status === 0 && !this.isLastUriEffectiveSchemeHttp_(); +goog.net.Jsonp = function(uri, opt_callbackParamName) { + /** + * The uri_ object will be used to encode the payload that is sent to the + * server. + * @type {goog.Uri} + * @private + */ + this.uri_ = new goog.Uri(uri); + + /** + * This is the callback parameter name that is added to the uri. + * @type {string} + * @private + */ + this.callbackParamName_ = opt_callbackParamName ? + opt_callbackParamName : 'callback'; + + /** + * The length of time, in milliseconds, this channel is prepared + * to wait for for a request to complete. The default value is 5 seconds. + * @type {number} + * @private + */ + this.timeout_ = 5000; }; /** - * @return {boolean} whether the effective scheme of the last URI that was - * fetched was 'http' or 'https'. - * @private + * The name of the property of goog.global under which the callback is + * stored. */ -goog.net.XhrIo.prototype.isLastUriEffectiveSchemeHttp_ = function() { - var scheme = goog.uri.utils.getEffectiveScheme(String(this.lastUri_)); - return goog.net.XhrIo.HTTP_SCHEME_PATTERN.test(scheme); -}; +goog.net.Jsonp.CALLBACKS = '_callbacks_'; /** - * Get the readystate from the Xhr object - * Will only return correct result when called from the context of a callback - * @return {goog.net.XmlHttp.ReadyState} goog.net.XmlHttp.ReadyState.*. + * Used to generate unique callback IDs. The counter must be global because + * all channels share a common callback object. + * @private */ -goog.net.XhrIo.prototype.getReadyState = function() { - return this.xhr_ ? - /** @type {goog.net.XmlHttp.ReadyState} */ (this.xhr_.readyState) : - goog.net.XmlHttp.ReadyState.UNINITIALIZED; -}; +goog.net.Jsonp.scriptCounter_ = 0; /** - * Get the status from the Xhr object - * Will only return correct result when called from the context of a callback - * @return {number} Http status. + * Sets the length of time, in milliseconds, this channel is prepared + * to wait for for a request to complete. If the call is not competed + * within the set time span, it is assumed to have failed. To wait + * indefinitely for a request to complete set the timout to a negative + * number. + * + * @param {number} timeout The length of time before calls are + * interrupted. */ -goog.net.XhrIo.prototype.getStatus = function() { - /** - * IE doesn't like you checking status until the readystate is greater than 2 - * (i.e. it is receiving or complete). The try/catch is used for when the - * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_. - * @preserveTry - */ - try { - return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? - this.xhr_.status : -1; - } catch (e) { - return -1; - } +goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) { + this.timeout_ = timeout; }; /** - * Get the status text from the Xhr object - * Will only return correct result when called from the context of a callback - * @return {string} Status text. + * Returns the current timeout value, in milliseconds. + * + * @return {number} The timeout value. */ -goog.net.XhrIo.prototype.getStatusText = function() { - /** - * IE doesn't like you checking status until the readystate is greater than 2 - * (i.e. it is recieving or complete). The try/catch is used for when the - * page is unloading and an ERROR_NOT_AVAILABLE may occur when accessing xhr_. - * @preserveTry - */ - try { - return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? - this.xhr_.statusText : ''; - } catch (e) { - goog.log.fine(this.logger_, 'Can not get status: ' + e.message); - return ''; - } +goog.net.Jsonp.prototype.getRequestTimeout = function() { + return this.timeout_; }; /** - * Get the last Uri that was requested - * @return {string} Last Uri. + * Sends the given payload to the URL specified at the construction + * time. The reply is delivered to the given replyCallback. If the + * errorCallback is specified and the reply does not arrive within the + * timeout period set on this channel, the errorCallback is invoked + * with the original payload. + * + * If no reply callback is specified, then the response is expected to + * consist of calls to globally registered functions. No &callback= + * URL parameter will be sent in the request, and the script element + * will be cleaned up after the timeout. + * + * @param {Object=} opt_payload Name-value pairs. If given, these will be + * added as parameters to the supplied URI as GET parameters to the + * given server URI. + * + * @param {Function=} opt_replyCallback A function expecting one + * argument, called when the reply arrives, with the response data. + * + * @param {Function=} opt_errorCallback A function expecting one + * argument, called on timeout, with the payload (if given), otherwise + * null. + * + * @param {string=} opt_callbackParamValue Value to be used as the + * parameter value for the callback parameter (callbackParamName). + * To be used when the value needs to be fixed by the client for a + * particular request, to make use of the cached responses for the request. + * NOTE: If multiple requests are made with the same + * opt_callbackParamValue, only the last call will work whenever the + * response comes back. + * + * @return {!Object} A request descriptor that may be used to cancel this + * transmission, or null, if the message may not be cancelled. */ -goog.net.XhrIo.prototype.getLastUri = function() { - return String(this.lastUri_); +goog.net.Jsonp.prototype.send = function(opt_payload, + opt_replyCallback, + opt_errorCallback, + opt_callbackParamValue) { + + var payload = opt_payload || null; + + var id = opt_callbackParamValue || + '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) + + goog.now().toString(36); + + if (!goog.global[goog.net.Jsonp.CALLBACKS]) { + goog.global[goog.net.Jsonp.CALLBACKS] = {}; + } + + // Create a new Uri object onto which this payload will be added + var uri = this.uri_.clone(); + if (payload) { + goog.net.Jsonp.addPayloadToUri_(payload, uri); + } + + if (opt_replyCallback) { + var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback); + goog.global[goog.net.Jsonp.CALLBACKS][id] = reply; + + uri.setParameterValues(this.callbackParamName_, + goog.net.Jsonp.CALLBACKS + '.' + id); + } + + var deferred = goog.net.jsloader.load(uri.toString(), + {timeout: this.timeout_, cleanupWhenDone: true}); + var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback); + deferred.addErrback(error); + + return {id_: id, deferred_: deferred}; }; /** - * Get the response text from the Xhr object - * Will only return correct result when called from the context of a callback. - * @return {string} Result from the server, or '' if no result available. + * Cancels a given request. The request must be exactly the object returned by + * the send method. + * + * @param {Object} request The request object returned by the send method. */ -goog.net.XhrIo.prototype.getResponseText = function() { - /** @preserveTry */ - try { - return this.xhr_ ? this.xhr_.responseText : ''; - } catch (e) { - // http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute - // states that responseText should return '' (and responseXML null) - // when the state is not LOADING or DONE. Instead, IE can - // throw unexpected exceptions, for example when a request is aborted - // or no data is available yet. - goog.log.fine(this.logger_, 'Can not get responseText: ' + e.message); - return ''; +goog.net.Jsonp.prototype.cancel = function(request) { + if (request) { + if (request.deferred_) { + request.deferred_.cancel(); + } + if (request.id_) { + goog.net.Jsonp.cleanup_(request.id_, false); + } } }; /** - * Get the response body from the Xhr object. This property is only available - * in IE since version 7 according to MSDN: - * http://msdn.microsoft.com/en-us/library/ie/ms534368(v=vs.85).aspx - * Will only return correct result when called from the context of a callback. - * - * One option is to construct a VBArray from the returned object and convert - * it to a JavaScript array using the toArray method: - * {@code (new window['VBArray'](xhrIo.getResponseBody())).toArray()} - * This will result in an array of numbers in the range of [0..255] - * - * Another option is to use the VBScript CStr method to convert it into a - * string as outlined in http://stackoverflow.com/questions/1919972 + * Creates a timeout callback that calls the given timeoutCallback with the + * original payload. * - * @return {Object} Binary result from the server or null if not available. + * @param {string} id The id of the script node. + * @param {Object} payload The payload that was sent to the server. + * @param {Function=} opt_errorCallback The function called on timeout. + * @return {!Function} A zero argument function that handles callback duties. + * @private */ -goog.net.XhrIo.prototype.getResponseBody = function() { - /** @preserveTry */ - try { - if (this.xhr_ && 'responseBody' in this.xhr_) { - return this.xhr_['responseBody']; +goog.net.Jsonp.newErrorHandler_ = function(id, + payload, + opt_errorCallback) { + /** + * When we call across domains with a request, this function is the + * timeout handler. Once it's done executing the user-specified + * error-handler, it removes the script node and original function. + */ + return function() { + goog.net.Jsonp.cleanup_(id, false); + if (opt_errorCallback) { + opt_errorCallback(payload); } - } catch (e) { - // IE can throw unexpected exceptions, for example when a request is aborted - // or no data is yet available. - goog.log.fine(this.logger_, 'Can not get responseBody: ' + e.message); - } - return null; + }; }; /** - * Get the response XML from the Xhr object - * Will only return correct result when called from the context of a callback. - * @return {Document} The DOM Document representing the XML file, or null - * if no result available. + * Creates a reply callback that calls the given replyCallback with data + * returned by the server. + * + * @param {string} id The id of the script node. + * @param {Function} replyCallback The function called on reply. + * @return {!Function} A reply callback function. + * @private */ -goog.net.XhrIo.prototype.getResponseXml = function() { - /** @preserveTry */ - try { - return this.xhr_ ? this.xhr_.responseXML : null; - } catch (e) { - goog.log.fine(this.logger_, 'Can not get responseXML: ' + e.message); - return null; - } +goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) { + /** + * This function is the handler for the all-is-well response. It + * clears the error timeout handler, calls the user's handler, then + * removes the script node and itself. + * + * @param {...Object} var_args The response data sent from the server. + */ + var handler = function(var_args) { + goog.net.Jsonp.cleanup_(id, true); + replyCallback.apply(undefined, arguments); + }; + return handler; }; /** - * Get the response and evaluates it as JSON from the Xhr object - * Will only return correct result when called from the context of a callback - * @param {string=} opt_xssiPrefix Optional XSSI prefix string to use for - * stripping of the response before parsing. This needs to be set only if - * your backend server prepends the same prefix string to the JSON response. - * @return {Object|undefined} JavaScript object. + * Removes the script node and reply handler with the given id. + * + * @param {string} id The id of the script node to be removed. + * @param {boolean} deleteReplyHandler If true, delete the reply handler + * instead of setting it to nullFunction (if we know the callback could + * never be called again). + * @private */ -goog.net.XhrIo.prototype.getResponseJson = function(opt_xssiPrefix) { - if (!this.xhr_) { - return undefined; - } - - var responseText = this.xhr_.responseText; - if (opt_xssiPrefix && responseText.indexOf(opt_xssiPrefix) == 0) { - responseText = responseText.substring(opt_xssiPrefix.length); +goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) { + if (goog.global[goog.net.Jsonp.CALLBACKS][id]) { + if (deleteReplyHandler) { + delete goog.global[goog.net.Jsonp.CALLBACKS][id]; + } else { + // Removing the script tag doesn't necessarily prevent the script + // from firing, so we make the callback a noop. + goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction; + } } - - return goog.json.parse(responseText); }; /** - * Get the response as the type specificed by {@link #setResponseType}. At time - * of writing, this is only directly supported in very recent versions of WebKit - * (10.0.612.1 dev and later). If the field is not supported directly, we will - * try to emulate it. - * - * Emulating the response means following the rules laid out at - * http://www.w3.org/TR/XMLHttpRequest/#the-response-attribute + * Returns URL encoded payload. The payload should be a map of name-value + * pairs, in the form {"foo": 1, "bar": true, ...}. If the map is empty, + * the URI will be unchanged. * - * On browsers with no support for this (Chrome < 10, Firefox < 4, etc), only - * response types of DEFAULT or TEXT may be used, and the response returned will - * be the text response. + * <p>The method uses hasOwnProperty() to assure the properties are on the + * object, not on its prototype. * - * On browsers with Mozilla's draft support for array buffers (Firefox 4, 5), - * only response types of DEFAULT, TEXT, and ARRAY_BUFFER may be used, and the - * response returned will be either the text response or the Mozilla - * implementation of the array buffer response. + * @param {!Object} payload A map of value name pairs to be encoded. + * A value may be specified as an array, in which case a query parameter + * will be created for each value, e.g.: + * {"foo": [1,2]} will encode to "foo=1&foo=2". * - * On browsers will full support, any valid response type supported by the - * browser may be used, and the response provided by the browser will be - * returned. + * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs + * will be encoded. * - * @return {*} The response. + * @return {!goog.Uri} A reference to the Uri sent as a parameter. + * @private */ -goog.net.XhrIo.prototype.getResponse = function() { - /** @preserveTry */ - try { - if (!this.xhr_) { - return null; - } - if ('response' in this.xhr_) { - return this.xhr_.response; - } - switch (this.responseType_) { - case goog.net.XhrIo.ResponseType.DEFAULT: - case goog.net.XhrIo.ResponseType.TEXT: - return this.xhr_.responseText; - // DOCUMENT and BLOB don't need to be handled here because they are - // introduced in the same spec that adds the .response field, and would - // have been caught above. - // ARRAY_BUFFER needs an implementation for Firefox 4, where it was - // implemented using a draft spec rather than the final spec. - case goog.net.XhrIo.ResponseType.ARRAY_BUFFER: - if ('mozResponseArrayBuffer' in this.xhr_) { - return this.xhr_.mozResponseArrayBuffer; - } +goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) { + for (var name in payload) { + // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that + // case, we iterate over all properties as a very lame workaround. + if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) { + uri.setParameterValues(name, payload[name]); } - // Fell through to a response type that is not supported on this browser. - goog.log.error(this.logger_, - 'Response type ' + this.responseType_ + ' is not ' + - 'supported on this browser'); - return null; - } catch (e) { - goog.log.fine(this.logger_, 'Can not get response: ' + e.message); - return null; } + return uri; }; +// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +// +// This class allows us (Google) to send data from non-Google and thus +// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return +// anything sensitive, such as session or cookie specific data. Return +// only data that you want parties external to Google to have. Also +// NEVER use this method to send data from web pages to untrusted +// servers, or redirects to unknown servers (www.google.com/cache, +// /q=xx&btnl, /url, www.googlepages.com, etc.) +// +// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + +goog.provide('ol.TileUrlFunction'); +goog.provide('ol.TileUrlFunctionType'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('ol.TileCoord'); +goog.require('ol.tilecoord'); + + +/** + * {@link ol.source.Tile} sources use a function of this type to get the url + * that provides a tile for a given tile coordinate. + * + * This function takes an {@link ol.TileCoord} for the tile coordinate, a + * `{number}` representing the pixel ratio and an {@link ol.proj.Projection} for + * the projection as arguments and returns a `{string}` representing the tile + * URL, or undefined if no tile should be requested for the passed tile + * coordinate. + * + * @typedef {function(ol.TileCoord, number, + * ol.proj.Projection): (string|undefined)} + * @api + */ +ol.TileUrlFunctionType; + + /** - * Get the value of the response-header with the given name from the Xhr object - * Will only return correct result when called from the context of a callback - * and the request has completed - * @param {string} key The name of the response-header to retrieve. - * @return {string|undefined} The value of the response-header named key. + * @typedef {function(ol.TileCoord, ol.proj.Projection, ol.TileCoord=): + * ol.TileCoord} */ -goog.net.XhrIo.prototype.getResponseHeader = function(key) { - return this.xhr_ && this.isComplete() ? - this.xhr_.getResponseHeader(key) : undefined; -}; +ol.TileCoordTransformType; /** - * Gets the text of all the headers in the response. - * Will only return correct result when called from the context of a callback - * and the request has completed. - * @return {string} The value of the response headers or empty string. + * @param {string} template Template. + * @return {ol.TileUrlFunctionType} Tile URL function. */ -goog.net.XhrIo.prototype.getAllResponseHeaders = function() { - return this.xhr_ && this.isComplete() ? - this.xhr_.getAllResponseHeaders() : ''; +ol.TileUrlFunction.createFromTemplate = function(template) { + var zRegEx = /\{z\}/g; + var xRegEx = /\{x\}/g; + var yRegEx = /\{y\}/g; + var dashYRegEx = /\{-y\}/g; + return ( + /** + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (goog.isNull(tileCoord)) { + return undefined; + } else { + return template.replace(zRegEx, tileCoord[0].toString()) + .replace(xRegEx, tileCoord[1].toString()) + .replace(yRegEx, function() { + var y = -tileCoord[2] - 1; + return y.toString(); + }) + .replace(dashYRegEx, function() { + var y = (1 << tileCoord[0]) + tileCoord[2]; + return y.toString(); + }); + } + }); }; /** - * Returns all response headers as a key-value map. - * Multiple values for the same header key can be combined into one, - * separated by a comma and a space. - * Note that the native getResponseHeader method for retrieving a single header - * does a case insensitive match on the header name. This method does not - * include any case normalization logic, it will just return a key-value - * representation of the headers. - * See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method - * @return {!Object<string, string>} An object with the header keys as keys - * and header values as values. + * @param {Array.<string>} templates Templates. + * @return {ol.TileUrlFunctionType} Tile URL function. */ -goog.net.XhrIo.prototype.getResponseHeaders = function() { - var headersObject = {}; - var headersArray = this.getAllResponseHeaders().split('\r\n'); - for (var i = 0; i < headersArray.length; i++) { - if (goog.string.isEmptyOrWhitespace(headersArray[i])) { - continue; - } - var keyValue = goog.string.splitLimit(headersArray[i], ': ', 2); - if (headersObject[keyValue[0]]) { - headersObject[keyValue[0]] += ', ' + keyValue[1]; - } else { - headersObject[keyValue[0]] = keyValue[1]; - } - } - return headersObject; +ol.TileUrlFunction.createFromTemplates = function(templates) { + return ol.TileUrlFunction.createFromTileUrlFunctions( + goog.array.map(templates, ol.TileUrlFunction.createFromTemplate)); }; /** - * Get the last error message - * @return {goog.net.ErrorCode} Last error code. + * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions. + * @return {ol.TileUrlFunctionType} Tile URL function. */ -goog.net.XhrIo.prototype.getLastErrorCode = function() { - return this.lastErrorCode_; +ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) { + goog.asserts.assert(tileUrlFunctions.length > 0, + 'Length of tile url functions should be greater than 0'); + if (tileUrlFunctions.length === 1) { + return tileUrlFunctions[0]; + } + return ( + /** + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + if (goog.isNull(tileCoord)) { + return undefined; + } else { + var h = ol.tilecoord.hash(tileCoord); + var index = goog.math.modulo(h, tileUrlFunctions.length); + return tileUrlFunctions[index](tileCoord, pixelRatio, projection); + } + }); }; /** - * Get the last error message - * @return {string} Last error message. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. */ -goog.net.XhrIo.prototype.getLastError = function() { - return goog.isString(this.lastError_) ? this.lastError_ : - String(this.lastError_); +ol.TileUrlFunction.nullTileUrlFunction = + function(tileCoord, pixelRatio, projection) { + return undefined; }; /** - * Adds the last method, status and URI to the message. This is used to add - * this information to the logging calls. - * @param {string} msg The message text that we want to add the extra text to. - * @return {string} The message with the extra text appended. - * @private + * @param {string} url URL. + * @return {Array.<string>} Array of urls. */ -goog.net.XhrIo.prototype.formatMsg_ = function(msg) { - return msg + ' [' + this.lastMethod_ + ' ' + this.lastUri_ + ' ' + - this.getStatus() + ']'; +ol.TileUrlFunction.expandUrl = function(url) { + var urls = []; + var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url); + if (match) { + var startCharCode = match[1].charCodeAt(0); + var stopCharCode = match[2].charCodeAt(0); + var charCode; + for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) { + urls.push(url.replace(match[0], String.fromCharCode(charCode))); + } + } else { + urls.push(url); + } + return urls; }; - -// Register the xhr handler as an entry point, so that -// it can be monitored for exception handling, etc. -goog.debug.entryPointRegistry.register( - /** - * @param {function(!Function): !Function} transformer The transforming - * function. - */ - function(transformer) { - goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = - transformer(goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_); - }); - -// FIXME consider delaying feature reading so projection can be provided by -// consumer (e.g. the view) - -goog.provide('ol.source.FormatVector'); +goog.provide('ol.source.TileImage'); goog.require('goog.asserts'); -goog.require('goog.dispose'); goog.require('goog.events'); -goog.require('goog.net.EventType'); -goog.require('goog.net.XhrIo'); -goog.require('goog.net.XhrIo.ResponseType'); -goog.require('goog.userAgent'); -goog.require('ol.format.FormatType'); -goog.require('ol.has'); -goog.require('ol.source.State'); -goog.require('ol.source.Vector'); -goog.require('ol.xml'); +goog.require('goog.events.EventType'); +goog.require('ol.ImageTile'); +goog.require('ol.TileCoord'); +goog.require('ol.TileLoadFunctionType'); +goog.require('ol.TileState'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.TileUrlFunctionType'); +goog.require('ol.source.Tile'); +goog.require('ol.source.TileEvent'); /** * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for vector sources in one of the supported formats. + * Base class for sources providing images divided into a tile grid. * * @constructor - * @extends {ol.source.Vector} - * @param {olx.source.FormatVectorOptions} options Options. + * @fires ol.source.TileEvent + * @extends {ol.source.Tile} + * @param {olx.source.TileImageOptions} options Image tile options. + * @api */ -ol.source.FormatVector = function(options) { +ol.source.TileImage = function(options) { goog.base(this, { attributions: options.attributions, + extent: options.extent, logo: options.logo, - projection: options.projection + opaque: options.opaque, + projection: options.projection, + state: goog.isDef(options.state) ? + /** @type {ol.source.State} */ (options.state) : undefined, + tileGrid: options.tileGrid, + tilePixelRatio: options.tilePixelRatio, + wrapX: options.wrapX }); /** * @protected - * @type {ol.format.Feature} + * @type {ol.TileUrlFunctionType} + */ + this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ? + options.tileUrlFunction : + ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @protected + * @type {?string} */ - this.format = options.format; + this.crossOrigin = + goog.isDef(options.crossOrigin) ? options.crossOrigin : null; -}; -goog.inherits(ol.source.FormatVector, ol.source.Vector); + /** + * @protected + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction = goog.isDef(options.tileLoadFunction) ? + options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction; + /** + * @protected + * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string, + * ?string, ol.TileLoadFunctionType)} + */ + this.tileClass = goog.isDef(options.tileClass) ? + options.tileClass : ol.ImageTile; -/** - * @param {goog.Uri|string} url URL. - * @param {function(this: T, Array.<ol.Feature>)} success Success Callback. - * @param {function(this: T)} error Error callback. - * @param {T} thisArg Value to use as `this` when executing `success` or - * `error`. - * @template T - */ -ol.source.FormatVector.prototype.loadFeaturesFromURL = - function(url, success, error, thisArg) { - var xhrIo = new goog.net.XhrIo(); - var type = this.format.getType(); - var responseType; - // FIXME maybe use ResponseType.DOCUMENT? - if (type == ol.format.FormatType.BINARY && - ol.has.ARRAY_BUFFER) { - responseType = goog.net.XhrIo.ResponseType.ARRAY_BUFFER; - } else { - responseType = goog.net.XhrIo.ResponseType.TEXT; - } - xhrIo.setResponseType(responseType); - goog.events.listen(xhrIo, goog.net.EventType.COMPLETE, - /** - * @param {Event} event Event. - * @private - * @this {ol.source.FormatVector} - */ - function(event) { - var xhrIo = event.target; - goog.asserts.assertInstanceof(xhrIo, goog.net.XhrIo); - if (xhrIo.isSuccess()) { - var type = this.format.getType(); - /** @type {ArrayBuffer|Document|Node|Object|string|undefined} */ - var source; - if (type == ol.format.FormatType.BINARY && - ol.has.ARRAY_BUFFER) { - source = xhrIo.getResponse(); - goog.asserts.assertInstanceof(source, ArrayBuffer); - } else if (type == ol.format.FormatType.JSON) { - source = xhrIo.getResponseText(); - } else if (type == ol.format.FormatType.TEXT) { - source = xhrIo.getResponseText(); - } else if (type == ol.format.FormatType.XML) { - if (!goog.userAgent.IE) { - source = xhrIo.getResponseXml(); - } - if (!goog.isDefAndNotNull(source)) { - source = ol.xml.parse(xhrIo.getResponseText()); - } - } else { - goog.asserts.fail(); - } - if (goog.isDefAndNotNull(source)) { - success.call(thisArg, this.readFeatures(source)); - } else { - this.setState(ol.source.State.ERROR); - goog.asserts.fail(); - } - } else { - error.call(thisArg); - } - goog.dispose(xhrIo); - }, false, this); - xhrIo.send(url); }; +goog.inherits(ol.source.TileImage, ol.source.Tile); /** - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {Array.<ol.Feature>} Features. - * @api + * @param {ol.ImageTile} imageTile Image tile. + * @param {string} src Source. */ -ol.source.FormatVector.prototype.readFeatures = function(source) { - var format = this.format; - var projection = this.getProjection(); - return format.readFeatures(source, {featureProjection: projection}); +ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) { + imageTile.getImage().src = src; }; -goog.provide('ol.source.StaticVector'); - -goog.require('ol.source.FormatVector'); -goog.require('ol.source.State'); - - /** - * @classdesc - * A vector source that uses one of the supported formats to read the data from - * a file or other static source. - * - * @constructor - * @extends {ol.source.FormatVector} - * @fires ol.source.VectorEvent - * @param {olx.source.StaticVectorOptions} options Options. - * @api + * @inheritDoc */ -ol.source.StaticVector = function(options) { - - goog.base(this, { - attributions: options.attributions, - format: options.format, - logo: options.logo, - projection: options.projection - }); - - if (goog.isDef(options.arrayBuffer)) { - this.addFeaturesInternal(this.readFeatures(options.arrayBuffer)); - } - - if (goog.isDef(options.doc)) { - this.addFeaturesInternal(this.readFeatures(options.doc)); - } - - if (goog.isDef(options.node)) { - this.addFeaturesInternal(this.readFeatures(options.node)); - } - - if (goog.isDef(options.object)) { - this.addFeaturesInternal(this.readFeatures(options.object)); - } +ol.source.TileImage.prototype.getTile = + function(z, x, y, pixelRatio, projection) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + } else { + goog.asserts.assert(projection, 'argument projection is truthy'); + var tileCoord = [z, x, y]; + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var tileUrl = goog.isNull(urlTileCoord) ? undefined : + this.tileUrlFunction(urlTileCoord, pixelRatio, projection); + var tile = new this.tileClass( + tileCoord, + goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY, + goog.isDef(tileUrl) ? tileUrl : '', + this.crossOrigin, + this.tileLoadFunction); + goog.events.listen(tile, goog.events.EventType.CHANGE, + this.handleTileChange_, false, this); - if (goog.isDef(options.text)) { - this.addFeaturesInternal(this.readFeatures(options.text)); + this.tileCache.set(tileCoordKey, tile); + return tile; } +}; - if (goog.isDef(options.url) || goog.isDef(options.urls)) { - this.setState(ol.source.State.LOADING); - if (goog.isDef(options.url)) { - this.loadFeaturesFromURL(options.url, - this.onFeaturesLoadedSuccess_, this.onFeaturesLoadedError_, this); - } - if (goog.isDef(options.urls)) { - var urls = options.urls; - var i, ii; - for (i = 0, ii = urls.length; i < ii; ++i) { - this.loadFeaturesFromURL(urls[i], - this.onFeaturesLoadedSuccess_, this.onFeaturesLoadedError_, this); - } - } - } +/** + * Return the tile load function of the source. + * @return {ol.TileLoadFunctionType} TileLoadFunction + * @api + */ +ol.source.TileImage.prototype.getTileLoadFunction = function() { + return this.tileLoadFunction; }; -goog.inherits(ol.source.StaticVector, ol.source.FormatVector); /** - * @private + * Return the tile URL function of the source. + * @return {ol.TileUrlFunctionType} TileUrlFunction + * @api */ -ol.source.StaticVector.prototype.onFeaturesLoadedError_ = function() { - this.setState(ol.source.State.ERROR); +ol.source.TileImage.prototype.getTileUrlFunction = function() { + return this.tileUrlFunction; }; /** - * @param {Array.<ol.Feature>} features Features. + * Handle tile change events. + * @param {goog.events.Event} event Event. * @private */ -ol.source.StaticVector.prototype.onFeaturesLoadedSuccess_ = function(features) { - this.addFeaturesInternal(features); - this.setState(ol.source.State.READY); +ol.source.TileImage.prototype.handleTileChange_ = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + switch (tile.getState()) { + case ol.TileState.LOADING: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADSTART, tile)); + break; + case ol.TileState.LOADED: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADEND, tile)); + break; + case ol.TileState.ERROR: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADERROR, tile)); + break; + } }; -goog.provide('ol.source.GeoJSON'); - -goog.require('ol.format.GeoJSON'); -goog.require('ol.source.StaticVector'); - - /** - * @classdesc - * Static vector source in GeoJSON format - * - * @constructor - * @extends {ol.source.StaticVector} - * @fires ol.source.VectorEvent - * @param {olx.source.GeoJSONOptions=} opt_options Options. + * Set the tile load function of the source. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. * @api */ -ol.source.GeoJSON = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - format: new ol.format.GeoJSON({ - defaultDataProjection: options.defaultProjection - }), - logo: options.logo, - object: options.object, - projection: options.projection, - text: options.text, - url: options.url, - urls: options.urls - }); - +ol.source.TileImage.prototype.setTileLoadFunction = function(tileLoadFunction) { + this.tileCache.clear(); + this.tileLoadFunction = tileLoadFunction; + this.changed(); }; -goog.inherits(ol.source.GeoJSON, ol.source.StaticVector); - -goog.provide('ol.source.GPX'); - -goog.require('ol.format.GPX'); -goog.require('ol.source.StaticVector'); - /** - * @classdesc - * Static vector source in GPX format - * - * @constructor - * @extends {ol.source.StaticVector} - * @fires ol.source.VectorEvent - * @param {olx.source.GPXOptions=} opt_options Options. + * Set the tile URL function of the source. + * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. * @api */ -ol.source.GPX = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, { - attributions: options.attributions, - doc: options.doc, - extent: options.extent, - format: new ol.format.GPX(), - logo: options.logo, - node: options.node, - projection: options.projection, - text: options.text, - url: options.url, - urls: options.urls - }); - +ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) { + // FIXME It should be possible to be more intelligent and avoid clearing the + // FIXME cache. The tile URL function would need to be incorporated into the + // FIXME cache key somehow. + this.tileCache.clear(); + this.tileUrlFunction = tileUrlFunction; + this.changed(); }; -goog.inherits(ol.source.GPX, ol.source.StaticVector); - -goog.provide('ol.source.IGC'); - -goog.require('ol.format.IGC'); -goog.require('ol.source.StaticVector'); - /** - * @classdesc - * Static vector source in IGC format - * - * @constructor - * @extends {ol.source.StaticVector} - * @fires ol.source.VectorEvent - * @param {olx.source.IGCOptions=} opt_options Options. - * @api + * @inheritDoc */ -ol.source.IGC = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, { - format: new ol.format.IGC({ - altitudeMode: options.altitudeMode - }), - projection: options.projection, - text: options.text, - url: options.url, - urls: options.urls - }); - +ol.source.TileImage.prototype.useTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); + } }; -goog.inherits(ol.source.IGC, ol.source.StaticVector); -goog.provide('ol.source.Image'); +goog.provide('ol.source.BingMaps'); +goog.require('goog.Uri'); goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('goog.net.Jsonp'); goog.require('ol.Attribution'); -goog.require('ol.Extent'); -goog.require('ol.array'); -goog.require('ol.source.Source'); - - -/** - * @typedef {{attributions: (Array.<ol.Attribution>|undefined), - * extent: (null|ol.Extent|undefined), - * logo: (string|olx.LogoOptions|undefined), - * projection: ol.proj.ProjectionLike, - * resolutions: (Array.<number>|undefined), - * state: (ol.source.State|undefined)}} - */ -ol.source.ImageOptions; +goog.require('ol.TileRange'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.source.State'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilecoord'); /** * @classdesc - * Abstract base class; normally only used for creating subclasses and not - * instantiated in apps. - * Base class for sources providing a single image. + * Layer source for Bing Maps tile data. * * @constructor - * @extends {ol.source.Source} - * @param {ol.source.ImageOptions} options Single image source options. - * @api + * @extends {ol.source.TileImage} + * @param {olx.source.BingMapsOptions} options Bing Maps options. + * @api stable */ -ol.source.Image = function(options) { +ol.source.BingMaps = function(options) { goog.base(this, { - attributions: options.attributions, - extent: options.extent, - logo: options.logo, - projection: options.projection, - state: options.state + crossOrigin: 'anonymous', + opaque: true, + projection: ol.proj.get('EPSG:3857'), + state: ol.source.State.LOADING, + tileLoadFunction: options.tileLoadFunction, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true }); /** * @private - * @type {Array.<number>} + * @type {string} */ - this.resolutions_ = goog.isDef(options.resolutions) ? - options.resolutions : null; - goog.asserts.assert(goog.isNull(this.resolutions_) || - goog.array.isSorted(this.resolutions_, - function(a, b) { - return b - a; - }, true)); + this.culture_ = goog.isDef(options.culture) ? options.culture : 'en-us'; + + /** + * @private + * @type {number} + */ + this.maxZoom_ = goog.isDef(options.maxZoom) ? options.maxZoom : -1; + + var uri = new goog.Uri( + 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' + + options.imagerySet); + + var jsonp = new goog.net.Jsonp(uri, 'jsonp'); + jsonp.send({ + 'include': 'ImageryProviders', + 'uriScheme': 'https', + 'key': options.key + }, goog.bind(this.handleImageryMetadataResponse, this)); }; -goog.inherits(ol.source.Image, ol.source.Source); +goog.inherits(ol.source.BingMaps, ol.source.TileImage); /** - * @return {Array.<number>} Resolutions. + * The attribution containing a link to the Microsoft® Bing™ Maps Platform APIs’ + * Terms Of Use. + * @const + * @type {ol.Attribution} + * @api */ -ol.source.Image.prototype.getResolutions = function() { - return this.resolutions_; -}; +ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({ + html: '<a class="ol-attribution-bing-tos" ' + + 'href="http://www.microsoft.com/maps/product/terms.html">' + + 'Terms of Use</a>' +}); /** - * @protected - * @param {number} resolution Resolution. - * @return {number} Resolution. + * @param {BingMapsImageryMetadataResponse} response Response. */ -ol.source.Image.prototype.findNearestResolution = - function(resolution) { - if (!goog.isNull(this.resolutions_)) { - var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0); - resolution = this.resolutions_[idx]; +ol.source.BingMaps.prototype.handleImageryMetadataResponse = + function(response) { + + if (response.statusCode != 200 || + response.statusDescription != 'OK' || + response.authenticationResultCode != 'ValidCredentials' || + response.resourceSets.length != 1 || + response.resourceSets[0].resources.length != 1) { + this.setState(ol.source.State.ERROR); + return; } - return resolution; -}; + var brandLogoUri = response.brandLogoUri; + if (brandLogoUri.indexOf('https') == -1) { + brandLogoUri = brandLogoUri.replace('http', 'https'); + } + //var copyright = response.copyright; // FIXME do we need to display this? + var resource = response.resourceSets[0].resources[0]; + goog.asserts.assert(resource.imageWidth == resource.imageHeight, + 'resource has imageWidth equal to imageHeight, i.e. is square'); + var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_; + + var sourceProjection = this.getProjection(); + var extent = ol.tilegrid.extentFromProjection(sourceProjection); + var tileSize = resource.imageWidth == resource.imageHeight ? + resource.imageWidth : [resource.imageWidth, resource.imageHeight]; + var tileGrid = ol.tilegrid.createXYZ({ + extent: extent, + minZoom: resource.zoomMin, + maxZoom: maxZoom, + tileSize: tileSize + }); + this.tileGrid = tileGrid; + + var culture = this.culture_; + this.tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions( + goog.array.map( + resource.imageUrlSubdomains, + function(subdomain) { + var quadKeyTileCoord = [0, 0, 0]; + var imageUrl = resource.imageUrl + .replace('{subdomain}', subdomain) + .replace('{culture}', culture); + return ( + /** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function(tileCoord, pixelRatio, projection) { + goog.asserts.assert(ol.proj.equivalent( + projection, sourceProjection), + 'projections are equivalent'); + if (goog.isNull(tileCoord)) { + return undefined; + } else { + ol.tilecoord.createOrUpdate(tileCoord[0], tileCoord[1], + -tileCoord[2] - 1, quadKeyTileCoord); + return imageUrl.replace('{quadkey}', ol.tilecoord.quadKey( + quadKeyTileCoord)); + } + }); + })); + + if (resource.imageryProviders) { + var transform = ol.proj.getTransformFromProjections( + ol.proj.get('EPSG:4326'), this.getProjection()); + + var attributions = goog.array.map( + resource.imageryProviders, + function(imageryProvider) { + var html = imageryProvider.attribution; + /** @type {Object.<string, Array.<ol.TileRange>>} */ + var tileRanges = {}; + goog.array.forEach( + imageryProvider.coverageAreas, + function(coverageArea) { + var minZ = coverageArea.zoomMin; + var maxZ = Math.min(coverageArea.zoomMax, maxZoom); + var bbox = coverageArea.bbox; + var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]]; + var extent = ol.extent.applyTransform( + epsg4326Extent, transform); + var tileRange, z, zKey; + for (z = minZ; z <= maxZ; ++z) { + zKey = z.toString(); + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + if (zKey in tileRanges) { + tileRanges[zKey].push(tileRange); + } else { + tileRanges[zKey] = [tileRange]; + } + } + }); + return new ol.Attribution({html: html, tileRanges: tileRanges}); + }); + attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION); + this.setAttributions(attributions); + } -/** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {ol.ImageBase} Single image. - */ -ol.source.Image.prototype.getImage = goog.abstractMethod; + this.setLogo(brandLogoUri); + this.setState(ol.source.State.READY); -/** - * Default image load function for image sources that use ol.Image image - * instances. - * @param {ol.Image} image Image. - * @param {string} src Source. - */ -ol.source.Image.defaultImageLoadFunction = function(image, src) { - image.getImage().src = src; }; -goog.provide('ol.source.ImageCanvas'); +// FIXME keep cluster cache by resolution ? +// FIXME distance not respected because of the centroid -goog.require('ol.CanvasFunctionType'); -goog.require('ol.ImageCanvas'); +goog.provide('ol.source.Cluster'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events.EventType'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.coordinate'); goog.require('ol.extent'); -goog.require('ol.source.Image'); +goog.require('ol.geom.Point'); +goog.require('ol.source.Vector'); /** * @classdesc - * Base class for image sources where a canvas element is the image. + * Layer source to cluster vector data. * * @constructor - * @extends {ol.source.Image} - * @param {olx.source.ImageCanvasOptions} options + * @param {olx.source.ClusterOptions} options + * @extends {ol.source.Vector} * @api */ -ol.source.ImageCanvas = function(options) { - +ol.source.Cluster = function(options) { goog.base(this, { attributions: options.attributions, + extent: options.extent, logo: options.logo, - projection: options.projection, - resolutions: options.resolutions, - state: goog.isDef(options.state) ? - /** @type {ol.source.State} */ (options.state) : undefined + projection: options.projection }); /** + * @type {number|undefined} * @private - * @type {ol.CanvasFunctionType} */ - this.canvasFunction_ = options.canvasFunction; + this.resolution_ = undefined; /** + * @type {number} * @private - * @type {ol.ImageCanvas} */ - this.canvas_ = null; + this.distance_ = goog.isDef(options.distance) ? options.distance : 20; /** + * @type {Array.<ol.Feature>} * @private - * @type {number} */ - this.renderedRevision_ = 0; + this.features_ = []; /** + * @type {ol.source.Vector} * @private - * @type {number} */ - this.ratio_ = goog.isDef(options.ratio) ? - options.ratio : 1.5; + this.source_ = options.source; + this.source_.on(goog.events.EventType.CHANGE, + ol.source.Cluster.prototype.onSourceChange_, this); +}; +goog.inherits(ol.source.Cluster, ol.source.Vector); + + +/** + * Get a reference to the wrapped source. + * @return {ol.source.Vector} Source. + * @api + */ +ol.source.Cluster.prototype.getSource = function() { + return this.source_; }; -goog.inherits(ol.source.ImageCanvas, ol.source.Image); /** * @inheritDoc */ -ol.source.ImageCanvas.prototype.getImage = - function(extent, resolution, pixelRatio, projection) { - resolution = this.findNearestResolution(resolution); +ol.source.Cluster.prototype.loadFeatures = function(extent, resolution, + projection) { + if (resolution !== this.resolution_) { + this.clear(); + this.resolution_ = resolution; + this.source_.loadFeatures(extent, resolution, projection); + this.cluster_(); + this.addFeatures(this.features_); + } +}; - var canvas = this.canvas_; - if (!goog.isNull(canvas) && - this.renderedRevision_ == this.getRevision() && - canvas.getResolution() == resolution && - canvas.getPixelRatio() == pixelRatio && - ol.extent.containsExtent(canvas.getExtent(), extent)) { - return canvas; + +/** + * handle the source changing + * @private + */ +ol.source.Cluster.prototype.onSourceChange_ = function() { + this.clear(); + this.cluster_(); + this.addFeatures(this.features_); + this.changed(); +}; + + +/** + * @private + */ +ol.source.Cluster.prototype.cluster_ = function() { + if (!goog.isDef(this.resolution_)) { + return; } + this.features_.length = 0; + var extent = ol.extent.createEmpty(); + var mapDistance = this.distance_ * this.resolution_; + var features = this.source_.getFeatures(); - extent = extent.slice(); - ol.extent.scaleFromCenter(extent, this.ratio_); - var width = ol.extent.getWidth(extent) / resolution; - var height = ol.extent.getHeight(extent) / resolution; - var size = [width * pixelRatio, height * pixelRatio]; + /** + * @type {Object.<string, boolean>} + */ + var clustered = {}; - var canvasElement = this.canvasFunction_( - extent, resolution, pixelRatio, size, projection); - if (!goog.isNull(canvasElement)) { - canvas = new ol.ImageCanvas(extent, resolution, pixelRatio, - this.getAttributions(), canvasElement); + for (var i = 0, ii = features.length; i < ii; i++) { + var feature = features[i]; + if (!goog.object.containsKey(clustered, goog.getUid(feature).toString())) { + var geometry = feature.getGeometry(); + goog.asserts.assert(geometry instanceof ol.geom.Point, + 'feature geometry is a ol.geom.Point instance'); + var coordinates = geometry.getCoordinates(); + ol.extent.createOrUpdateFromCoordinate(coordinates, extent); + ol.extent.buffer(extent, mapDistance, extent); + + var neighbors = this.source_.getFeaturesInExtent(extent); + goog.asserts.assert(neighbors.length >= 1, 'at least one neighbor found'); + neighbors = goog.array.filter(neighbors, function(neighbor) { + var uid = goog.getUid(neighbor).toString(); + if (!goog.object.containsKey(clustered, uid)) { + clustered[uid] = true; + return true; + } else { + return false; + } + }); + this.features_.push(this.createCluster_(neighbors)); + } } - this.canvas_ = canvas; - this.renderedRevision_ = this.getRevision(); + goog.asserts.assert( + goog.object.getCount(clustered) == this.source_.getFeatures().length, + 'number of clustered equals number of features in the source'); +}; - return canvas; + +/** + * @param {Array.<ol.Feature>} features Features + * @return {ol.Feature} + * @private + */ +ol.source.Cluster.prototype.createCluster_ = function(features) { + var length = features.length; + var centroid = [0, 0]; + for (var i = 0; i < length; i++) { + var geometry = features[i].getGeometry(); + goog.asserts.assert(geometry instanceof ol.geom.Point, + 'feature geometry is a ol.geom.Point instance'); + var coordinates = geometry.getCoordinates(); + ol.coordinate.add(centroid, coordinates); + } + ol.coordinate.scale(centroid, 1 / length); + + var cluster = new ol.Feature(new ol.geom.Point(centroid)); + cluster.set('features', features); + return cluster; }; goog.provide('ol.source.ImageMapGuide'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('goog.uri.utils'); goog.require('ol.Image'); @@ -107947,6 +110948,7 @@ goog.require('ol.source.Image'); * Source for images from Mapguide servers * * @constructor + * @fires ol.source.ImageEvent * @extends {ol.source.Image} * @param {olx.source.ImageMapGuideOptions} options Options. * @api stable @@ -108082,6 +111084,8 @@ ol.source.ImageMapGuide.prototype.getImage = image = new ol.Image(extent, resolution, pixelRatio, this.getAttributions(), imageUrl, this.crossOrigin_, this.imageLoadFunction_); + goog.events.listen(image, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); } else { image = null; } @@ -108092,9 +111096,19 @@ ol.source.ImageMapGuide.prototype.getImage = }; +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageMapGuide.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + /** * @param {ol.Extent} extent The map extents. - * @param {ol.Size} size the viewport size. + * @param {ol.Size} size The viewport size. * @param {number} metersPerUnit The meters-per-unit value. * @param {number} dpi The display resolution. * @return {number} The computed map scale. @@ -108154,368 +111168,87 @@ ol.source.ImageMapGuide.prototype.getUrl = return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams); }; -goog.provide('ol.source.ImageStatic'); - -goog.require('goog.events'); -goog.require('ol.Image'); -goog.require('ol.extent'); -goog.require('ol.proj'); -goog.require('ol.source.Image'); - - - -/** - * @classdesc - * A layer source for displaying a single, static image. - * - * @constructor - * @extends {ol.source.Image} - * @param {olx.source.ImageStaticOptions} options Options. - * @api stable - */ -ol.source.ImageStatic = function(options) { - - var attributions = goog.isDef(options.attributions) ? - options.attributions : null; - - var imageExtent = options.imageExtent; - - var resolution, resolutions; - if (goog.isDef(options.imageSize)) { - resolution = ol.extent.getHeight(imageExtent) / options.imageSize[1]; - resolutions = [resolution]; - } - - var crossOrigin = goog.isDef(options.crossOrigin) ? - options.crossOrigin : null; - - var imageLoadFunction = goog.isDef(options.imageLoadFunction) ? - options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; - - goog.base(this, { - attributions: attributions, - logo: options.logo, - projection: ol.proj.get(options.projection), - resolutions: resolutions - }); - - /** - * @private - * @type {ol.Image} - */ - this.image_ = new ol.Image(imageExtent, resolution, 1, attributions, - options.url, crossOrigin, imageLoadFunction); - -}; -goog.inherits(ol.source.ImageStatic, ol.source.Image); - - -/** - * @inheritDoc - */ -ol.source.ImageStatic.prototype.getImage = - function(extent, resolution, pixelRatio, projection) { - if (ol.extent.intersects(extent, this.image_.getExtent())) { - return this.image_; - } - return null; -}; - -goog.provide('ol.source.ImageVector'); - -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('goog.vec.Mat4'); -goog.require('ol.dom'); -goog.require('ol.extent'); -goog.require('ol.render.canvas.ReplayGroup'); -goog.require('ol.renderer.vector'); -goog.require('ol.source.ImageCanvas'); -goog.require('ol.source.Vector'); -goog.require('ol.style.Style'); -goog.require('ol.vec.Mat4'); - - - -/** - * @classdesc - * An image source whose images are canvas elements into which vector features - * read from a vector source (`ol.source.Vector`) are drawn. An - * `ol.source.ImageVector` object is to be used as the `source` of an image - * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated, - * as opposed to being re-rendered, during animations and interactions. So, like - * any other image layer, an image layer configured with an - * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a - * vector layer, where vector features are re-drawn during animations and - * interactions. - * - * @constructor - * @extends {ol.source.ImageCanvas} - * @param {olx.source.ImageVectorOptions} options Options. - * @api - */ -ol.source.ImageVector = function(options) { - - /** - * @private - * @type {ol.source.Vector} - */ - this.source_ = options.source; - - /** - * @private - * @type {!goog.vec.Mat4.Number} - */ - this.transform_ = goog.vec.Mat4.createNumber(); - - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.canvasContext_ = ol.dom.createCanvasContext2D(); - - /** - * @private - * @type {ol.Size} - */ - this.canvasSize_ = [0, 0]; - - /** - * @private - * @type {ol.render.canvas.ReplayGroup} - */ - this.replayGroup_ = null; - - goog.base(this, { - attributions: options.attributions, - canvasFunction: goog.bind(this.canvasFunctionInternal_, this), - logo: options.logo, - projection: options.projection, - ratio: options.ratio, - resolutions: options.resolutions, - state: this.source_.getState() - }); - - /** - * User provided style. - * @type {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} - * @private - */ - this.style_ = null; - - /** - * Style function for use within the library. - * @type {ol.style.StyleFunction|undefined} - * @private - */ - this.styleFunction_ = undefined; - - this.setStyle(options.style); - - goog.events.listen(this.source_, goog.events.EventType.CHANGE, - this.handleSourceChange_, undefined, this); - -}; -goog.inherits(ol.source.ImageVector, ol.source.ImageCanvas); - - -/** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.Size} size Size. - * @param {ol.proj.Projection} projection Projection; - * @return {HTMLCanvasElement} Canvas element. - * @private - */ -ol.source.ImageVector.prototype.canvasFunctionInternal_ = - function(extent, resolution, pixelRatio, size, projection) { - - var replayGroup = new ol.render.canvas.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); - - this.source_.loadFeatures(extent, resolution, projection); - - var loading = false; - this.source_.forEachFeatureInExtentAtResolution(extent, resolution, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - loading = loading || - this.renderFeature_(feature, resolution, pixelRatio, replayGroup); - }, this); - replayGroup.finish(); - - if (loading) { - return null; - } - - if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) { - this.canvasContext_.canvas.width = size[0]; - this.canvasContext_.canvas.height = size[1]; - this.canvasSize_[0] = size[0]; - this.canvasSize_[1] = size[1]; - } else { - this.canvasContext_.clearRect(0, 0, size[0], size[1]); - } - - var transform = this.getTransform_(ol.extent.getCenter(extent), - resolution, pixelRatio, size); - replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {}); - - this.replayGroup_ = replayGroup; - - return this.canvasContext_.canvas; -}; - - -/** - * @inheritDoc - */ -ol.source.ImageVector.prototype.forEachFeatureAtPixel = function( - resolution, rotation, coordinate, skippedFeatureUids, callback) { - if (goog.isNull(this.replayGroup_)) { - return undefined; - } else { - /** @type {Object.<string, boolean>} */ - var features = {}; - return this.replayGroup_.forEachGeometryAtPixel( - resolution, 0, coordinate, skippedFeatureUids, - /** - * @param {ol.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - goog.asserts.assert(goog.isDef(feature)); - var key = goog.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback(feature); - } - }); - } -}; - /** - * @return {ol.source.Vector} Source. + * Set the image load function of the MapGuide source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. * @api */ -ol.source.ImageVector.prototype.getSource = function() { - return this.source_; -}; - - -/** - * Get the style for features. This returns whatever was passed to the `style` - * option at construction or to the `setStyle` method. - * @return {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction} - * Layer style. - * @api stable - */ -ol.source.ImageVector.prototype.getStyle = function() { - return this.style_; -}; - - -/** - * Get the style function. - * @return {ol.style.StyleFunction|undefined} Layer style function. - * @api stable - */ -ol.source.ImageVector.prototype.getStyleFunction = function() { - return this.styleFunction_; -}; - - -/** - * @param {ol.Coordinate} center Center. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.Size} size Size. - * @return {!goog.vec.Mat4.Number} Transform. - * @private - */ -ol.source.ImageVector.prototype.getTransform_ = - function(center, resolution, pixelRatio, size) { - return ol.vec.Mat4.makeTransform2D(this.transform_, - size[0] / 2, size[1] / 2, - pixelRatio / resolution, -pixelRatio / resolution, - 0, - -center[0], -center[1]); +ol.source.ImageMapGuide.prototype.setImageLoadFunction = function( + imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); }; +goog.provide('ol.source.ImageStatic'); + +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.Image'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.source.Image'); -/** - * Handle changes in image style state. - * @param {goog.events.Event} event Image style change event. - * @private - */ -ol.source.ImageVector.prototype.handleImageChange_ = - function(event) { - this.changed(); -}; /** - * @private + * @classdesc + * A layer source for displaying a single, static image. + * + * @constructor + * @extends {ol.source.Image} + * @param {olx.source.ImageStaticOptions} options Options. + * @api stable */ -ol.source.ImageVector.prototype.handleSourceChange_ = function() { - // setState will trigger a CHANGE event, so we always rely - // change events by calling setState. - this.setState(this.source_.getState()); -}; +ol.source.ImageStatic = function(options) { + var attributions = goog.isDef(options.attributions) ? + options.attributions : null; -/** - * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. - * @return {boolean} `true` if an image is loading. - * @private - */ -ol.source.ImageVector.prototype.renderFeature_ = - function(feature, resolution, pixelRatio, replayGroup) { - var styles; - if (goog.isDef(feature.getStyleFunction())) { - styles = feature.getStyleFunction().call(feature, resolution); - } else if (goog.isDef(this.styleFunction_)) { - styles = this.styleFunction_(feature, resolution); - } - if (!goog.isDefAndNotNull(styles)) { - return false; - } - var i, ii, loading = false; - for (i = 0, ii = styles.length; i < ii; ++i) { - loading = ol.renderer.vector.renderFeature( - replayGroup, feature, styles[i], - ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), - this.handleImageChange_, this) || loading; + var imageExtent = options.imageExtent; + + var resolution, resolutions; + if (goog.isDef(options.imageSize)) { + resolution = ol.extent.getHeight(imageExtent) / options.imageSize[1]; + resolutions = [resolution]; } - return loading; + + var crossOrigin = goog.isDef(options.crossOrigin) ? + options.crossOrigin : null; + + var imageLoadFunction = goog.isDef(options.imageLoadFunction) ? + options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction; + + goog.base(this, { + attributions: attributions, + logo: options.logo, + projection: ol.proj.get(options.projection), + resolutions: resolutions + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = new ol.Image(imageExtent, resolution, 1, attributions, + options.url, crossOrigin, imageLoadFunction); + goog.events.listen(this.image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + }; +goog.inherits(ol.source.ImageStatic, ol.source.Image); /** - * Set the style for features. This can be a single style object, an array - * of styles, or a function that takes a feature and resolution and returns - * an array of styles. If it is `undefined` the default style is used. If - * it is `null` the layer has no style (a `null` style), so only features - * that have their own styles will be rendered in the layer. See - * {@link ol.style} for information on the default style. - * @param {ol.style.Style|Array.<ol.style.Style>|ol.style.StyleFunction|undefined} - * style Layer style. - * @api stable + * @inheritDoc */ -ol.source.ImageVector.prototype.setStyle = function(style) { - this.style_ = goog.isDef(style) ? style : ol.style.defaultStyleFunction; - this.styleFunction_ = goog.isNull(style) ? - undefined : ol.style.createStyleFunction(this.style_); - this.changed(); +ol.source.ImageStatic.prototype.getImage = + function(extent, resolution, pixelRatio, projection) { + if (ol.extent.intersects(extent, this.image_.getExtent())) { + return this.image_; + } + return null; }; goog.provide('ol.source.wms'); @@ -108541,6 +111274,8 @@ ol.source.wms.ServerType = { goog.provide('ol.source.ImageWMS'); goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('goog.string'); goog.require('goog.uri.utils'); @@ -108560,6 +111295,7 @@ goog.require('ol.source.wms.ServerType'); * Source for WMS servers providing single, untiled images. * * @constructor + * @fires ol.source.ImageEvent * @extends {ol.source.Image} * @param {olx.source.ImageWMSOptions=} opt_options Options. * @api stable @@ -108674,7 +111410,8 @@ ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101]; ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) { - goog.asserts.assert(!('VERSION' in params)); + goog.asserts.assert(!('VERSION' in params), + 'key VERSION is not allowed in params'); if (!goog.isDef(this.url_)) { return undefined; @@ -108690,14 +111427,14 @@ ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = 'REQUEST': 'GetFeatureInfo', 'FORMAT': 'image/png', 'TRANSPARENT': true, - 'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS') + 'QUERY_LAYERS': this.params_['LAYERS'] }; goog.object.extend(baseParams, this.params_, params); var x = Math.floor((coordinate[0] - extent[0]) / resolution); var y = Math.floor((extent[3] - coordinate[1]) / resolution); - goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x); - goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y); + baseParams[this.v13_ ? 'I' : 'X'] = x; + baseParams[this.v13_ ? 'J' : 'Y'] = y; return this.getRequestUrl_( extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_, @@ -108785,11 +111522,24 @@ ol.source.ImageWMS.prototype.getImage = this.renderedRevision_ = this.getRevision(); + goog.events.listen(this.image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + return this.image_; }; +/** + * Return the image load function of the source. + * @return {ol.ImageLoadFunctionType} The image load function. + * @api + */ +ol.source.ImageWMS.prototype.getImageLoadFunction = function() { + return this.imageLoadFunction_; +}; + + /** * @param {ol.Extent} extent Extent. * @param {ol.Size} size Size. @@ -108802,13 +111552,13 @@ ol.source.ImageWMS.prototype.getImage = ol.source.ImageWMS.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) { - goog.asserts.assert(goog.isDef(this.url_)); + goog.asserts.assert(goog.isDef(this.url_), 'url is defined'); params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); if (!('STYLES' in this.params_)) { /* jshint -W053 */ - goog.object.set(params, 'STYLES', new String('')); + params['STYLES'] = new String(''); /* jshint +W053 */ } @@ -108816,23 +111566,27 @@ ol.source.ImageWMS.prototype.getRequestUrl_ = switch (this.serverType_) { case ol.source.wms.ServerType.GEOSERVER: var dpi = (90 * pixelRatio + 0.5) | 0; - goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi); + if (goog.isDef(params['FORMAT_OPTIONS'])) { + params['FORMAT_OPTIONS'] += ';dpi:' + dpi; + } else { + params['FORMAT_OPTIONS'] = 'dpi:' + dpi; + } break; case ol.source.wms.ServerType.MAPSERVER: - goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio); + params['MAP_RESOLUTION'] = 90 * pixelRatio; break; case ol.source.wms.ServerType.CARMENTA_SERVER: case ol.source.wms.ServerType.QGIS: - goog.object.set(params, 'DPI', 90 * pixelRatio); + params['DPI'] = 90 * pixelRatio; break; default: - goog.asserts.fail(); + goog.asserts.fail('unknown serverType configured'); break; } } - goog.object.set(params, 'WIDTH', size[0]); - goog.object.set(params, 'HEIGHT', size[1]); + params['WIDTH'] = size[0]; + params['HEIGHT'] = size[1]; var axisOrientation = projection.getAxisOrientation(); var bbox; @@ -108841,14 +111595,14 @@ ol.source.ImageWMS.prototype.getRequestUrl_ = } else { bbox = extent; } - goog.object.set(params, 'BBOX', bbox.join(',')); + params['BBOX'] = bbox.join(','); return goog.uri.utils.appendParamsFromMap(this.url_, params); }; /** - * Return the URL used for this WMS source. + * Return the URL used for this WMS source. * @return {string|undefined} URL. * @api stable */ @@ -108858,6 +111612,20 @@ ol.source.ImageWMS.prototype.getUrl = function() { /** + * Set the image load function of the source. + * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function. + * @api + */ +ol.source.ImageWMS.prototype.setImageLoadFunction = function( + imageLoadFunction) { + this.image_ = null; + this.imageLoadFunction_ = imageLoadFunction; + this.changed(); +}; + + +/** + * Set the URL to use for requests. * @param {string|undefined} url URL. * @api stable */ @@ -108892,57 +111660,30 @@ ol.source.ImageWMS.prototype.updateV13_ = function() { this.v13_ = goog.string.compareVersions(version, '1.3') >= 0; }; -goog.provide('ol.source.KML'); - -goog.require('ol.format.KML'); -goog.require('ol.source.StaticVector'); - - - -/** - * @classdesc - * Static vector source in KML format - * - * @constructor - * @extends {ol.source.StaticVector} - * @fires ol.source.VectorEvent - * @param {olx.source.KMLOptions=} opt_options Options. - * @api - */ -ol.source.KML = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, { - attributions: options.attributions, - doc: options.doc, - format: new ol.format.KML({ - extractStyles: options.extractStyles, - defaultStyle: options.defaultStyle - }), - logo: options.logo, - node: options.node, - projection: options.projection, - text: options.text, - url: options.url, - urls: options.urls - }); - -}; -goog.inherits(ol.source.KML, ol.source.StaticVector); - goog.provide('ol.source.XYZ'); goog.require('ol.Attribution'); goog.require('ol.TileUrlFunction'); goog.require('ol.source.TileImage'); -goog.require('ol.tilegrid.XYZ'); /** * @classdesc - * Layer source for tile data with URLs in a set XYZ format. + * Layer source for tile data with URLs in a set XYZ format that are + * defined in a URL template. By default, this follows the widely-used + * Google grid where `x` 0 and `y` 0 are in the top left. Grids like + * TMS where `x` 0 and `y` 0 are in the bottom left can be used by + * using the `{-y}` placeholder in the URL template, so long as the + * source does not have a custom tile grid. In this case, + * {@link ol.source.TileImage} can be used with a `tileUrlFunction` + * such as: + * + * tileUrlFunction: function(coordinate) { + * return 'http://mapserver.com/' + coordinate[0] + '/' + + * coordinate[1] + '/' + coordinate[2] + '.png'; + * } + * * * @constructor * @extends {ol.source.TileImage} @@ -108953,11 +111694,12 @@ ol.source.XYZ = function(options) { var projection = goog.isDef(options.projection) ? options.projection : 'EPSG:3857'; - var tileGrid = new ol.tilegrid.XYZ({ - extent: ol.tilegrid.extentFromProjection(projection), - maxZoom: options.maxZoom, - tileSize: options.tileSize - }); + var tileGrid = goog.isDef(options.tileGrid) ? options.tileGrid : + ol.tilegrid.createXYZ({ + extent: ol.tilegrid.extentFromProjection(projection), + maxZoom: options.maxZoom, + tileSize: options.tileSize + }); goog.base(this, { attributions: options.attributions, @@ -108967,15 +111709,8 @@ ol.source.XYZ = function(options) { tileGrid: tileGrid, tileLoadFunction: options.tileLoadFunction, tilePixelRatio: options.tilePixelRatio, - tileUrlFunction: ol.TileUrlFunction.nullTileUrlFunction - }); - - /** - * @private - * @type {ol.TileCoordTransformType} - */ - this.tileCoordTransform_ = tileGrid.createTileCoordTransform({ - wrapX: options.wrapX + tileUrlFunction: ol.TileUrlFunction.nullTileUrlFunction, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true }); if (goog.isDef(options.tileUrlFunction)) { @@ -108991,17 +111726,7 @@ goog.inherits(ol.source.XYZ, ol.source.TileImage); /** - * @inheritDoc - * @api - */ -ol.source.XYZ.prototype.setTileUrlFunction = function(tileUrlFunction) { - goog.base(this, 'setTileUrlFunction', - ol.TileUrlFunction.withTileCoordTransform( - this.tileCoordTransform_, tileUrlFunction)); -}; - - -/** + * Set the URL to use for requests. * @param {string} url URL. * @api stable */ @@ -109012,6 +111737,7 @@ ol.source.XYZ.prototype.setUrl = function(url) { /** + * Set the URLs to use for requests. * @param {Array.<string>} urls URLs. */ ol.source.XYZ.prototype.setUrls = function(urls) { @@ -109020,7 +111746,6 @@ ol.source.XYZ.prototype.setUrls = function(urls) { goog.provide('ol.source.OSM'); -goog.require('ol'); goog.require('ol.Attribution'); goog.require('ol.source.XYZ'); @@ -109049,9 +111774,8 @@ ol.source.OSM = function(opt_options) { var crossOrigin = goog.isDef(options.crossOrigin) ? options.crossOrigin : 'anonymous'; - var protocol = ol.IS_HTTPS ? 'https:' : 'http:'; var url = goog.isDef(options.url) ? - options.url : protocol + '//{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'; goog.base(this, { attributions: attributions, @@ -109059,7 +111783,8 @@ ol.source.OSM = function(opt_options) { opaque: true, maxZoom: goog.isDef(options.maxZoom) ? options.maxZoom : 19, tileLoadFunction: options.tileLoadFunction, - url: url + url: url, + wrapX: options.wrapX }); }; @@ -109067,6 +111792,8 @@ goog.inherits(ol.source.OSM, ol.source.XYZ); /** + * The attribution containing a link to the OpenStreetMap Copyright and License + * page. * @const * @type {ol.Attribution} * @api @@ -109080,7 +111807,6 @@ ol.source.OSM.ATTRIBUTION = new ol.Attribution({ goog.provide('ol.source.MapQuest'); goog.require('goog.asserts'); -goog.require('ol'); goog.require('ol.Attribution'); goog.require('ol.source.OSM'); goog.require('ol.source.XYZ'); @@ -109099,18 +111825,26 @@ goog.require('ol.source.XYZ'); ol.source.MapQuest = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; - goog.asserts.assert(options.layer in ol.source.MapQuestConfig); + goog.asserts.assert(options.layer in ol.source.MapQuestConfig, + 'known layer configured'); var layerConfig = ol.source.MapQuestConfig[options.layer]; - var protocol = ol.IS_HTTPS ? 'https:' : 'http:'; - var url = protocol + '//otile{1-4}-s.mqcdn.com/tiles/1.0.0/' + - options.layer + '/{z}/{x}/{y}.jpg'; + /** + * Layer. Possible values are `osm`, `sat`, and `hyb`. + * @type {string} + * @private + */ + this.layer_ = options.layer; + + var url = goog.isDef(options.url) ? options.url : + 'https://otile{1-4}-s.mqcdn.com/tiles/1.0.0/' + + this.layer_ + '/{z}/{x}/{y}.jpg'; goog.base(this, { attributions: layerConfig.attributions, crossOrigin: 'anonymous', - logo: '//developer.mapquest.com/content/osm/mq_logo.png', + logo: 'https://developer.mapquest.com/content/osm/mq_logo.png', maxZoom: layerConfig.maxZoom, opaque: true, tileLoadFunction: options.tileLoadFunction, @@ -109160,172 +111894,838 @@ ol.source.MapQuestConfig = { } }; -goog.provide('ol.source.OSMXML'); -goog.require('ol.format.OSMXML'); -goog.require('ol.source.StaticVector'); +/** + * Get the layer of the source, either `osm`, `sat`, or `hyb`. + * @return {string} Layer. + * @api + */ +ol.source.MapQuest.prototype.getLayer = function() { + return this.layer_; +}; +goog.provide('ol.ext.pixelworks'); +/** @typedef {function(*)} */ +ol.ext.pixelworks; +(function() { +var exports = {}; +var module = {exports: exports}; +var define; +/** + * @fileoverview + * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility} + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pixelworks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +var Processor = require('./processor'); +exports.Processor = Processor; + +},{"./processor":2}],2:[function(require,module,exports){ +/* eslint-disable dot-notation */ /** - * @classdesc - * Static vector source in OSMXML format - * - * @constructor - * @extends {ol.source.StaticVector} - * @fires ol.source.VectorEvent - * @param {olx.source.OSMXMLOptions=} opt_options Options. - * @api + * Create a function for running operations. + * @param {function(Array, Object):*} operation The operation. + * @return {function(Object):ArrayBuffer} A function that takes an object with + * buffers, meta, imageOps, width, and height properties and returns an array + * buffer. */ -ol.source.OSMXML = function(opt_options) { +function createMinion(operation) { + return function(data) { + // bracket notation for minification support + var buffers = data['buffers']; + var meta = data['meta']; + var imageOps = data['imageOps']; + var width = data['width']; + var height = data['height']; - var options = goog.isDef(opt_options) ? opt_options : {}; + var numBuffers = buffers.length; + var numBytes = buffers[0].byteLength; + var output, b; - goog.base(this, { - attributions: options.attributions, - doc: options.doc, - format: new ol.format.OSMXML(), - logo: options.logo, - node: options.node, - projection: options.projection, - text: options.text, - url: options.url, - urls: options.urls + if (imageOps) { + var images = new Array(numBuffers); + for (b = 0; b < numBuffers; ++b) { + images[b] = new ImageData( + new Uint8ClampedArray(buffers[b]), width, height); + } + output = operation(images, meta).data; + } else { + output = new Uint8ClampedArray(numBytes); + var arrays = new Array(numBuffers); + var pixels = new Array(numBuffers); + for (b = 0; b < numBuffers; ++b) { + arrays[b] = new Uint8ClampedArray(buffers[b]); + pixels[b] = [0, 0, 0, 0]; + } + for (var i = 0; i < numBytes; i += 4) { + for (var j = 0; j < numBuffers; ++j) { + var array = arrays[j]; + pixels[j][0] = array[i]; + pixels[j][1] = array[i + 1]; + pixels[j][2] = array[i + 2]; + pixels[j][3] = array[i + 3]; + } + var pixel = operation(pixels, meta); + output[i] = pixel[0]; + output[i + 1] = pixel[1]; + output[i + 2] = pixel[2]; + output[i + 3] = pixel[3]; + } + } + return output.buffer; + }; +} + +/** + * Create a worker for running operations. + * @param {Object} config Configuration. + * @param {function(Object)} onMessage Called with a message event. + * @return {Worker} The worker. + */ +function createWorker(config, onMessage) { + var lib = Object.keys(config.lib || {}).map(function(name) { + return 'var ' + name + ' = ' + config.lib[name].toString() + ';'; + }); + + var lines = lib.concat([ + 'var __minion__ = (' + createMinion.toString() + ')(', + config.operation.toString(), + ');', + 'self.addEventListener("message", function(__event__) {', + 'var buffer = __minion__(__event__.data);', + 'self.postMessage({buffer: buffer, meta: __event__.data.meta}, [buffer]);', + '});' + ]); + + var blob = new Blob(lines, {type: 'text/javascript'}); + var source = URL.createObjectURL(blob); + var worker = new Worker(source); + worker.addEventListener('message', onMessage); + return worker; +} + +/** + * Create a faux worker for running operations. + * @param {Object} config Configuration. + * @param {function(Object)} onMessage Called with a message event. + * @return {Object} The faux worker. + */ +function createFauxWorker(config, onMessage) { + var minion = createMinion(config.operation); + return { + postMessage: function(data) { + setTimeout(function() { + onMessage({data: {buffer: minion(data), meta: data.meta}}); + }, 0); + } + }; +} + +/** + * A processor runs pixel or image operations in workers. + * @param {Object} config Configuration. + */ +function Processor(config) { + this._imageOps = !!config.imageOps; + var threads; + if (config.threads === 0) { + threads = 0; + } else if (this._imageOps) { + threads = 1; + } else { + threads = config.threads || 1; + } + var workers = []; + if (threads) { + for (var i = 0; i < threads; ++i) { + workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i)); + } + } else { + workers[0] = createFauxWorker(config, this._onWorkerMessage.bind(this, 0)); + } + this._workers = workers; + this._queue = []; + this._maxQueueLength = config.queue || Infinity; + this._running = 0; + this._dataLookup = {}; + this._job = null; +} + +/** + * Run operation on input data. + * @param {Array.<Array|ImageData>} inputs Array of pixels or image data + * (depending on the operation type). + * @param {Object} meta A user data object. This is passed to all operations + * and must be serializable. + * @param {function(Error, ImageData, Object)} callback Called when work + * completes. The first argument is any error. The second is the ImageData + * generated by operations. The third is the user data object. + */ +Processor.prototype.process = function(inputs, meta, callback) { + this._enqueue({ + inputs: inputs, + meta: meta, + callback: callback }); + this._dispatch(); +}; + +/** + * Stop responding to any completed work and destroy the processor. + */ +Processor.prototype.destroy = function() { + for (var key in this) { + this[key] = null; + } + this._destroyed = true; +}; + +/** + * Add a job to the queue. + * @param {Object} job The job. + */ +Processor.prototype._enqueue = function(job) { + this._queue.push(job); + while (this._queue.length > this._maxQueueLength) { + this._queue.shift().callback(null, null); + } +}; + +/** + * Dispatch a job. + */ +Processor.prototype._dispatch = function() { + if (this._running === 0 && this._queue.length > 0) { + var job = this._job = this._queue.shift(); + var width = job.inputs[0].width; + var height = job.inputs[0].height; + var buffers = job.inputs.map(function(input) { + return input.data.buffer; + }); + var threads = this._workers.length; + this._running = threads; + if (threads === 1) { + this._workers[0].postMessage({ + 'buffers': buffers, + 'meta': job.meta, + 'imageOps': this._imageOps, + 'width': width, + 'height': height + }, buffers); + } else { + var length = job.inputs[0].data.length; + var segmentLength = 4 * Math.ceil(length / 4 / threads); + for (var i = 0; i < threads; ++i) { + var offset = i * segmentLength; + var slices = []; + for (var j = 0, jj = buffers.length; j < jj; ++j) { + slices.push(buffers[i].slice(offset, offset + segmentLength)); + } + this._workers[i].postMessage({ + 'buffers': slices, + 'meta': job.meta, + 'imageOps': this._imageOps, + 'width': width, + 'height': height + }, slices); + } + } + } +}; + +/** + * Handle messages from the worker. + * @param {number} index The worker index. + * @param {Object} event The message event. + */ +Processor.prototype._onWorkerMessage = function(index, event) { + if (this._destroyed) { + return; + } + this._dataLookup[index] = event.data; + --this._running; + if (this._running === 0) { + this._resolveJob(); + } +}; +/** + * Resolve a job. If there are no more worker threads, the processor callback + * will be called. + */ +Processor.prototype._resolveJob = function() { + var job = this._job; + var threads = this._workers.length; + var data, meta; + if (threads === 1) { + data = new Uint8ClampedArray(this._dataLookup[0]['buffer']); + meta = this._dataLookup[0]['meta']; + } else { + var length = job.inputs[0].data.length; + data = new Uint8ClampedArray(length); + meta = new Array(length); + var segmentLength = 4 * Math.ceil(length / 4 / threads); + for (var i = 0; i < threads; ++i) { + var buffer = this._dataLookup[i]['buffer']; + var offset = i * segmentLength; + data.set(new Uint8ClampedArray(buffer), offset); + meta[i] = this._dataLookup[i]['meta']; + } + } + this._job = null; + this._dataLookup = {}; + job.callback(null, + new ImageData(data, job.inputs[0].width, job.inputs[0].height), meta); + this._dispatch(); }; -goog.inherits(ol.source.OSMXML, ol.source.StaticVector); -// FIXME cache expiration +module.exports = Processor; + +},{}]},{},[1])(1) +}); +ol.ext.pixelworks = module.exports; +})(); -goog.provide('ol.source.ServerVector'); +goog.provide('ol.source.Raster'); +goog.provide('ol.source.RasterEvent'); +goog.provide('ol.source.RasterEventType'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.functions'); goog.require('goog.object'); +goog.require('goog.vec.Mat4'); +goog.require('ol.ImageCanvas'); +goog.require('ol.TileQueue'); +goog.require('ol.dom'); +goog.require('ol.ext.pixelworks'); goog.require('ol.extent'); -goog.require('ol.loadingstrategy'); -goog.require('ol.source.FormatVector'); -goog.require('ol.structs.RBush'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Tile'); +goog.require('ol.raster.OperationType'); +goog.require('ol.renderer.canvas.ImageLayer'); +goog.require('ol.renderer.canvas.TileLayer'); +goog.require('ol.source.Image'); +goog.require('ol.source.State'); +goog.require('ol.source.Tile'); /** * @classdesc - * A vector source in one of the supported formats, using a custom function to - * read in the data from a remote server. + * A source that transforms data from any number of input sources using an array + * of {@link ol.raster.Operation} functions to transform input pixel values into + * output pixel values. * * @constructor - * @extends {ol.source.FormatVector} - * @param {olx.source.ServerVectorOptions} options Options. + * @extends {ol.source.Image} + * @param {olx.source.RasterOptions} options Options. * @api */ -ol.source.ServerVector = function(options) { +ol.source.Raster = function(options) { - goog.base(this, { - attributions: options.attributions, - format: options.format, - logo: options.logo, - projection: options.projection - }); + /** + * @private + * @type {*} + */ + this.worker_ = null; /** * @private - * @type {ol.structs.RBush.<{extent: ol.Extent}>} + * @type {ol.raster.OperationType} + */ + this.operationType_ = goog.isDef(options.operationType) ? + options.operationType : ol.raster.OperationType.PIXEL; + + /** + * @private + * @type {number} + */ + this.threads_ = goog.isDef(options.threads) ? options.threads : 1; + + /** + * @private + * @type {Array.<ol.renderer.canvas.Layer>} + */ + this.renderers_ = ol.source.Raster.createRenderers_(options.sources); + + for (var r = 0, rr = this.renderers_.length; r < rr; ++r) { + goog.events.listen(this.renderers_[r], goog.events.EventType.CHANGE, + this.changed, false, this); + } + + /** + * @private + * @type {CanvasRenderingContext2D} */ - this.loadedExtents_ = new ol.structs.RBush(); + this.canvasContext_ = ol.dom.createCanvasContext2D(); /** * @private - * @type {function(this: ol.source.ServerVector, ol.Extent, number, - * ol.proj.Projection)} + * @type {ol.TileQueue} */ - this.loader_ = options.loader; + this.tileQueue_ = new ol.TileQueue( + goog.functions.constant(1), + goog.bind(this.changed, this)); + + var layerStatesArray = ol.source.Raster.getLayerStatesArray_(this.renderers_); + var layerStates = {}; + for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerStates[goog.getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; + } /** + * The most recently rendered state. + * @type {?ol.source.Raster.RenderedState} * @private - * @type {function(ol.Extent, number): Array.<ol.Extent>} */ - this.strategy_ = goog.isDef(options.strategy) ? - options.strategy : ol.loadingstrategy.bbox; + this.renderedState_ = null; /** + * The most recently rendered image canvas. + * @type {ol.ImageCanvas} * @private - * @type {Object.<number|string, boolean>} */ - this.loadedFeatures_ = {}; + this.renderedImageCanvas_ = null; + + /** + * @private + * @type {olx.FrameState} + */ + this.frameState_ = { + animate: false, + attributions: {}, + coordinateToPixelMatrix: goog.vec.Mat4.createNumber(), + extent: null, + focus: null, + index: 0, + layerStates: layerStates, + layerStatesArray: layerStatesArray, + logos: {}, + pixelRatio: 1, + pixelToCoordinateMatrix: goog.vec.Mat4.createNumber(), + postRenderFunctions: [], + size: [0, 0], + skippedFeatureUids: {}, + tileQueue: this.tileQueue_, + time: Date.now(), + usedTiles: {}, + viewState: /** @type {olx.ViewState} */ ({ + rotation: 0 + }), + viewHints: [], + wantedTiles: {} + }; + + goog.base(this, {}); + + if (goog.isDef(options.operation)) { + this.setOperation(options.operation, options.lib); + } + +}; +goog.inherits(ol.source.Raster, ol.source.Image); + + +/** + * Set the operation. + * @param {ol.raster.Operation} operation New operation. + * @param {Object=} opt_lib Functions that will be available to operations run + * in a worker. + * @api + */ +ol.source.Raster.prototype.setOperation = function(operation, opt_lib) { + this.worker_ = new ol.ext.pixelworks.Processor({ + operation: operation, + imageOps: this.operationType_ === ol.raster.OperationType.IMAGE, + queue: 1, + lib: opt_lib, + threads: this.threads_ + }); + this.changed(); +}; + + +/** + * Update the stored frame state. + * @param {ol.Extent} extent The view extent (in map units). + * @param {number} resolution The view resolution. + * @param {ol.proj.Projection} projection The view projection. + * @return {olx.FrameState} The updated frame state. + * @private + */ +ol.source.Raster.prototype.updateFrameState_ = + function(extent, resolution, projection) { + + var frameState = /** @type {olx.FrameState} */ ( + goog.object.clone(this.frameState_)); + + frameState.viewState = /** @type {olx.ViewState} */ ( + goog.object.clone(frameState.viewState)); + + var center = ol.extent.getCenter(extent); + var width = Math.round(ol.extent.getWidth(extent) / resolution); + var height = Math.round(ol.extent.getHeight(extent) / resolution); + + frameState.extent = extent; + frameState.focus = ol.extent.getCenter(extent); + frameState.size[0] = width; + frameState.size[1] = height; + + var viewState = frameState.viewState; + viewState.center = center; + viewState.projection = projection; + viewState.resolution = resolution; + return frameState; +}; + +/** + * Determine if the most recently rendered image canvas is dirty. + * @param {ol.Extent} extent The requested extent. + * @param {number} resolution The requested resolution. + * @return {boolean} The image is dirty. + * @private + */ +ol.source.Raster.prototype.isDirty_ = function(extent, resolution) { + var state = this.renderedState_; + return !state || + this.getRevision() !== state.revision || + resolution !== state.resolution || + !ol.extent.equals(extent, state.extent); }; -goog.inherits(ol.source.ServerVector, ol.source.FormatVector); /** * @inheritDoc */ -ol.source.ServerVector.prototype.addFeaturesInternal = function(features) { - /** @type {Array.<ol.Feature>} */ - var notLoadedFeatures = []; - var i, ii; - for (i = 0, ii = features.length; i < ii; ++i) { - var feature = features[i]; - var featureId = feature.getId(); - if (!goog.isDef(featureId)) { - notLoadedFeatures.push(feature); - } else if (!(featureId in this.loadedFeatures_)) { - notLoadedFeatures.push(feature); - this.loadedFeatures_[featureId] = true; +ol.source.Raster.prototype.getImage = + function(extent, resolution, pixelRatio, projection) { + + if (!this.allSourcesReady_()) { + return null; + } + + if (!this.isDirty_(extent, resolution)) { + return this.renderedImageCanvas_; + } + + var context = this.canvasContext_; + var canvas = context.canvas; + + var width = Math.round(ol.extent.getWidth(extent) / resolution); + var height = Math.round(ol.extent.getHeight(extent) / resolution); + + if (width !== canvas.width || + height !== canvas.height) { + canvas.width = width; + canvas.height = height; + } + + var frameState = this.updateFrameState_(extent, resolution, projection); + + var imageCanvas = new ol.ImageCanvas( + extent, resolution, 1, this.getAttributions(), canvas, + this.composeFrame_.bind(this, frameState)); + + this.renderedImageCanvas_ = imageCanvas; + + this.renderedState_ = { + extent: extent, + resolution: resolution, + revision: this.getRevision() + }; + + return imageCanvas; +}; + + +/** + * Determine if all sources are ready. + * @return {boolean} All sources are ready. + * @private + */ +ol.source.Raster.prototype.allSourcesReady_ = function() { + var ready = true; + var source; + for (var i = 0, ii = this.renderers_.length; i < ii; ++i) { + source = this.renderers_[i].getLayer().getSource(); + if (source.getState() !== ol.source.State.READY) { + ready = false; + break; } } - goog.base(this, 'addFeaturesInternal', notLoadedFeatures); + return ready; }; /** - * @inheritDoc + * Compose the frame. This renders data from all sources, runs pixel-wise + * operations, and renders the result to the stored canvas context. + * @param {olx.FrameState} frameState The frame state. + * @param {function(Error)} callback Called when composition is complete. + * @private + */ +ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) { + var len = this.renderers_.length; + var imageDatas = new Array(len); + for (var i = 0; i < len; ++i) { + var imageData = ol.source.Raster.getImageData_( + this.renderers_[i], frameState, frameState.layerStatesArray[i]); + if (imageData) { + imageDatas[i] = imageData; + } else { + // image not yet ready + return; + } + } + + var data = {}; + this.dispatchEvent(new ol.source.RasterEvent( + ol.source.RasterEventType.BEFOREOPERATIONS, frameState, data)); + + this.worker_.process(imageDatas, data, + this.onWorkerComplete_.bind(this, frameState, callback)); + + frameState.tileQueue.loadMoreTiles(16, 16); +}; + + +/** + * Called when pixel processing is complete. + * @param {olx.FrameState} frameState The frame state. + * @param {function(Error)} callback Called when rendering is complete. + * @param {Error} err Any error during processing. + * @param {ImageData} output The output image data. + * @param {Object} data The user data. + * @private */ -ol.source.ServerVector.prototype.clear = function() { - goog.object.clear(this.loadedFeatures_); - this.loadedExtents_.clear(); - goog.base(this, 'clear'); +ol.source.Raster.prototype.onWorkerComplete_ = + function(frameState, callback, err, output, data) { + if (err) { + callback(err); + return; + } + if (goog.isNull(output)) { + // job aborted + return; + } + + this.dispatchEvent(new ol.source.RasterEvent( + ol.source.RasterEventType.AFTEROPERATIONS, frameState, data)); + + var resolution = frameState.viewState.resolution / frameState.pixelRatio; + if (!this.isDirty_(frameState.extent, resolution)) { + this.canvasContext_.putImageData(output, 0, 0); + } + + callback(null); }; /** - * @inheritDoc + * Get image data from a renderer. + * @param {ol.renderer.canvas.Layer} renderer Layer renderer. + * @param {olx.FrameState} frameState The frame state. + * @param {ol.layer.LayerState} layerState The layer state. + * @return {ImageData} The image data. + * @private */ -ol.source.ServerVector.prototype.loadFeatures = - function(extent, resolution, projection) { - var loadedExtents = this.loadedExtents_; - var extentsToLoad = this.strategy_(extent, resolution); - var i, ii; - for (i = 0, ii = extentsToLoad.length; i < ii; ++i) { - var extentToLoad = extentsToLoad[i]; - var alreadyLoaded = loadedExtents.forEachInExtent(extentToLoad, - /** - * @param {{extent: ol.Extent}} object Object. - * @return {boolean} Contains. - */ - function(object) { - return ol.extent.containsExtent(object.extent, extentToLoad); - }); - if (!alreadyLoaded) { - this.loader_.call(this, extentToLoad, resolution, projection); - loadedExtents.insert(extentToLoad, {extent: extentToLoad.slice()}); +ol.source.Raster.getImageData_ = function(renderer, frameState, layerState) { + renderer.prepareFrame(frameState, layerState); + // We should be able to call renderer.composeFrame(), but this is inefficient + // for tiled sources (we've already rendered to an intermediate canvas in the + // prepareFrame call and we don't need to render again to the output canvas). + // TODO: make all canvas renderers render to a single canvas + var image = renderer.getImage(); + if (!image) { + return null; + } + var imageTransform = renderer.getImageTransform(); + var dx = Math.round(goog.vec.Mat4.getElement(imageTransform, 0, 3)); + var dy = Math.round(goog.vec.Mat4.getElement(imageTransform, 1, 3)); + var width = frameState.size[0]; + var height = frameState.size[1]; + if (image instanceof Image) { + if (!ol.source.Raster.context_) { + ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height); + } else { + var canvas = ol.source.Raster.context_.canvas; + if (canvas.width !== width || canvas.height !== height) { + ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height); + } else { + ol.source.Raster.context_.clearRect(0, 0, width, height); + } } + var dw = Math.round( + image.width * goog.vec.Mat4.getElement(imageTransform, 0, 0)); + var dh = Math.round( + image.height * goog.vec.Mat4.getElement(imageTransform, 1, 1)); + ol.source.Raster.context_.drawImage(image, dx, dy, dw, dh); + return ol.source.Raster.context_.getImageData(0, 0, width, height); + } else { + return image.getContext('2d').getImageData(-dx, -dy, width, height); } }; /** - * @function - * @param {ArrayBuffer|Document|Node|Object|string} source Source. - * @return {Array.<ol.Feature>} Features. - * @api + * A reusable canvas context. + * @type {CanvasRenderingContext2D} + * @private + */ +ol.source.Raster.context_ = null; + + +/** + * Get a list of layer states from a list of renderers. + * @param {Array.<ol.renderer.canvas.Layer>} renderers Layer renderers. + * @return {Array.<ol.layer.LayerState>} The layer states. + * @private + */ +ol.source.Raster.getLayerStatesArray_ = function(renderers) { + return renderers.map(function(renderer) { + return renderer.getLayer().getLayerState(); + }); +}; + + +/** + * Create renderers for all sources. + * @param {Array.<ol.source.Source>} sources The sources. + * @return {Array.<ol.renderer.canvas.Layer>} Array of layer renderers. + * @private + */ +ol.source.Raster.createRenderers_ = function(sources) { + var len = sources.length; + var renderers = new Array(len); + for (var i = 0; i < len; ++i) { + renderers[i] = ol.source.Raster.createRenderer_(sources[i]); + } + return renderers; +}; + + +/** + * Create a renderer for the provided source. + * @param {ol.source.Source} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createRenderer_ = function(source) { + var renderer = null; + if (source instanceof ol.source.Tile) { + renderer = ol.source.Raster.createTileRenderer_( + /** @type {ol.source.Tile} */ (source)); + } else if (source instanceof ol.source.Image) { + renderer = ol.source.Raster.createImageRenderer_( + /** @type {ol.source.Image} */ (source)); + } else { + goog.asserts.fail('Unsupported source type: ' + source); + } + return renderer; +}; + + +/** + * Create an image renderer for the provided source. + * @param {ol.source.Image} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createImageRenderer_ = function(source) { + var layer = new ol.layer.Image({source: source}); + return new ol.renderer.canvas.ImageLayer(layer); +}; + + +/** + * Create a tile renderer for the provided source. + * @param {ol.source.Tile} source The source. + * @return {ol.renderer.canvas.Layer} The renderer. + * @private + */ +ol.source.Raster.createTileRenderer_ = function(source) { + var layer = new ol.layer.Tile({source: source}); + return new ol.renderer.canvas.TileLayer(layer); +}; + + +/** + * @typedef {{revision: number, + * resolution: number, + * extent: ol.Extent}} + */ +ol.source.Raster.RenderedState; + + + +/** + * @classdesc + * Events emitted by {@link ol.source.Raster} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.source.RasterEvent} + * @param {string} type Type. + * @param {olx.FrameState} frameState The frame state. + * @param {Object} data An object made available to operations. */ -ol.source.ServerVector.prototype.readFeatures; +ol.source.RasterEvent = function(type, frameState, data) { + goog.base(this, type); + + /** + * The raster extent. + * @type {ol.Extent} + * @api + */ + this.extent = frameState.extent; + + /** + * The pixel resolution (map units per pixel). + * @type {number} + * @api + */ + this.resolution = frameState.viewState.resolution / frameState.pixelRatio; + + /** + * An object made available to all operations. This can be used by operations + * as a storage object (e.g. for calculating statistics). + * @type {Object} + * @api + */ + this.data = data; + +}; +goog.inherits(ol.source.RasterEvent, goog.events.Event); + + +/** + * @enum {string} + */ +ol.source.RasterEventType = { + /** + * Triggered before operations are run. + * @event ol.source.RasterEvent#beforeoperations + * @api + */ + BEFOREOPERATIONS: 'beforeoperations', + + /** + * Triggered after operations are run. + * @event ol.source.RasterEvent#afteroperations + * @api + */ + AFTEROPERATIONS: 'afteroperations' +}; goog.provide('ol.source.Stamen'); goog.require('goog.asserts'); -goog.require('ol'); goog.require('ol.Attribution'); goog.require('ol.source.OSM'); goog.require('ol.source.XYZ'); @@ -109415,17 +112815,17 @@ ol.source.Stamen = function(options) { var i = options.layer.indexOf('-'); var provider = i == -1 ? options.layer : options.layer.slice(0, i); - goog.asserts.assert(provider in ol.source.StamenProviderConfig); + goog.asserts.assert(provider in ol.source.StamenProviderConfig, + 'known provider configured'); var providerConfig = ol.source.StamenProviderConfig[provider]; - goog.asserts.assert(options.layer in ol.source.StamenLayerConfig); + goog.asserts.assert(options.layer in ol.source.StamenLayerConfig, + 'known layer configured'); var layerConfig = ol.source.StamenLayerConfig[options.layer]; - var root = ol.IS_HTTPS ? 'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' : - 'http://{a-d}.tile.stamen.com/'; var url = goog.isDef(options.url) ? options.url : - root + options.layer + '/{z}/{x}/{y}.' + - layerConfig.extension; + 'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer + + '/{z}/{x}/{y}.' + layerConfig.extension; goog.base(this, { attributions: ol.source.Stamen.ATTRIBUTIONS, @@ -109455,16 +112855,258 @@ ol.source.Stamen.ATTRIBUTIONS = [ ol.source.OSM.ATTRIBUTION ]; +goog.provide('ol.source.TileArcGISRest'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.uri.utils'); +goog.require('ol'); +goog.require('ol.TileCoord'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.extent'); +goog.require('ol.proj'); +goog.require('ol.size'); +goog.require('ol.source.TileImage'); +goog.require('ol.tilecoord'); + + + +/** + * @classdesc + * Layer source for tile data from ArcGIS Rest services. Map and Image + * Services are supported. + * + * For cached ArcGIS services, better performance is available using the + * {@link ol.source.XYZ} data source. + * + * @constructor + * @extends {ol.source.TileImage} + * @param {olx.source.TileArcGISRestOptions=} opt_options Tile ArcGIS Rest + * options. + * @api + */ +ol.source.TileArcGISRest = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + var params = goog.isDef(options.params) ? options.params : {}; + + goog.base(this, { + attributions: options.attributions, + crossOrigin: options.crossOrigin, + logo: options.logo, + projection: options.projection, + tileGrid: options.tileGrid, + tileLoadFunction: options.tileLoadFunction, + tileUrlFunction: goog.bind(this.tileUrlFunction_, this), + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true + }); + + var urls = options.urls; + if (!goog.isDef(urls) && goog.isDef(options.url)) { + urls = ol.TileUrlFunction.expandUrl(options.url); + } + + /** + * @private + * @type {!Array.<string>} + */ + this.urls_ = goog.isDefAndNotNull(urls) ? urls : []; + + /** + * @private + * @type {Object} + */ + this.params_ = params; + + /** + * @private + * @type {ol.Extent} + */ + this.tmpExtent_ = ol.extent.createEmpty(); + +}; +goog.inherits(ol.source.TileArcGISRest, ol.source.TileImage); + + +/** + * Get the user-provided params, i.e. those passed to the constructor through + * the "params" option, and possibly updated using the updateParams method. + * @return {Object} Params. + * @api + */ +ol.source.TileArcGISRest.prototype.getParams = function() { + return this.params_; +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.Size} tileSize Tile size. + * @param {ol.Extent} tileExtent Tile extent. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {Object} params Params. + * @return {string|undefined} Request URL. + * @private + */ +ol.source.TileArcGISRest.prototype.getRequestUrl_ = + function(tileCoord, tileSize, tileExtent, + pixelRatio, projection, params) { + + var urls = this.urls_; + if (goog.array.isEmpty(urls)) { + return undefined; + } + + // ArcGIS Server only wants the numeric portion of the projection ID. + var srid = projection.getCode().split(':').pop(); + + params['SIZE'] = tileSize[0] + ',' + tileSize[1]; + params['BBOX'] = tileExtent.join(','); + params['BBOXSR'] = srid; + params['IMAGESR'] = srid; + params['DPI'] = Math.round(90 * pixelRatio); + + var url; + if (urls.length == 1) { + url = urls[0]; + } else { + var index = goog.math.modulo(ol.tilecoord.hash(tileCoord), urls.length); + url = urls[index]; + } + + if (!goog.string.endsWith(url, '/')) { + url = url + '/'; + } + + // If a MapServer, use export. If an ImageServer, use exportImage. + if (goog.string.endsWith(url, 'MapServer/')) { + url = url + 'export'; + } + else if (goog.string.endsWith(url, 'ImageServer/')) { + url = url + 'exportImage'; + } + else { + goog.asserts.fail('Unknown Rest Service', url); + } + + return goog.uri.utils.appendParamsFromMap(url, params); +}; + + +/** + * @param {number} z Z. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.Size} Size. + */ +ol.source.TileArcGISRest.prototype.getTilePixelSize = + function(z, pixelRatio, projection) { + var tileSize = goog.base(this, 'getTilePixelSize', z, pixelRatio, projection); + if (pixelRatio == 1) { + return tileSize; + } else { + return ol.size.scale(tileSize, pixelRatio, this.tmpSize); + } +}; + + +/** + * Return the URLs used for this ArcGIS source. + * @return {!Array.<string>} URLs. + * @api stable + */ +ol.source.TileArcGISRest.prototype.getUrls = function() { + return this.urls_; +}; + + +/** + * Set the URL to use for requests. + * @param {string|undefined} url URL. + * @api stable + */ +ol.source.TileArcGISRest.prototype.setUrl = function(url) { + var urls = goog.isDef(url) ? ol.TileUrlFunction.expandUrl(url) : null; + this.setUrls(urls); +}; + + +/** + * Set the URLs to use for requests. + * @param {Array.<string>|undefined} urls URLs. + * @api stable + */ +ol.source.TileArcGISRest.prototype.setUrls = function(urls) { + this.urls_ = goog.isDefAndNotNull(urls) ? urls : []; + this.changed(); +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + * @private + */ +ol.source.TileArcGISRest.prototype.tileUrlFunction_ = + function(tileCoord, pixelRatio, projection) { + + var tileGrid = this.getTileGrid(); + if (goog.isNull(tileGrid)) { + tileGrid = this.getTileGridForProjection(projection); + } + + if (tileGrid.getResolutions().length <= tileCoord[0]) { + return undefined; + } + + var tileExtent = tileGrid.getTileCoordExtent( + tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); + + if (pixelRatio != 1) { + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); + } + + // Apply default params and override with user specified values. + var baseParams = { + 'F': 'image', + 'FORMAT': 'PNG32', + 'TRANSPARENT': true + }; + goog.object.extend(baseParams, this.params_); + + return this.getRequestUrl_(tileCoord, tileSize, tileExtent, + pixelRatio, projection, baseParams); +}; + + +/** + * Update the user-provided params. + * @param {Object} params Params. + * @api stable + */ +ol.source.TileArcGISRest.prototype.updateParams = function(params) { + goog.object.extend(this.params_, params); + this.changed(); +}; + goog.provide('ol.source.TileDebug'); goog.require('ol.Tile'); -goog.require('ol.TileCache'); goog.require('ol.TileCoord'); goog.require('ol.TileState'); goog.require('ol.dom'); +goog.require('ol.size'); goog.require('ol.source.Tile'); goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); @@ -109472,18 +113114,25 @@ goog.require('ol.tilegrid.TileGrid'); * @constructor * @extends {ol.Tile} * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.tilegrid.TileGrid} tileGrid Tile grid. + * @param {ol.Size} tileSize Tile size. + * @param {string} text Text. * @private */ -ol.DebugTile_ = function(tileCoord, tileGrid) { +ol.DebugTile_ = function(tileCoord, tileSize, text) { goog.base(this, tileCoord, ol.TileState.LOADED); /** * @private - * @type {number} + * @type {ol.Size} + */ + this.tileSize_ = tileSize; + + /** + * @private + * @type {string} */ - this.tileSize_ = tileGrid.getTileSize(tileCoord[0]); + this.text_ = text; /** * @private @@ -109505,17 +113154,16 @@ ol.DebugTile_.prototype.getImage = function(opt_context) { } else { var tileSize = this.tileSize_; - var context = ol.dom.createCanvasContext2D(tileSize, tileSize); + var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]); context.strokeStyle = 'black'; - context.strokeRect(0.5, 0.5, tileSize + 0.5, tileSize + 0.5); + context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5); context.fillStyle = 'black'; context.textAlign = 'center'; context.textBaseline = 'middle'; context.font = '24px sans-serif'; - context.fillText(ol.tilecoord.toString(this.tileCoord), - tileSize / 2, tileSize / 2); + context.fillText(this.text_, tileSize[0] / 2, tileSize[1] / 2); this.canvasByContext_[key] = context.canvas; return context.canvas; @@ -109543,45 +113191,29 @@ ol.source.TileDebug = function(options) { goog.base(this, { opaque: false, projection: options.projection, - tileGrid: options.tileGrid + tileGrid: options.tileGrid, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true }); - /** - * @private - * @type {ol.TileCache} - */ - this.tileCache_ = new ol.TileCache(); - }; goog.inherits(ol.source.TileDebug, ol.source.Tile); -/** - * @inheritDoc - */ -ol.source.TileDebug.prototype.canExpireCache = function() { - return this.tileCache_.canExpireCache(); -}; - - -/** - * @inheritDoc - */ -ol.source.TileDebug.prototype.expireCache = function(usedTiles) { - this.tileCache_.expireCache(usedTiles); -}; - - /** * @inheritDoc */ ol.source.TileDebug.prototype.getTile = function(z, x, y) { var tileCoordKey = this.getKeyZXY(z, x, y); - if (this.tileCache_.containsKey(tileCoordKey)) { - return /** @type {!ol.DebugTile_} */ (this.tileCache_.get(tileCoordKey)); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.DebugTile_} */ (this.tileCache.get(tileCoordKey)); } else { - var tile = new ol.DebugTile_([z, x, y], this.tileGrid); - this.tileCache_.set(tileCoordKey, tile); + var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + var tileCoord = [z, x, y]; + var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord); + var text = goog.isNull(textTileCoord) ? '' : ol.tilecoord.toString( + this.getTileCoordForTileUrlFunction(textTileCoord)); + var tile = new ol.DebugTile_(tileCoord, tileSize, text); + this.tileCache.set(tileCoordKey, tile); return tile; } }; @@ -109605,7 +113237,6 @@ goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.State'); goog.require('ol.source.TileImage'); -goog.require('ol.tilegrid.XYZ'); @@ -109621,18 +113252,14 @@ goog.require('ol.tilegrid.XYZ'); ol.source.TileJSON = function(options) { goog.base(this, { + attributions: options.attributions, crossOrigin: options.crossOrigin, projection: ol.proj.get('EPSG:3857'), state: ol.source.State.LOADING, - tileLoadFunction: options.tileLoadFunction + tileLoadFunction: options.tileLoadFunction, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true }); - /** - * @type {boolean|undefined} - * @private - */ - this.wrapX_ = options.wrapX; - var request = new goog.net.Jsonp(options.url); request.send(undefined, goog.bind(this.handleTileJSONResponse, this)); @@ -109657,25 +113284,21 @@ ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) { } if (goog.isDef(tileJSON.scheme)) { - goog.asserts.assert(tileJSON.scheme == 'xyz'); + goog.asserts.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"'); } var minZoom = tileJSON.minzoom || 0; var maxZoom = tileJSON.maxzoom || 22; - var tileGrid = new ol.tilegrid.XYZ({ + var tileGrid = ol.tilegrid.createXYZ({ extent: ol.tilegrid.extentFromProjection(sourceProjection), maxZoom: maxZoom, minZoom: minZoom }); this.tileGrid = tileGrid; - this.tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform( - tileGrid.createTileCoordTransform({ - extent: extent, - wrapX: this.wrapX_ - }), - ol.TileUrlFunction.createFromTemplates(tileJSON.tiles)); + this.tileUrlFunction = ol.TileUrlFunction.createFromTemplates(tileJSON.tiles); - if (goog.isDef(tileJSON.attribution)) { + if (goog.isDef(tileJSON.attribution) && + goog.isNull(this.getAttributions())) { var attributionExtent = goog.isDef(extent) ? extent : epsg4326Projection.getExtent(); /** @type {Object.<string, Array.<ol.TileRange>>} */ @@ -109707,14 +113330,12 @@ goog.require('goog.events.EventType'); goog.require('goog.net.Jsonp'); goog.require('ol.Attribution'); goog.require('ol.Tile'); -goog.require('ol.TileCache'); goog.require('ol.TileState'); goog.require('ol.TileUrlFunction'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.State'); goog.require('ol.source.Tile'); -goog.require('ol.tilegrid.XYZ'); @@ -109746,12 +113367,6 @@ ol.source.TileUTFGrid = function(options) { */ this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction; - /** - * @private - * @type {!ol.TileCache} - */ - this.tileCache_ = new ol.TileCache(); - /** * @private * @type {string|undefined} @@ -109765,22 +113380,7 @@ goog.inherits(ol.source.TileUTFGrid, ol.source.Tile); /** - * @inheritDoc - */ -ol.source.TileUTFGrid.prototype.canExpireCache = function() { - return this.tileCache_.canExpireCache(); -}; - - -/** - * @inheritDoc - */ -ol.source.TileUTFGrid.prototype.expireCache = function(usedTiles) { - this.tileCache_.expireCache(usedTiles); -}; - - -/** + * Return the template from TileJSON. * @return {string|undefined} The template from TileJSON. * @api */ @@ -109840,11 +113440,11 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) { } if (goog.isDef(tileJSON.scheme)) { - goog.asserts.assert(tileJSON.scheme == 'xyz'); + goog.asserts.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"'); } var minZoom = tileJSON.minzoom || 0; var maxZoom = tileJSON.maxzoom || 22; - var tileGrid = new ol.tilegrid.XYZ({ + var tileGrid = ol.tilegrid.createXYZ({ extent: ol.tilegrid.extentFromProjection(sourceProjection), maxZoom: maxZoom, minZoom: minZoom @@ -109859,11 +113459,7 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) { return; } - this.tileUrlFunction_ = ol.TileUrlFunction.withTileCoordTransform( - tileGrid.createTileCoordTransform({ - extent: extent - }), - ol.TileUrlFunction.createFromTemplates(grids)); + this.tileUrlFunction_ = ol.TileUrlFunction.createFromTemplates(grids); if (goog.isDef(tileJSON.attribution)) { var attributionExtent = goog.isDef(extent) ? @@ -109895,19 +113491,21 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) { ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) { var tileCoordKey = this.getKeyZXY(z, x, y); - if (this.tileCache_.containsKey(tileCoordKey)) { - return /** @type {!ol.Tile} */ (this.tileCache_.get(tileCoordKey)); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); } else { - goog.asserts.assert(projection); + goog.asserts.assert(projection, 'argument projection is truthy'); var tileCoord = [z, x, y]; - var tileUrl = this.tileUrlFunction_(tileCoord, pixelRatio, projection); + var urlTileCoord = + this.getTileCoordForTileUrlFunction(tileCoord, projection); + var tileUrl = this.tileUrlFunction_(urlTileCoord, pixelRatio, projection); var tile = new ol.source.TileUTFGridTile_( tileCoord, goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY, goog.isDef(tileUrl) ? tileUrl : '', this.tileGrid.getTileCoordExtent(tileCoord), this.preemptive_); - this.tileCache_.set(tileCoordKey, tile); + this.tileCache.set(tileCoordKey, tile); return tile; } }; @@ -109918,8 +113516,8 @@ ol.source.TileUTFGrid.prototype.getTile = */ ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) { var tileCoordKey = this.getKeyZXY(z, x, y); - if (this.tileCache_.containsKey(tileCoordKey)) { - this.tileCache_.get(tileCoordKey); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); } }; @@ -109994,7 +113592,7 @@ ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) { */ ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) { if (goog.isNull(this.grid_) || goog.isNull(this.keys_) || - goog.isNull(this.data_)) { + !goog.isDefAndNotNull(this.data_)) { return null; } var xRelative = (coordinate[0] - this.extent_[0]) / @@ -110110,10 +113708,11 @@ goog.provide('ol.source.TileVector'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.object'); -goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); -goog.require('ol.source.FormatVector'); +goog.require('ol.featureloader'); goog.require('ol.source.State'); +goog.require('ol.source.Vector'); +goog.require('ol.tilecoord'); goog.require('ol.tilegrid.TileGrid'); @@ -110124,7 +113723,7 @@ goog.require('ol.tilegrid.TileGrid'); * into tiles in a fixed grid pattern. * * @constructor - * @extends {ol.source.FormatVector} + * @extends {ol.source.Vector} * @param {olx.source.TileVectorOptions} options Options. * @api */ @@ -110132,11 +113731,18 @@ ol.source.TileVector = function(options) { goog.base(this, { attributions: options.attributions, - format: options.format, logo: options.logo, - projection: options.projection + projection: undefined, + state: ol.source.State.READY, + wrapX: options.wrapX }); + /** + * @private + * @type {ol.format.Feature|undefined} + */ + this.format_ = goog.isDef(options.format) ? options.format : null; + /** * @private * @type {ol.tilegrid.TileGrid} @@ -110151,9 +113757,14 @@ ol.source.TileVector = function(options) { /** * @private - * @type {ol.TileCoordTransformType} + * @type {?ol.TileVectorLoadFunctionType} */ - this.tileCoordTransform_ = this.tileGrid_.createTileCoordTransform(); + this.tileLoadFunction_ = goog.isDef(options.tileLoadFunction) ? + options.tileLoadFunction : null; + + goog.asserts.assert(!goog.isNull(this.format_) || + !goog.isNull(this.tileLoadFunction_), + 'Either format or tileLoadFunction are required'); /** * @private @@ -110170,7 +113781,7 @@ ol.source.TileVector = function(options) { } }; -goog.inherits(ol.source.TileVector, ol.source.FormatVector); +goog.inherits(ol.source.TileVector, ol.source.Vector); /** @@ -110228,7 +113839,8 @@ ol.source.TileVector.prototype.forEachFeatureAtCoordinateAndResolution = for (i = 0, ii = features.length; i < ii; ++i) { var feature = features[i]; var geometry = feature.getGeometry(); - goog.asserts.assert(goog.isDefAndNotNull(geometry)); + goog.asserts.assert(goog.isDefAndNotNull(geometry), + 'feature geometry is defined and not null'); if (geometry.containsCoordinate(coordinate)) { var result = callback.call(opt_this, feature); if (result) { @@ -110290,6 +113902,7 @@ ol.source.TileVector.prototype.getExtent = goog.abstractMethod; /** + * Return the features of the TileVector source. * @inheritDoc * @api */ @@ -110332,6 +113945,28 @@ ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution = ol.source.TileVector.prototype.getFeaturesInExtent = goog.abstractMethod; +/** + * Handles x-axis wrapping and returns a tile coordinate transformed from the + * internal tile scheme to the tile grid's tile scheme. When the tile coordinate + * is outside the resolution and extent range of the tile grid, `null` will be + * returned. + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.proj.Projection} projection Projection. + * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or + * null if no tile URL should be created for the passed `tileCoord`. + */ +ol.source.TileVector.prototype.getTileCoordForTileUrlFunction = + function(tileCoord, projection) { + var tileGrid = this.tileGrid_; + goog.asserts.assert(!goog.isNull(tileGrid), 'tile grid needed'); + if (this.getWrapX() && projection.isGlobal()) { + tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); + } + return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? + tileCoord : null; +}; + + /** * @param {number} z Z. * @param {number} x X. @@ -110349,7 +113984,6 @@ ol.source.TileVector.prototype.getTileKeyZXY_ = function(z, x, y) { */ ol.source.TileVector.prototype.loadFeatures = function(extent, resolution, projection) { - var tileCoordTransform = this.tileCoordTransform_; var tileGrid = this.tileGrid_; var tileUrlFunction = this.tileUrlFunction_; var tiles = this.tiles_; @@ -110364,21 +113998,28 @@ ol.source.TileVector.prototype.loadFeatures = */ function success(tileKey, features) { tiles[tileKey] = features; - this.setState(ol.source.State.READY); + this.changed(); } for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { var tileKey = this.getTileKeyZXY_(z, x, y); if (!(tileKey in tiles)) { - tileCoord[0] = z; tileCoord[1] = x; tileCoord[2] = y; - tileCoordTransform(tileCoord, projection, tileCoord); - var url = tileUrlFunction(tileCoord, 1, projection); + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var url = goog.isNull(urlTileCoord) ? undefined : + tileUrlFunction(urlTileCoord, 1, projection); if (goog.isDef(url)) { tiles[tileKey] = []; - this.loadFeaturesFromURL(url, goog.partial(success, tileKey), - goog.nullFunction, this); + var tileSuccess = goog.partial(success, tileKey); + if (!goog.isNull(this.tileLoadFunction_)) { + this.tileLoadFunction_(url, goog.bind(tileSuccess, this)); + } else { + var loader = ol.featureloader.loadFeaturesXhr(url, + /** @type {ol.format.Feature} */ (this.format_), tileSuccess); + loader.call(this, extent, resolution, projection); + } } } } @@ -110434,6 +114075,7 @@ goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); goog.require('ol.extent'); goog.require('ol.proj'); +goog.require('ol.size'); goog.require('ol.source.TileImage'); goog.require('ol.source.wms'); goog.require('ol.source.wms.ServerType'); @@ -110466,7 +114108,8 @@ ol.source.TileWMS = function(opt_options) { projection: options.projection, tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction, - tileUrlFunction: goog.bind(this.tileUrlFunction_, this) + tileUrlFunction: goog.bind(this.tileUrlFunction_, this), + wrapX: goog.isDef(options.wrapX) ? options.wrapX : true }); var urls = options.urls; @@ -110547,7 +114190,8 @@ goog.inherits(ol.source.TileWMS, ol.source.TileImage); ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) { - goog.asserts.assert(!('VERSION' in params)); + goog.asserts.assert(!('VERSION' in params), + 'key VERSION is not allowed in params'); var projectionObj = ol.proj.get(projection); @@ -110565,11 +114209,12 @@ ol.source.TileWMS.prototype.getGetFeatureInfoUrl = var tileResolution = tileGrid.getResolution(tileCoord[0]); var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); - var tileSize = tileGrid.getTileSize(tileCoord[0]); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); var gutter = this.gutter_; if (gutter !== 0) { - tileSize += 2 * gutter; + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); tileExtent = ol.extent.buffer(tileExtent, tileResolution * gutter, tileExtent); } @@ -110580,15 +114225,15 @@ ol.source.TileWMS.prototype.getGetFeatureInfoUrl = 'REQUEST': 'GetFeatureInfo', 'FORMAT': 'image/png', 'TRANSPARENT': true, - 'QUERY_LAYERS': goog.object.get(this.params_, 'LAYERS') + 'QUERY_LAYERS': this.params_['LAYERS'] }; goog.object.extend(baseParams, this.params_, params); var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution); var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution); - goog.object.set(baseParams, this.v13_ ? 'I' : 'X', x); - goog.object.set(baseParams, this.v13_ ? 'J' : 'Y', y); + baseParams[this.v13_ ? 'I' : 'X'] = x; + baseParams[this.v13_ ? 'J' : 'Y'] = y; return this.getRequestUrl_(tileCoord, tileSize, tileExtent, 1, projectionObj, baseParams); @@ -110624,7 +114269,7 @@ ol.source.TileWMS.prototype.getParams = function() { /** * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {number} tileSize Tile size. + * @param {ol.Size} tileSize Tile size. * @param {ol.Extent} tileExtent Tile extent. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. @@ -110641,14 +114286,14 @@ ol.source.TileWMS.prototype.getRequestUrl_ = return undefined; } - goog.object.set(params, 'WIDTH', tileSize); - goog.object.set(params, 'HEIGHT', tileSize); + params['WIDTH'] = tileSize[0]; + params['HEIGHT'] = tileSize[1]; params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode(); if (!('STYLES' in this.params_)) { /* jshint -W053 */ - goog.object.set(params, 'STYLES', new String('')); + params['STYLES'] = new String(''); /* jshint +W053 */ } @@ -110656,17 +114301,21 @@ ol.source.TileWMS.prototype.getRequestUrl_ = switch (this.serverType_) { case ol.source.wms.ServerType.GEOSERVER: var dpi = (90 * pixelRatio + 0.5) | 0; - goog.object.set(params, 'FORMAT_OPTIONS', 'dpi:' + dpi); + if (goog.isDef(params['FORMAT_OPTIONS'])) { + params['FORMAT_OPTIONS'] += ';dpi:' + dpi; + } else { + params['FORMAT_OPTIONS'] = 'dpi:' + dpi; + } break; case ol.source.wms.ServerType.MAPSERVER: - goog.object.set(params, 'MAP_RESOLUTION', 90 * pixelRatio); + params['MAP_RESOLUTION'] = 90 * pixelRatio; break; case ol.source.wms.ServerType.CARMENTA_SERVER: case ol.source.wms.ServerType.QGIS: - goog.object.set(params, 'DPI', 90 * pixelRatio); + params['DPI'] = 90 * pixelRatio; break; default: - goog.asserts.fail(); + goog.asserts.fail('unknown serverType configured'); break; } } @@ -110682,7 +114331,7 @@ ol.source.TileWMS.prototype.getRequestUrl_ = bbox[2] = tileExtent[3]; bbox[3] = tmp; } - goog.object.set(params, 'BBOX', bbox.join(',')); + params['BBOX'] = bbox.join(','); var url; if (urls.length == 1) { @@ -110699,7 +114348,7 @@ ol.source.TileWMS.prototype.getRequestUrl_ = * @param {number} z Z. * @param {number} pixelRatio Pixel ratio. * @param {ol.proj.Projection} projection Projection. - * @return {number} Size. + * @return {ol.Size} Size. */ ol.source.TileWMS.prototype.getTilePixelSize = function(z, pixelRatio, projection) { @@ -110707,13 +114356,13 @@ ol.source.TileWMS.prototype.getTilePixelSize = if (pixelRatio == 1 || !this.hidpi_ || !goog.isDef(this.serverType_)) { return tileSize; } else { - return (tileSize * pixelRatio + 0.5) | 0; + return ol.size.scale(tileSize, pixelRatio, this.tmpSize); } }; /** - * Return the URLs used for this WMS source. + * Return the URLs used for this WMS source. * @return {!Array.<string>} URLs. * @api stable */ @@ -110744,6 +114393,7 @@ ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() { /** + * Set the URL to use for requests. * @param {string|undefined} url URL. * @api stable */ @@ -110754,6 +114404,7 @@ ol.source.TileWMS.prototype.setUrl = function(url) { /** + * Set the URLs to use for requests. * @param {Array.<string>|undefined} urls URLs. * @api stable */ @@ -110788,19 +114439,19 @@ ol.source.TileWMS.prototype.tileUrlFunction_ = } var tileResolution = tileGrid.getResolution(tileCoord[0]); - var tileExtent = tileGrid.getTileCoordExtent( - tileCoord, this.tmpExtent_); - var tileSize = tileGrid.getTileSize(tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); + var tileSize = ol.size.toSize( + tileGrid.getTileSize(tileCoord[0]), this.tmpSize); var gutter = this.gutter_; if (gutter !== 0) { - tileSize += 2 * gutter; + tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize); tileExtent = ol.extent.buffer(tileExtent, tileResolution * gutter, tileExtent); } if (pixelRatio != 1) { - tileSize = (tileSize * pixelRatio + 0.5) | 0; + tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize); } var baseParams = { @@ -110839,43 +114490,6 @@ ol.source.TileWMS.prototype.updateV13_ = function() { this.v13_ = goog.string.compareVersions(version, '1.3') >= 0; }; -goog.provide('ol.source.TopoJSON'); - -goog.require('ol.format.TopoJSON'); -goog.require('ol.source.StaticVector'); - - - -/** - * @classdesc - * Static vector source in TopoJSON format - * - * @constructor - * @extends {ol.source.StaticVector} - * @fires ol.source.VectorEvent - * @param {olx.source.TopoJSONOptions=} opt_options Options. - * @api - */ -ol.source.TopoJSON = function(opt_options) { - - var options = goog.isDef(opt_options) ? opt_options : {}; - - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - format: new ol.format.TopoJSON({ - defaultDataProjection: options.defaultProjection - }), - logo: options.logo, - object: options.object, - projection: options.projection, - text: options.text, - url: options.url - }); - -}; -goog.inherits(ol.source.TopoJSON, ol.source.StaticVector); - goog.provide('ol.tilegrid.WMTS'); goog.require('goog.array'); @@ -110898,7 +114512,9 @@ goog.require('ol.tilegrid.TileGrid'); ol.tilegrid.WMTS = function(options) { goog.asserts.assert( - options.resolutions.length == options.matrixIds.length); + options.resolutions.length == options.matrixIds.length, + 'options resolutions and matrixIds must have equal length (%s == %s)', + options.resolutions.length, options.matrixIds.length); /** * @private @@ -110908,11 +114524,13 @@ ol.tilegrid.WMTS = function(options) { // FIXME: should the matrixIds become optionnal? goog.base(this, { + extent: options.extent, origin: options.origin, origins: options.origins, resolutions: options.resolutions, tileSize: options.tileSize, - tileSizes: options.tileSizes + tileSizes: options.tileSizes, + sizes: options.sizes }); }; @@ -110924,12 +114542,14 @@ goog.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid); * @return {string} MatrixId.. */ ol.tilegrid.WMTS.prototype.getMatrixId = function(z) { - goog.asserts.assert(0 <= z && z < this.matrixIds_.length); + goog.asserts.assert(0 <= z && z < this.matrixIds_.length, + 'attempted to retrive matrixId for illegal z (%s)', z); return this.matrixIds_[z]; }; /** + * Get the list of matrix identifiers. * @return {Array.<string>} MatrixIds. * @api */ @@ -110939,12 +114559,16 @@ ol.tilegrid.WMTS.prototype.getMatrixIds = function() { /** + * Create a tile grid from a WMTS capabilities matrix set. * @param {Object} matrixSet An object representing a matrixSet in the * capabilities document. + * @param {ol.Extent=} opt_extent An optional extent to restrict the tile + * ranges the server provides. * @return {ol.tilegrid.WMTS} WMTS tileGrid instance. + * @api */ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = - function(matrixSet) { + function(matrixSet, opt_extent) { /** @type {!Array.<number>} */ var resolutions = []; @@ -110952,19 +114576,25 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = var matrixIds = []; /** @type {!Array.<ol.Coordinate>} */ var origins = []; - /** @type {!Array.<number>} */ + /** @type {!Array.<ol.Size>} */ var tileSizes = []; + /** @type {!Array.<ol.Size>} */ + var sizes = []; - var supportedCRSPropName = 'supportedCRS'; - var matrixIdsPropName = 'matrixIds'; - var identifierPropName = 'identifier'; - var scaleDenominatorPropName = 'scaleDenominator'; - var topLeftCornerPropName = 'topLeftCorner'; - var tileWidthPropName = 'tileWidth'; - var tileHeightPropName = 'tileHeight'; + var supportedCRSPropName = 'SupportedCRS'; + var matrixIdsPropName = 'TileMatrix'; + var identifierPropName = 'Identifier'; + var scaleDenominatorPropName = 'ScaleDenominator'; + var topLeftCornerPropName = 'TopLeftCorner'; + var tileWidthPropName = 'TileWidth'; + var tileHeightPropName = 'TileHeight'; - var projection = ol.proj.get(matrixSet[supportedCRSPropName]); + var projection; + projection = ol.proj.get(matrixSet[supportedCRSPropName].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); var metersPerUnit = projection.getMetersPerUnit(); + // swap origin x and y coordinates if axis orientation is lat/long + var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne'; goog.array.sort(matrixSet[matrixIdsPropName], function(a, b) { return b[scaleDenominatorPropName] - a[scaleDenominatorPropName]; @@ -110973,20 +114603,30 @@ ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = goog.array.forEach(matrixSet[matrixIdsPropName], function(elt, index, array) { matrixIds.push(elt[identifierPropName]); - origins.push(elt[topLeftCornerPropName]); - resolutions.push(elt[scaleDenominatorPropName] * 0.28E-3 / - metersPerUnit); + var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / + metersPerUnit; var tileWidth = elt[tileWidthPropName]; var tileHeight = elt[tileHeightPropName]; - goog.asserts.assert(tileWidth == tileHeight); - tileSizes.push(tileWidth); + if (switchOriginXY) { + origins.push([elt[topLeftCornerPropName][1], + elt[topLeftCornerPropName][0]]); + } else { + origins.push(elt[topLeftCornerPropName]); + } + resolutions.push(resolution); + tileSizes.push(tileWidth == tileHeight ? + tileWidth : [tileWidth, tileHeight]); + // top-left origin, so height is negative + sizes.push([elt['MatrixWidth'], -elt['MatrixHeight']]); }); return new ol.tilegrid.WMTS({ + extent: opt_extent, origins: origins, resolutions: resolutions, matrixIds: matrixIds, - tileSizes: tileSizes + tileSizes: tileSizes, + sizes: sizes }); }; @@ -110995,15 +114635,14 @@ goog.provide('ol.source.WMTSRequestEncoding'); goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.math'); goog.require('goog.object'); +goog.require('goog.string'); goog.require('goog.uri.utils'); goog.require('ol.TileUrlFunction'); goog.require('ol.TileUrlFunctionType'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.TileImage'); -goog.require('ol.tilecoord'); goog.require('ol.tilegrid.WMTS'); @@ -111026,14 +114665,23 @@ ol.source.WMTSRequestEncoding = { * @constructor * @extends {ol.source.TileImage} * @param {olx.source.WMTSOptions} options WMTS options. - * @api + * @api stable */ ol.source.WMTS = function(options) { // TODO: add support for TileMatrixLimits - var version = goog.isDef(options.version) ? options.version : '1.0.0'; - var format = goog.isDef(options.format) ? options.format : 'image/jpeg'; + /** + * @private + * @type {string} + */ + this.version_ = goog.isDef(options.version) ? options.version : '1.0.0'; + + /** + * @private + * @type {string} + */ + this.format_ = goog.isDef(options.format) ? options.format : 'image/jpeg'; /** * @private @@ -111048,28 +114696,66 @@ ol.source.WMTS = function(options) { this.coordKeyPrefix_ = ''; this.resetCoordKeyPrefix_(); + /** + * @private + * @type {string} + */ + this.layer_ = options.layer; + + /** + * @private + * @type {string} + */ + this.matrixSet_ = options.matrixSet; + + /** + * @private + * @type {string} + */ + this.style_ = options.style; + + var urls = options.urls; + if (!goog.isDef(urls) && goog.isDef(options.url)) { + urls = ol.TileUrlFunction.expandUrl(options.url); + } + + /** + * @private + * @type {!Array.<string>} + */ + this.urls_ = goog.isDefAndNotNull(urls) ? urls : []; + // FIXME: should we guess this requestEncoding from options.url(s) // structure? that would mean KVP only if a template is not provided. - var requestEncoding = goog.isDef(options.requestEncoding) ? + + /** + * @private + * @type {ol.source.WMTSRequestEncoding} + */ + this.requestEncoding_ = goog.isDef(options.requestEncoding) ? /** @type {ol.source.WMTSRequestEncoding} */ (options.requestEncoding) : ol.source.WMTSRequestEncoding.KVP; + var requestEncoding = this.requestEncoding_; + // FIXME: should we create a default tileGrid? // we could issue a getCapabilities xhr to retrieve missing configuration var tileGrid = options.tileGrid; + // context property names are lower case to allow for a case insensitive + // replacement as some services use different naming conventions var context = { - 'Layer': options.layer, - 'Style': options.style, - 'TileMatrixSet': options.matrixSet + 'layer': this.layer_, + 'style': this.style_, + 'tilematrixset': this.matrixSet_ }; if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) { goog.object.extend(context, { 'Service': 'WMTS', 'Request': 'GetTile', - 'Version': version, - 'Format': format + 'Version': this.version_, + 'Format': this.format_ }); } @@ -111088,7 +114774,7 @@ ol.source.WMTS = function(options) { template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ? goog.uri.utils.appendParamsFromMap(template, context) : template.replace(/\{(\w+?)\}/g, function(m, p) { - return (p in context) ? context[p] : m; + return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m; }); return ( @@ -111105,7 +114791,7 @@ ol.source.WMTS = function(options) { var localContext = { 'TileMatrix': tileGrid.getMatrixId(tileCoord[0]), 'TileCol': tileCoord[1], - 'TileRow': tileCoord[2] + 'TileRow': -tileCoord[2] - 1 }; goog.object.extend(localContext, dimensions); var url = template; @@ -111121,52 +114807,10 @@ ol.source.WMTS = function(options) { }); } - var tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction; - var urls = options.urls; - if (!goog.isDef(urls) && goog.isDef(options.url)) { - urls = ol.TileUrlFunction.expandUrl(options.url); - } - if (goog.isDef(urls)) { - tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions( - goog.array.map(urls, createFromWMTSTemplate)); - } - - var tmpExtent = ol.extent.createEmpty(); - var tmpTileCoord = [0, 0, 0]; - tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform( - /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.TileCoord=} opt_tileCoord Tile coordinate. - * @return {ol.TileCoord} Tile coordinate. - */ - function(tileCoord, projection, opt_tileCoord) { - goog.asserts.assert(!goog.isNull(tileGrid)); - if (tileGrid.getResolutions().length <= tileCoord[0]) { - return null; - } - var x = tileCoord[1]; - var y = -tileCoord[2] - 1; - var tileExtent = tileGrid.getTileCoordExtent(tileCoord, tmpExtent); - var extent = projection.getExtent(); - - if (!goog.isNull(extent) && projection.isGlobal()) { - var numCols = Math.ceil( - ol.extent.getWidth(extent) / - ol.extent.getWidth(tileExtent)); - x = goog.math.modulo(x, numCols); - tmpTileCoord[0] = tileCoord[0]; - tmpTileCoord[1] = x; - tmpTileCoord[2] = tileCoord[2]; - tileExtent = tileGrid.getTileCoordExtent(tmpTileCoord, tmpExtent); - } - if (!ol.extent.intersects(tileExtent, extent) || - ol.extent.touches(tileExtent, extent)) { - return null; - } - return ol.tilecoord.createOrUpdate(tileCoord[0], x, y, opt_tileCoord); - }, - tileUrlFunction); + var tileUrlFunction = this.urls_.length > 0 ? + ol.TileUrlFunction.createFromTileUrlFunctions( + goog.array.map(this.urls_, createFromWMTSTemplate)) : + ol.TileUrlFunction.nullTileUrlFunction; goog.base(this, { attributions: options.attributions, @@ -111177,7 +114821,8 @@ ol.source.WMTS = function(options) { tileGrid: tileGrid, tileLoadFunction: options.tileLoadFunction, tilePixelRatio: options.tilePixelRatio, - tileUrlFunction: tileUrlFunction + tileUrlFunction: tileUrlFunction, + wrapX: goog.isDef(options.wrapX) ? options.wrapX : false }); }; @@ -111196,6 +114841,16 @@ ol.source.WMTS.prototype.getDimensions = function() { }; +/** + * Return the image format of the WMTS source. + * @return {string} Format. + * @api + */ +ol.source.WMTS.prototype.getFormat = function() { + return this.format_; +}; + + /** * @inheritDoc */ @@ -111204,6 +114859,66 @@ ol.source.WMTS.prototype.getKeyZXY = function(z, x, y) { }; +/** + * Return the layer of the WMTS source. + * @return {string} Layer. + * @api + */ +ol.source.WMTS.prototype.getLayer = function() { + return this.layer_; +}; + + +/** + * Return the matrix set of the WMTS source. + * @return {string} MatrixSet. + * @api + */ +ol.source.WMTS.prototype.getMatrixSet = function() { + return this.matrixSet_; +}; + + +/** + * Return the request encoding, either "KVP" or "REST". + * @return {ol.source.WMTSRequestEncoding} Request encoding. + * @api + */ +ol.source.WMTS.prototype.getRequestEncoding = function() { + return this.requestEncoding_; +}; + + +/** + * Return the style of the WMTS source. + * @return {string} Style. + * @api + */ +ol.source.WMTS.prototype.getStyle = function() { + return this.style_; +}; + + +/** + * Return the URLs used for this WMTS source. + * @return {!Array.<string>} URLs. + * @api + */ +ol.source.WMTS.prototype.getUrls = function() { + return this.urls_; +}; + + +/** + * Return the version of the WMTS source. + * @return {string} Version. + * @api + */ +ol.source.WMTS.prototype.getVersion = function() { + return this.version_; +}; + + /** * @private */ @@ -111231,183 +114946,197 @@ ol.source.WMTS.prototype.updateDimensions = function(dimensions) { /** * @param {Object} wmtsCap An object representing the capabilities document. - * @param {string} layer The layer identifier. + * @param {Object} config Configuration properties for the layer. Defaults for + * the layer will apply if not provided. + * + * Required config properties: + * layer - {String} The layer identifier. + * + * Optional config properties: + * matrixSet - {String} The matrix set identifier, required if there is + * more than one matrix set in the layer capabilities. + * projection - {String} The desired CRS when no matrixSet is specified. + * eg: "EPSG:3857". If the desired projection is not available, + * an error is thrown. + * requestEncoding - {String} url encoding format for the layer. Default is the + * first tile url format found in the GetCapabilities response. + * style - {String} The name of the style + * format - {String} Image format for the layer. Default is the first + * format returned in the GetCapabilities response. * @return {olx.source.WMTSOptions} WMTS source options object. + * @api */ -ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, layer) { +ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) { /* jshint -W069 */ // TODO: add support for TileMatrixLimits + goog.asserts.assert(!goog.isNull(config['layer']), + 'config "layer" must not be null'); - var layers = wmtsCap['contents']['layers']; + var layers = wmtsCap['Contents']['Layer']; var l = goog.array.find(layers, function(elt, index, array) { - return elt['identifier'] == layer; - }); - goog.asserts.assert(!goog.isNull(l)); - goog.asserts.assert(l['tileMatrixSetLinks'].length > 0); - var matrixSet = /** @type {string} */ - (l['tileMatrixSetLinks'][0]['tileMatrixSet']); - var format = /** @type {string} */ (l['formats'][0]); - var idx = goog.array.findIndex(l['styles'], function(elt, index, array) { - return elt['isDefault']; + return elt['Identifier'] == config['layer']; }); + goog.asserts.assert(!goog.isNull(l), + 'found a matching layer in Contents/Layer'); + + goog.asserts.assert(l['TileMatrixSetLink'].length > 0, + 'layer has TileMatrixSetLink'); + var tileMatrixSets = wmtsCap['Contents']['TileMatrixSet']; + var idx, matrixSet; + if (l['TileMatrixSetLink'].length > 1) { + if (goog.isDef(config['projection'])) { + idx = goog.array.findIndex(l['TileMatrixSetLink'], + function(elt, index, array) { + var tileMatrixSet = goog.array.find(tileMatrixSets, function(el) { + return el['Identifier'] == elt['TileMatrixSet']; + }); + return tileMatrixSet['SupportedCRS'].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3' + ) == config['projection']; + }); + } else { + idx = goog.array.findIndex(l['TileMatrixSetLink'], + function(elt, index, array) { + return elt['TileMatrixSet'] == config['matrixSet']; + }); + } + } else { + idx = 0; + } if (idx < 0) { idx = 0; } - var style = /** @type {string} */ (l['styles'][idx]['identifier']); + matrixSet = /** @type {string} */ + (l['TileMatrixSetLink'][idx]['TileMatrixSet']); - var dimensions = {}; - goog.array.forEach(l['dimensions'], function(elt, index, array) { - var key = elt['identifier']; - var value = elt['default']; - if (goog.isDef(value)) { - goog.asserts.assert(goog.array.contains(elt['values'], value)); + goog.asserts.assert(!goog.isNull(matrixSet), + 'TileMatrixSet must not be null'); + + var format = /** @type {string} */ (l['Format'][0]); + if (goog.isDef(config['format'])) { + format = config['format']; + } + idx = goog.array.findIndex(l['Style'], function(elt, index, array) { + if (goog.isDef(config['style'])) { + return elt['Title'] == config['style']; } else { - value = elt['values'][0]; + return elt['isDefault']; } - goog.asserts.assert(goog.isDef(value)); - dimensions[key] = value; }); + if (idx < 0) { + idx = 0; + } + var style = /** @type {string} */ (l['Style'][idx]['Identifier']); + + var dimensions = {}; + if (goog.isDef(l['Dimension'])) { + goog.array.forEach(l['Dimension'], function(elt, index, array) { + var key = elt['Identifier']; + var value = elt['default']; + if (goog.isDef(value)) { + goog.asserts.assert(goog.array.contains(elt['values'], value), + 'default value contained in values'); + } else { + value = elt['values'][0]; + } + goog.asserts.assert(goog.isDef(value), 'value could be found'); + dimensions[key] = value; + }); + } + + var matrixSets = wmtsCap['Contents']['TileMatrixSet']; + var matrixSetObj = goog.array.find(matrixSets, function(elt, index, array) { + return elt['Identifier'] == matrixSet; + }); + goog.asserts.assert(!goog.isNull(matrixSetObj), + 'found matrixSet in Contents/TileMatrixSet'); - var matrixSets = wmtsCap['contents']['tileMatrixSets']; - goog.asserts.assert(matrixSet in matrixSets); - var matrixSetObj = matrixSets[matrixSet]; + var projection; + if (goog.isDef(config['projection'])) { + projection = ol.proj.get(config['projection']); + } else { + projection = ol.proj.get(matrixSetObj['SupportedCRS'].replace( + /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3')); + } + + var wgs84BoundingBox = l['WGS84BoundingBox']; + var extent, wrapX; + if (goog.isDef(wgs84BoundingBox)) { + var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent(); + wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] && + wgs84BoundingBox[2] == wgs84ProjectionExtent[2]); + extent = ol.proj.transformExtent( + wgs84BoundingBox, 'EPSG:4326', projection); + var projectionExtent = projection.getExtent(); + if (!goog.isNull(projectionExtent)) { + // If possible, do a sanity check on the extent - it should never be + // bigger than the validity extent of the projection of a matrix set. + if (!ol.extent.containsExtent(projectionExtent, extent)) { + extent = undefined; + } + } + } var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet( - matrixSetObj); - - var projection = ol.proj.get(matrixSetObj['supportedCRS']); - - var gets = wmtsCap['operationsMetadata']['GetTile']['dcp']['http']['get']; - var encodings = goog.object.getKeys( - gets[0]['constraints']['GetEncoding']['allowedValues']); - goog.asserts.assert(encodings.length > 0); - - var urls; - var requestEncoding; - switch (encodings[0]) { - case 'REST': - case 'RESTful': - // The OGC documentation is not clear if we should use REST or RESTful, - // ArcGis use RESTful, and OpenLayers use REST. - requestEncoding = ol.source.WMTSRequestEncoding.REST; - goog.asserts.assert(l['resourceUrls'].hasOwnProperty('tile')); - goog.asserts.assert(l['resourceUrls']['tile'].hasOwnProperty(format)); - urls = /** @type {Array.<string>} */ - (l['resourceUrls']['tile'][format]); - break; - case 'KVP': - requestEncoding = ol.source.WMTSRequestEncoding.KVP; - urls = []; - goog.array.forEach(gets, function(elt, index, array) { - if (elt['constraints']['GetEncoding']['allowedValues'].hasOwnProperty( - ol.source.WMTSRequestEncoding.KVP)) { - urls.push(/** @type {string} */ (elt['url'])); - } - }); - goog.asserts.assert(urls.length > 0); - break; - default: - goog.asserts.fail(); + matrixSetObj, extent); + + /** @type {!Array.<string>} */ + var urls = []; + var requestEncoding = config['requestEncoding']; + requestEncoding = goog.isDef(requestEncoding) ? requestEncoding : ''; + + goog.asserts.assert( + goog.array.contains(['REST', 'RESTful', 'KVP', ''], requestEncoding), + 'requestEncoding (%s) is one of "REST", "RESTful", "KVP" or ""', + requestEncoding); + + if (!wmtsCap.hasOwnProperty('OperationsMetadata') || + !wmtsCap['OperationsMetadata'].hasOwnProperty('GetTile') || + goog.string.startsWith(requestEncoding, 'REST')) { + // Add REST tile resource url + requestEncoding = ol.source.WMTSRequestEncoding.REST; + goog.array.forEach(l['ResourceURL'], function(elt, index, array) { + if (elt['resourceType'] == 'tile') { + format = elt['format']; + urls.push(/** @type {string} */ (elt['template'])); + } + }); + } else { + var gets = wmtsCap['OperationsMetadata']['GetTile']['DCP']['HTTP']['Get']; + + for (var i = 0, ii = gets.length; i < ii; ++i) { + var constraint = goog.array.find(gets[i]['Constraint'], + function(elt, index, array) { + return elt['name'] == 'GetEncoding'; + }); + var encodings = constraint['AllowedValues']['Value']; + if (encodings.length > 0 && goog.array.contains(encodings, 'KVP')) { + requestEncoding = ol.source.WMTSRequestEncoding.KVP; + urls.push(/** @type {string} */ (gets[i]['href'])); + } + } } + goog.asserts.assert(urls.length > 0, 'At least one URL found'); return { urls: urls, - layer: layer, + layer: config['layer'], matrixSet: matrixSet, format: format, projection: projection, requestEncoding: requestEncoding, tileGrid: tileGrid, style: style, - dimensions: dimensions + dimensions: dimensions, + wrapX: wrapX }; /* jshint +W069 */ }; -goog.provide('ol.tilegrid.Zoomify'); - -goog.require('goog.math'); -goog.require('ol.TileCoord'); -goog.require('ol.proj'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); - - - -/** - * @classdesc - * Set the grid pattern for sources accessing Zoomify tiled-image servers. - * - * @constructor - * @extends {ol.tilegrid.TileGrid} - * @param {olx.tilegrid.ZoomifyOptions=} opt_options Options. - * @api - */ -ol.tilegrid.Zoomify = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : options; - goog.base(this, { - origin: [0, 0], - resolutions: options.resolutions - }); - -}; -goog.inherits(ol.tilegrid.Zoomify, ol.tilegrid.TileGrid); - - -/** - * @inheritDoc - */ -ol.tilegrid.Zoomify.prototype.createTileCoordTransform = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - var minZ = this.minZoom; - var maxZ = this.maxZoom; - /** @type {Array.<ol.TileRange>} */ - var tileRangeByZ = null; - if (goog.isDef(options.extent)) { - tileRangeByZ = new Array(maxZ + 1); - var z; - for (z = 0; z <= maxZ; ++z) { - if (z < minZ) { - tileRangeByZ[z] = null; - } else { - tileRangeByZ[z] = this.getTileRangeForExtentAndZ(options.extent, z); - } - } - } - return ( - /** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection} projection Projection. - * @param {ol.TileCoord=} opt_tileCoord Destination tile coordinate. - * @return {ol.TileCoord} Tile coordinate. - */ - function(tileCoord, projection, opt_tileCoord) { - var z = tileCoord[0]; - if (z < minZ || maxZ < z) { - return null; - } - var n = Math.pow(2, z); - var x = tileCoord[1]; - if (x < 0 || n <= x) { - return null; - } - var y = tileCoord[2]; - if (y < -n || -1 < y) { - return null; - } - if (!goog.isNull(tileRangeByZ)) { - if (!tileRangeByZ[z].containsXY(x, -y - 1)) { - return null; - } - } - return ol.tilecoord.createOrUpdate(z, x, -y - 1, opt_tileCoord); - }); -}; - goog.provide('ol.source.Zoomify'); goog.require('goog.asserts'); @@ -111415,11 +115144,11 @@ goog.require('ol'); goog.require('ol.ImageTile'); goog.require('ol.TileCoord'); goog.require('ol.TileState'); -goog.require('ol.TileUrlFunction'); goog.require('ol.dom'); +goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.TileImage'); -goog.require('ol.tilegrid.Zoomify'); +goog.require('ol.tilegrid.TileGrid'); /** @@ -111497,36 +115226,38 @@ ol.source.Zoomify = function(opt_options) { } resolutions.reverse(); - var tileGrid = new ol.tilegrid.Zoomify({ + var extent = [0, -size[1], size[0], 0]; + var tileGrid = new ol.tilegrid.TileGrid({ + extent: extent, + origin: ol.extent.getTopLeft(extent), resolutions: resolutions }); var url = options.url; - var tileUrlFunction = ol.TileUrlFunction.withTileCoordTransform( - tileGrid.createTileCoordTransform({extent: [0, 0, size[0], size[1]]}), - /** - * @this {ol.source.TileImage} - * @param {ol.TileCoord} tileCoord Tile Coordinate. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. - * @return {string|undefined} Tile URL. - */ - function(tileCoord, pixelRatio, projection) { - if (goog.isNull(tileCoord)) { - return undefined; - } else { - var tileCoordZ = tileCoord[0]; - var tileCoordX = tileCoord[1]; - var tileCoordY = tileCoord[2]; - var tileIndex = - tileCoordX + - tileCoordY * tierSizeInTiles[tileCoordZ][0] + - tileCountUpToTier[tileCoordZ]; - var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0; - return url + 'TileGroup' + tileGroup + '/' + - tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg'; - } - }); + + /** + * @this {ol.source.TileImage} + * @param {ol.TileCoord} tileCoord Tile Coordinate. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @return {string|undefined} Tile URL. + */ + function tileUrlFunction(tileCoord, pixelRatio, projection) { + if (goog.isNull(tileCoord)) { + return undefined; + } else { + var tileCoordZ = tileCoord[0]; + var tileCoordX = tileCoord[1]; + var tileCoordY = -tileCoord[2] - 1; + var tileIndex = + tileCoordX + + tileCoordY * tierSizeInTiles[tileCoordZ][0] + + tileCountUpToTier[tileCoordZ]; + var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0; + return url + 'TileGroup' + tileGroup + '/' + + tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg'; + } + } goog.base(this, { attributions: options.attributions, @@ -111600,6 +115331,7 @@ goog.provide('ol.style.AtlasManager'); goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); +goog.require('goog.functions'); goog.require('goog.object'); goog.require('ol'); @@ -111607,12 +115339,10 @@ goog.require('ol'); /** * Provides information for an image inside an atlas manager. * `offsetX` and `offsetY` is the position of the image inside - * the atlas image `image`. - * `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image - * inside the hit-detection atlas image `hitImage` (only when a hit-detection - * image was created for this image). + * the atlas image `image` and the position of the hit-detection image + * inside the hit-detection atlas image `hitImage`. * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement, - * hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}} + * hitImage: HTMLCanvasElement}} */ ol.style.AtlasManagerInfo; @@ -111699,6 +115429,8 @@ ol.style.AtlasManager.prototype.getInfo = function(id) { } /** @type {?ol.style.AtlasInfo} */ var hitInfo = this.getInfo_(this.hitAtlases_, id); + goog.asserts.assert(!goog.isNull(hitInfo), + 'hitInfo must not be null'); return this.mergeInfos_(info, hitInfo); }; @@ -111727,19 +115459,21 @@ ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) { /** * @private * @param {ol.style.AtlasInfo} info The info for the real image. - * @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection + * @param {ol.style.AtlasInfo} hitInfo The info for the hit-detection * image. * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the * entry, or `null` if the entry is not part of the atlases. */ ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) { + goog.asserts.assert(info.offsetX === hitInfo.offsetX, + 'in order to merge, offsetX of info and hitInfo must be equal'); + goog.asserts.assert(info.offsetY === hitInfo.offsetY, + 'in order to merge, offsetY of info and hitInfo must be equal'); return /** @type {ol.style.AtlasManagerInfo} */ ({ offsetX: info.offsetX, offsetY: info.offsetY, image: info.image, - hitOffsetX: goog.isNull(hitInfo) ? undefined : hitInfo.offsetX, - hitOffsetY: goog.isNull(hitInfo) ? undefined : hitInfo.offsetY, - hitImage: goog.isNull(hitInfo) ? undefined : hitInfo.image + hitImage: hitInfo.image }); }; @@ -111781,12 +115515,18 @@ ol.style.AtlasManager.prototype.add = return null; } + // even if no hit-detection entry is requested, we insert a fake entry into + // the hit-detection atlas, to make sure that the offset is the same for + // the original image and the hit-detection image. + var renderHitCallback = goog.isDef(opt_renderHitCallback) ? + opt_renderHitCallback : goog.functions.NULL; + /** @type {?ol.style.AtlasInfo} */ - var hitInfo = null; - if (goog.isDef(opt_renderHitCallback)) { - hitInfo = this.add_(true, - id, width, height, opt_renderHitCallback, opt_this); - } + var hitInfo = this.add_(true, + id, width, height, renderHitCallback, opt_this); + goog.asserts.assert(!goog.isNull(hitInfo), + 'hitInfo must not be null'); + return this.mergeInfos_(info, hitInfo); }; @@ -111831,7 +115571,7 @@ ol.style.AtlasManager.prototype.add_ = ++ii; } } - goog.asserts.fail(); + goog.asserts.fail('Failed to add to atlasmanager'); }; @@ -112043,6 +115783,8 @@ goog.require('goog.dom.TagName'); goog.require('ol.color'); goog.require('ol.has'); goog.require('ol.render.canvas'); +goog.require('ol.structs.IHasChecksum'); +goog.require('ol.style.AtlasManager'); goog.require('ol.style.Fill'); goog.require('ol.style.Image'); goog.require('ol.style.ImageState'); @@ -112064,8 +115806,9 @@ goog.require('ol.style.Stroke'); */ ol.style.RegularShape = function(options) { - goog.asserts.assert(goog.isDef(options.radius) || - goog.isDef(options.radius1)); + goog.asserts.assert( + goog.isDef(options.radius) || goog.isDef(options.radius1), + 'must provide either "radius" or "radius1"'); /** * @private @@ -112097,12 +115840,6 @@ ol.style.RegularShape = function(options) { */ this.origin_ = [0, 0]; - /** - * @private - * @type {Array.<number>} - */ - this.hitDetectionOrigin_ = [0, 0]; - /** * @private * @type {number} @@ -112189,6 +115926,7 @@ ol.style.RegularShape.prototype.getAnchor = function() { /** + * Get the angle used in generating the shape. * @return {number} Shape's rotation in radians. * @api */ @@ -112198,6 +115936,7 @@ ol.style.RegularShape.prototype.getAngle = function() { /** + * Get the fill style for the shape. * @return {ol.style.Fill} Fill style. * @api */ @@ -112257,14 +115996,7 @@ ol.style.RegularShape.prototype.getOrigin = function() { /** - * @inheritDoc - */ -ol.style.RegularShape.prototype.getHitDetectionOrigin = function() { - return this.hitDetectionOrigin_; -}; - - -/** + * Get the number of points for generating the shape. * @return {number} Number of points for stars and regular polygons. * @api */ @@ -112274,6 +116006,7 @@ ol.style.RegularShape.prototype.getPoints = function() { /** + * Get the (primary) radius for the shape. * @return {number} Radius. * @api */ @@ -112283,6 +116016,7 @@ ol.style.RegularShape.prototype.getRadius = function() { /** + * Get the secondary radius for the shape. * @return {number} Radius2. * @api */ @@ -112301,6 +116035,7 @@ ol.style.RegularShape.prototype.getSize = function() { /** + * Get the stroke style for the shape. * @return {ol.style.Stroke} Stroke style. * @api */ @@ -112328,8 +116063,15 @@ ol.style.RegularShape.prototype.unlistenImageChange = goog.nullFunction; /** - * @typedef {{strokeStyle: (string|undefined), strokeWidth: number, - * size: number, lineDash: Array.<number>}} + * @typedef {{ + * strokeStyle: (string|undefined), + * strokeWidth: number, + * size: number, + * lineCap: string, + * lineDash: Array.<number>, + * lineJoin: string, + * miterLimit: number + * }} */ ol.style.RegularShape.RenderOptions; @@ -112340,6 +116082,9 @@ ol.style.RegularShape.RenderOptions; */ ol.style.RegularShape.prototype.render_ = function(atlasManager) { var imageSize; + var lineCap = ''; + var lineJoin = ''; + var miterLimit = 0; var lineDash = null; var strokeStyle; var strokeWidth = 0; @@ -112354,6 +116099,18 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) { if (!ol.has.CANVAS_LINE_DASH) { lineDash = null; } + lineJoin = this.stroke_.getLineJoin(); + if (!goog.isDef(lineJoin)) { + lineJoin = ol.render.canvas.defaultLineJoin; + } + lineCap = this.stroke_.getLineCap(); + if (!goog.isDef(lineCap)) { + lineCap = ol.render.canvas.defaultLineCap; + } + miterLimit = this.stroke_.getMiterLimit(); + if (!goog.isDef(miterLimit)) { + miterLimit = ol.render.canvas.defaultMiterLimit; + } } var size = 2 * (this.radius_ + strokeWidth) + 1; @@ -112363,7 +116120,10 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) { strokeStyle: strokeStyle, strokeWidth: strokeWidth, size: size, - lineDash: lineDash + lineCap: lineCap, + lineDash: lineDash, + lineJoin: lineJoin, + miterLimit: miterLimit }; if (!goog.isDef(atlasManager)) { @@ -112407,12 +116167,10 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) { if (hasCustomHitDetectionImage) { this.hitDetectionCanvas_ = info.hitImage; - this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY]; this.hitDetectionImageSize_ = [info.hitImage.width, info.hitImage.height]; } else { this.hitDetectionCanvas_ = this.canvas_; - this.hitDetectionOrigin_ = this.origin_; this.hitDetectionImageSize_ = [imageSize, imageSize]; } } @@ -112425,7 +116183,7 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) { /** * @private - * @param {ol.style.Circle.RenderOptions} renderOptions + * @param {ol.style.RegularShape.RenderOptions} renderOptions * @param {CanvasRenderingContext2D} context * @param {number} x The origin for the symbol (x). * @param {number} y The origin for the symbol (y). @@ -112459,6 +116217,9 @@ ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) { if (!goog.isNull(renderOptions.lineDash)) { context.setLineDash(renderOptions.lineDash); } + context.lineCap = renderOptions.lineCap; + context.lineJoin = renderOptions.lineJoin; + context.miterLimit = renderOptions.miterLimit; context.stroke(); } context.closePath(); @@ -112595,21 +116356,23 @@ goog.require('ol.Attribution'); goog.require('ol.Collection'); goog.require('ol.CollectionEvent'); goog.require('ol.CollectionEventType'); +goog.require('ol.Color'); goog.require('ol.Coordinate'); goog.require('ol.CoordinateFormatType'); goog.require('ol.DeviceOrientation'); goog.require('ol.DeviceOrientationProperty'); goog.require('ol.DragBoxEvent'); -goog.require('ol.DrawEvent'); goog.require('ol.Extent'); goog.require('ol.Feature'); -goog.require('ol.FeatureOverlay'); +goog.require('ol.FeatureLoader'); +goog.require('ol.FeatureStyleFunction'); goog.require('ol.Geolocation'); goog.require('ol.GeolocationProperty'); goog.require('ol.Graticule'); goog.require('ol.Image'); goog.require('ol.ImageTile'); goog.require('ol.Kinetic'); +goog.require('ol.LoadingStrategy'); goog.require('ol.Map'); goog.require('ol.MapBrowserEvent'); goog.require('ol.MapBrowserEvent.EventType'); @@ -112625,11 +116388,13 @@ goog.require('ol.Observable'); goog.require('ol.Overlay'); goog.require('ol.OverlayPositioning'); goog.require('ol.OverlayProperty'); +goog.require('ol.Size'); goog.require('ol.Sphere'); goog.require('ol.Tile'); goog.require('ol.TileState'); goog.require('ol.View'); goog.require('ol.ViewHint'); +goog.require('ol.ViewProperty'); goog.require('ol.animation'); goog.require('ol.color'); goog.require('ol.control'); @@ -112646,15 +116411,14 @@ goog.require('ol.control.Zoom'); goog.require('ol.control.ZoomSlider'); goog.require('ol.control.ZoomToExtent'); goog.require('ol.coordinate'); -goog.require('ol.dom.Input'); -goog.require('ol.dom.InputProperty'); goog.require('ol.easing'); goog.require('ol.events.ConditionType'); goog.require('ol.events.condition'); goog.require('ol.extent'); goog.require('ol.extent.Corner'); goog.require('ol.extent.Relationship'); -goog.require('ol.feature'); +goog.require('ol.featureloader'); +goog.require('ol.format.EsriJSON'); goog.require('ol.format.Feature'); goog.require('ol.format.GML'); goog.require('ol.format.GML2'); @@ -112672,9 +116436,11 @@ goog.require('ol.format.WFS'); goog.require('ol.format.WKT'); goog.require('ol.format.WMSCapabilities'); goog.require('ol.format.WMSGetFeatureInfo'); +goog.require('ol.format.WMTSCapabilities'); goog.require('ol.geom.Circle'); goog.require('ol.geom.Geometry'); goog.require('ol.geom.GeometryCollection'); +goog.require('ol.geom.GeometryLayout'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.LineString'); goog.require('ol.geom.LinearRing'); @@ -112695,6 +116461,10 @@ goog.require('ol.interaction.DragRotate'); goog.require('ol.interaction.DragRotateAndZoom'); goog.require('ol.interaction.DragZoom'); goog.require('ol.interaction.Draw'); +goog.require('ol.interaction.DrawEvent'); +goog.require('ol.interaction.DrawEventType'); +goog.require('ol.interaction.DrawGeometryFunctionType'); +goog.require('ol.interaction.DrawMode'); goog.require('ol.interaction.Interaction'); goog.require('ol.interaction.InteractionProperty'); goog.require('ol.interaction.KeyboardPan'); @@ -112705,6 +116475,9 @@ goog.require('ol.interaction.PinchRotate'); goog.require('ol.interaction.PinchZoom'); goog.require('ol.interaction.Pointer'); goog.require('ol.interaction.Select'); +goog.require('ol.interaction.SelectFilterFunction'); +goog.require('ol.interaction.Snap'); +goog.require('ol.interaction.SnapProperty'); goog.require('ol.layer.Base'); goog.require('ol.layer.Group'); goog.require('ol.layer.Heatmap'); @@ -112723,38 +116496,36 @@ goog.require('ol.proj.Units'); goog.require('ol.proj.common'); goog.require('ol.render.Event'); goog.require('ol.render.EventType'); +goog.require('ol.render.VectorContext'); goog.require('ol.render.canvas.Immediate'); goog.require('ol.render.webgl.Immediate'); +goog.require('ol.size'); goog.require('ol.source.BingMaps'); goog.require('ol.source.Cluster'); -goog.require('ol.source.FormatVector'); -goog.require('ol.source.GPX'); -goog.require('ol.source.GeoJSON'); -goog.require('ol.source.IGC'); goog.require('ol.source.Image'); goog.require('ol.source.ImageCanvas'); goog.require('ol.source.ImageMapGuide'); goog.require('ol.source.ImageStatic'); goog.require('ol.source.ImageVector'); goog.require('ol.source.ImageWMS'); -goog.require('ol.source.KML'); goog.require('ol.source.MapQuest'); goog.require('ol.source.OSM'); -goog.require('ol.source.OSMXML'); -goog.require('ol.source.ServerVector'); +goog.require('ol.source.Raster'); +goog.require('ol.source.RasterEvent'); +goog.require('ol.source.RasterEventType'); goog.require('ol.source.Source'); goog.require('ol.source.Stamen'); goog.require('ol.source.State'); -goog.require('ol.source.StaticVector'); goog.require('ol.source.Tile'); +goog.require('ol.source.TileArcGISRest'); goog.require('ol.source.TileDebug'); +goog.require('ol.source.TileEvent'); goog.require('ol.source.TileImage'); goog.require('ol.source.TileJSON'); goog.require('ol.source.TileOptions'); goog.require('ol.source.TileUTFGrid'); goog.require('ol.source.TileVector'); goog.require('ol.source.TileWMS'); -goog.require('ol.source.TopoJSON'); goog.require('ol.source.Vector'); goog.require('ol.source.VectorEvent'); goog.require('ol.source.VectorEventType'); @@ -112766,6 +116537,7 @@ goog.require('ol.style.Atlas'); goog.require('ol.style.AtlasManager'); goog.require('ol.style.Circle'); goog.require('ol.style.Fill'); +goog.require('ol.style.GeometryFunction'); goog.require('ol.style.Icon'); goog.require('ol.style.IconAnchorUnits'); goog.require('ol.style.IconImageCache'); @@ -112775,12 +116547,11 @@ goog.require('ol.style.ImageState'); goog.require('ol.style.RegularShape'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); +goog.require('ol.style.StyleFunction'); goog.require('ol.style.Text'); goog.require('ol.style.defaultGeometryFunction'); goog.require('ol.tilegrid.TileGrid'); goog.require('ol.tilegrid.WMTS'); -goog.require('ol.tilegrid.XYZ'); -goog.require('ol.tilegrid.Zoomify'); goog.require('ol.tilejson'); goog.require('ol.webgl.Context'); goog.require('ol.xml'); @@ -113132,50 +116903,10 @@ goog.exportProperty( ol.Feature.prototype.setGeometryName); goog.exportSymbol( - 'ol.FeatureOverlay', - ol.FeatureOverlay, + 'ol.featureloader.xhr', + ol.featureloader.xhr, OPENLAYERS); -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'addFeature', - ol.FeatureOverlay.prototype.addFeature); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'getFeatures', - ol.FeatureOverlay.prototype.getFeatures); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'removeFeature', - ol.FeatureOverlay.prototype.removeFeature); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'setFeatures', - ol.FeatureOverlay.prototype.setFeatures); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'setMap', - ol.FeatureOverlay.prototype.setMap); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'setStyle', - ol.FeatureOverlay.prototype.setStyle); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'getStyle', - ol.FeatureOverlay.prototype.getStyle); - -goog.exportProperty( - ol.FeatureOverlay.prototype, - 'getStyleFunction', - ol.FeatureOverlay.prototype.getStyleFunction); - goog.exportSymbol( 'ol.Geolocation', ol.Geolocation, @@ -113327,8 +117058,8 @@ goog.exportSymbol( OPENLAYERS); goog.exportSymbol( - 'ol.loadingstrategy.createTile', - ol.loadingstrategy.createTile, + 'ol.loadingstrategy.tile', + ol.loadingstrategy.tile, OPENLAYERS); goog.exportSymbol( @@ -113366,6 +117097,16 @@ goog.exportProperty( 'forEachFeatureAtPixel', ol.Map.prototype.forEachFeatureAtPixel); +goog.exportProperty( + ol.Map.prototype, + 'forEachLayerAtPixel', + ol.Map.prototype.forEachLayerAtPixel); + +goog.exportProperty( + ol.Map.prototype, + 'hasFeatureAtPixel', + ol.Map.prototype.hasFeatureAtPixel); + goog.exportProperty( ol.Map.prototype, 'getEventCoordinate', @@ -113506,6 +117247,11 @@ goog.exportProperty( 'coordinate', ol.MapBrowserEvent.prototype.coordinate); +goog.exportProperty( + ol.MapBrowserEvent.prototype, + 'dragging', + ol.MapBrowserEvent.prototype.dragging); + goog.exportProperty( ol.MapBrowserEvent.prototype, 'preventDefault', @@ -113536,21 +117282,11 @@ goog.exportProperty( 'oldValue', ol.ObjectEvent.prototype.oldValue); -goog.exportProperty( - ol.ObjectAccessor.prototype, - 'transform', - ol.ObjectAccessor.prototype.transform); - goog.exportSymbol( 'ol.Object', ol.Object, OPENLAYERS); -goog.exportProperty( - ol.Object.prototype, - 'bindTo', - ol.Object.prototype.bindTo); - goog.exportProperty( ol.Object.prototype, 'get', @@ -113578,13 +117314,8 @@ goog.exportProperty( goog.exportProperty( ol.Object.prototype, - 'unbind', - ol.Object.prototype.unbind); - -goog.exportProperty( - ol.Object.prototype, - 'unbindAll', - ol.Object.prototype.unbindAll); + 'unset', + ol.Object.prototype.unset); goog.exportSymbol( 'ol.Observable', @@ -113626,11 +117357,6 @@ goog.exportProperty( 'unByKey', ol.Observable.prototype.unByKey); -goog.exportSymbol( - 'ol.WEBGL_MAX_TEXTURE_SIZE', - ol.WEBGL_MAX_TEXTURE_SIZE, - OPENLAYERS); - goog.exportSymbol( 'ol.inherits', ol.inherits, @@ -113691,6 +117417,11 @@ goog.exportProperty( 'setPositioning', ol.Overlay.prototype.setPositioning); +goog.exportSymbol( + 'ol.size.toSize', + ol.size.toSize, + OPENLAYERS); + goog.exportProperty( ol.Tile.prototype, 'getTileCoord', @@ -113736,11 +117467,6 @@ goog.exportProperty( 'getResolution', ol.View.prototype.getResolution); -goog.exportProperty( - ol.View.prototype, - 'getResolutionForExtent', - ol.View.prototype.getResolutionForExtent); - goog.exportProperty( ol.View.prototype, 'getRotation', @@ -113753,13 +117479,8 @@ goog.exportProperty( goog.exportProperty( ol.View.prototype, - 'fitExtent', - ol.View.prototype.fitExtent); - -goog.exportProperty( - ol.View.prototype, - 'fitGeometry', - ol.View.prototype.fitGeometry); + 'fit', + ol.View.prototype.fit); goog.exportProperty( ol.View.prototype, @@ -113811,6 +117532,11 @@ goog.exportProperty( 'getGL', ol.webgl.Context.prototype.getGL); +goog.exportProperty( + ol.webgl.Context.prototype, + 'getHitDetectionFramebuffer', + ol.webgl.Context.prototype.getHitDetectionFramebuffer); + goog.exportProperty( ol.webgl.Context.prototype, 'useProgram', @@ -113861,6 +117587,11 @@ goog.exportProperty( 'getTileSize', ol.tilegrid.TileGrid.prototype.getTileSize); +goog.exportSymbol( + 'ol.tilegrid.createXYZ', + ol.tilegrid.createXYZ, + OPENLAYERS); + goog.exportSymbol( 'ol.tilegrid.WMTS', ol.tilegrid.WMTS, @@ -113872,13 +117603,8 @@ goog.exportProperty( ol.tilegrid.WMTS.prototype.getMatrixIds); goog.exportSymbol( - 'ol.tilegrid.XYZ', - ol.tilegrid.XYZ, - OPENLAYERS); - -goog.exportSymbol( - 'ol.tilegrid.Zoomify', - ol.tilegrid.Zoomify, + 'ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet', + ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet, OPENLAYERS); goog.exportSymbol( @@ -114001,11 +117727,6 @@ goog.exportProperty( 'getSnapToPixel', ol.style.Image.prototype.getSnapToPixel); -goog.exportProperty( - ol.style.Image.prototype, - 'getImage', - ol.style.Image.prototype.getImage); - goog.exportProperty( ol.style.Image.prototype, 'setRotation', @@ -114286,6 +118007,16 @@ goog.exportSymbol( ol.Sphere, OPENLAYERS); +goog.exportProperty( + ol.Sphere.prototype, + 'geodesicArea', + ol.Sphere.prototype.geodesicArea); + +goog.exportProperty( + ol.Sphere.prototype, + 'haversineDistance', + ol.Sphere.prototype.haversineDistance); + goog.exportSymbol( 'ol.source.BingMaps', ol.source.BingMaps, @@ -114302,24 +118033,9 @@ goog.exportSymbol( OPENLAYERS); goog.exportProperty( - ol.source.FormatVector.prototype, - 'readFeatures', - ol.source.FormatVector.prototype.readFeatures); - -goog.exportSymbol( - 'ol.source.GeoJSON', - ol.source.GeoJSON, - OPENLAYERS); - -goog.exportSymbol( - 'ol.source.GPX', - ol.source.GPX, - OPENLAYERS); - -goog.exportSymbol( - 'ol.source.IGC', - ol.source.IGC, - OPENLAYERS); + ol.source.Cluster.prototype, + 'getSource', + ol.source.Cluster.prototype.getSource); goog.exportSymbol( 'ol.source.ImageCanvas', @@ -114336,16 +118052,31 @@ goog.exportProperty( 'getParams', ol.source.ImageMapGuide.prototype.getParams); +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getImageLoadFunction', + ol.source.ImageMapGuide.prototype.getImageLoadFunction); + goog.exportProperty( ol.source.ImageMapGuide.prototype, 'updateParams', ol.source.ImageMapGuide.prototype.updateParams); +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setImageLoadFunction', + ol.source.ImageMapGuide.prototype.setImageLoadFunction); + goog.exportSymbol( 'ol.source.Image', ol.source.Image, OPENLAYERS); +goog.exportProperty( + ol.source.ImageEvent.prototype, + 'image', + ol.source.ImageEvent.prototype.image); + goog.exportSymbol( 'ol.source.ImageStatic', ol.source.ImageStatic, @@ -114391,11 +118122,21 @@ goog.exportProperty( 'getParams', ol.source.ImageWMS.prototype.getParams); +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'getImageLoadFunction', + ol.source.ImageWMS.prototype.getImageLoadFunction); + goog.exportProperty( ol.source.ImageWMS.prototype, 'getUrl', ol.source.ImageWMS.prototype.getUrl); +goog.exportProperty( + ol.source.ImageWMS.prototype, + 'setImageLoadFunction', + ol.source.ImageWMS.prototype.setImageLoadFunction); + goog.exportProperty( ol.source.ImageWMS.prototype, 'setUrl', @@ -114406,16 +118147,16 @@ goog.exportProperty( 'updateParams', ol.source.ImageWMS.prototype.updateParams); -goog.exportSymbol( - 'ol.source.KML', - ol.source.KML, - OPENLAYERS); - goog.exportSymbol( 'ol.source.MapQuest', ol.source.MapQuest, OPENLAYERS); +goog.exportProperty( + ol.source.MapQuest.prototype, + 'getLayer', + ol.source.MapQuest.prototype.getLayer); + goog.exportSymbol( 'ol.source.OSM', ol.source.OSM, @@ -114427,19 +118168,29 @@ goog.exportSymbol( OPENLAYERS); goog.exportSymbol( - 'ol.source.OSMXML', - ol.source.OSMXML, + 'ol.source.Raster', + ol.source.Raster, OPENLAYERS); -goog.exportSymbol( - 'ol.source.ServerVector', - ol.source.ServerVector, - OPENLAYERS); +goog.exportProperty( + ol.source.Raster.prototype, + 'setOperation', + ol.source.Raster.prototype.setOperation); goog.exportProperty( - ol.source.ServerVector.prototype, - 'readFeatures', - ol.source.ServerVector.prototype.readFeatures); + ol.source.RasterEvent.prototype, + 'extent', + ol.source.RasterEvent.prototype.extent); + +goog.exportProperty( + ol.source.RasterEvent.prototype, + 'resolution', + ol.source.RasterEvent.prototype.resolution); + +goog.exportProperty( + ol.source.RasterEvent.prototype, + 'data', + ol.source.RasterEvent.prototype.data); goog.exportSymbol( 'ol.source.Source', @@ -114472,10 +118223,35 @@ goog.exportSymbol( OPENLAYERS); goog.exportSymbol( - 'ol.source.StaticVector', - ol.source.StaticVector, + 'ol.source.TileArcGISRest', + ol.source.TileArcGISRest, OPENLAYERS); +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getParams', + ol.source.TileArcGISRest.prototype.getParams); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getUrls', + ol.source.TileArcGISRest.prototype.getUrls); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setUrl', + ol.source.TileArcGISRest.prototype.setUrl); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setUrls', + ol.source.TileArcGISRest.prototype.setUrls); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'updateParams', + ol.source.TileArcGISRest.prototype.updateParams); + goog.exportSymbol( 'ol.source.TileDebug', ol.source.TileDebug, @@ -114521,6 +118297,11 @@ goog.exportProperty( 'getTileGrid', ol.source.Tile.prototype.getTileGrid); +goog.exportProperty( + ol.source.TileEvent.prototype, + 'tile', + ol.source.TileEvent.prototype.tile); + goog.exportSymbol( 'ol.source.TileUTFGrid', ol.source.TileUTFGrid, @@ -114586,11 +118367,6 @@ goog.exportProperty( 'updateParams', ol.source.TileWMS.prototype.updateParams); -goog.exportSymbol( - 'ol.source.TopoJSON', - ol.source.TopoJSON, - OPENLAYERS); - goog.exportSymbol( 'ol.source.Vector', ol.source.Vector, @@ -114626,6 +118402,11 @@ goog.exportProperty( 'forEachFeatureIntersectingExtent', ol.source.Vector.prototype.forEachFeatureIntersectingExtent); +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesCollection', + ol.source.Vector.prototype.getFeaturesCollection); + goog.exportProperty( ol.source.Vector.prototype, 'getFeatures', @@ -114636,6 +118417,11 @@ goog.exportProperty( 'getFeaturesAtCoordinate', ol.source.Vector.prototype.getFeaturesAtCoordinate); +goog.exportProperty( + ol.source.Vector.prototype, + 'getFeaturesInExtent', + ol.source.Vector.prototype.getFeaturesInExtent); + goog.exportProperty( ol.source.Vector.prototype, 'getClosestFeatureToCoordinate', @@ -114671,21 +118457,56 @@ goog.exportProperty( 'getDimensions', ol.source.WMTS.prototype.getDimensions); +goog.exportProperty( + ol.source.WMTS.prototype, + 'getFormat', + ol.source.WMTS.prototype.getFormat); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getLayer', + ol.source.WMTS.prototype.getLayer); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getMatrixSet', + ol.source.WMTS.prototype.getMatrixSet); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getRequestEncoding', + ol.source.WMTS.prototype.getRequestEncoding); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getStyle', + ol.source.WMTS.prototype.getStyle); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getUrls', + ol.source.WMTS.prototype.getUrls); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getVersion', + ol.source.WMTS.prototype.getVersion); + goog.exportProperty( ol.source.WMTS.prototype, 'updateDimensions', ol.source.WMTS.prototype.updateDimensions); +goog.exportSymbol( + 'ol.source.WMTS.optionsFromCapabilities', + ol.source.WMTS.optionsFromCapabilities, + OPENLAYERS); + goog.exportSymbol( 'ol.source.XYZ', ol.source.XYZ, OPENLAYERS); -goog.exportProperty( - ol.source.XYZ.prototype, - 'setTileUrlFunction', - ol.source.XYZ.prototype.setTileUrlFunction); - goog.exportProperty( ol.source.XYZ.prototype, 'setUrl', @@ -114716,6 +118537,11 @@ goog.exportProperty( 'glContext', ol.render.Event.prototype.glContext); +goog.exportSymbol( + 'ol.render.VectorContext', + ol.render.VectorContext, + OPENLAYERS); + goog.exportProperty( ol.render.webgl.Immediate.prototype, 'drawAsync', @@ -114891,6 +118717,11 @@ goog.exportProperty( 'isGlobal', ol.proj.Projection.prototype.isGlobal); +goog.exportProperty( + ol.proj.Projection.prototype, + 'setGlobal', + ol.proj.Projection.prototype.setGlobal); + goog.exportProperty( ol.proj.Projection.prototype, 'setExtent', @@ -114901,6 +118732,16 @@ goog.exportProperty( 'setWorldExtent', ol.proj.Projection.prototype.setWorldExtent); +goog.exportProperty( + ol.proj.Projection.prototype, + 'setGetPointResolution', + ol.proj.Projection.prototype.setGetPointResolution); + +goog.exportProperty( + ol.proj.Projection.prototype, + 'getPointResolution', + ol.proj.Projection.prototype.getPointResolution); + goog.exportSymbol( 'ol.proj.addEquivalentProjections', ol.proj.addEquivalentProjections, @@ -114916,6 +118757,16 @@ goog.exportSymbol( ol.proj.addCoordinateTransforms, OPENLAYERS); +goog.exportSymbol( + 'ol.proj.fromLonLat', + ol.proj.fromLonLat, + OPENLAYERS); + +goog.exportSymbol( + 'ol.proj.toLonLat', + ol.proj.toLonLat, + OPENLAYERS); + goog.exportSymbol( 'ol.proj.get', ol.proj.get, @@ -114941,16 +118792,36 @@ goog.exportSymbol( ol.layer.Heatmap, OPENLAYERS); +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getBlur', + ol.layer.Heatmap.prototype.getBlur); + goog.exportProperty( ol.layer.Heatmap.prototype, 'getGradient', ol.layer.Heatmap.prototype.getGradient); +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'getRadius', + ol.layer.Heatmap.prototype.getRadius); + +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setBlur', + ol.layer.Heatmap.prototype.setBlur); + goog.exportProperty( ol.layer.Heatmap.prototype, 'setGradient', ol.layer.Heatmap.prototype.setGradient); +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setRadius', + ol.layer.Heatmap.prototype.setRadius); + goog.exportSymbol( 'ol.layer.Image', ol.layer.Image, @@ -114971,6 +118842,11 @@ goog.exportProperty( 'getSource', ol.layer.Layer.prototype.getSource); +goog.exportProperty( + ol.layer.Layer.prototype, + 'setMap', + ol.layer.Layer.prototype.setMap); + goog.exportProperty( ol.layer.Layer.prototype, 'setSource', @@ -115212,9 +119088,9 @@ goog.exportSymbol( OPENLAYERS); goog.exportProperty( - ol.DrawEvent.prototype, + ol.interaction.DrawEvent.prototype, 'feature', - ol.DrawEvent.prototype.feature); + ol.interaction.DrawEvent.prototype.feature); goog.exportSymbol( 'ol.interaction.Draw', @@ -115231,6 +119107,11 @@ goog.exportProperty( 'finishDrawing', ol.interaction.Draw.prototype.finishDrawing); +goog.exportSymbol( + 'ol.interaction.Draw.createRegularPolygon', + ol.interaction.Draw.createRegularPolygon, + OPENLAYERS); + goog.exportSymbol( 'ol.interaction.Interaction', ol.interaction.Interaction, @@ -115271,6 +119152,16 @@ goog.exportSymbol( ol.interaction.KeyboardZoom.handleEvent, OPENLAYERS); +goog.exportProperty( + ol.ModifyEvent.prototype, + 'features', + ol.ModifyEvent.prototype.features); + +goog.exportProperty( + ol.ModifyEvent.prototype, + 'mapBrowserPointerEvent', + ol.ModifyEvent.prototype.mapBrowserPointerEvent); + goog.exportSymbol( 'ol.interaction.Modify', ol.interaction.Modify, @@ -115311,6 +119202,21 @@ goog.exportSymbol( ol.interaction.Pointer.handleEvent, OPENLAYERS); +goog.exportProperty( + ol.SelectEvent.prototype, + 'selected', + ol.SelectEvent.prototype.selected); + +goog.exportProperty( + ol.SelectEvent.prototype, + 'deselected', + ol.SelectEvent.prototype.deselected); + +goog.exportProperty( + ol.SelectEvent.prototype, + 'mapBrowserEvent', + ol.SelectEvent.prototype.mapBrowserEvent); + goog.exportSymbol( 'ol.interaction.Select', ol.interaction.Select, @@ -115331,6 +119237,21 @@ goog.exportProperty( 'setMap', ol.interaction.Select.prototype.setMap); +goog.exportSymbol( + 'ol.interaction.Snap', + ol.interaction.Snap, + OPENLAYERS); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'addFeature', + ol.interaction.Snap.prototype.addFeature); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'removeFeature', + ol.interaction.Snap.prototype.removeFeature); + goog.exportSymbol( 'ol.geom.Circle', ol.geom.Circle, @@ -115346,11 +119267,6 @@ goog.exportProperty( 'getCenter', ol.geom.Circle.prototype.getCenter); -goog.exportProperty( - ol.geom.Circle.prototype, - 'getExtent', - ol.geom.Circle.prototype.getExtent); - goog.exportProperty( ol.geom.Circle.prototype, 'getRadius', @@ -115361,6 +119277,11 @@ goog.exportProperty( 'getType', ol.geom.Circle.prototype.getType); +goog.exportProperty( + ol.geom.Circle.prototype, + 'intersectsExtent', + ol.geom.Circle.prototype.intersectsExtent); + goog.exportProperty( ol.geom.Circle.prototype, 'setCenter', @@ -115386,11 +119307,6 @@ goog.exportSymbol( ol.geom.Geometry, OPENLAYERS); -goog.exportProperty( - ol.geom.Geometry.prototype, - 'clone', - ol.geom.Geometry.prototype.clone); - goog.exportProperty( ol.geom.Geometry.prototype, 'getClosestPoint', @@ -115401,26 +119317,6 @@ goog.exportProperty( 'getExtent', ol.geom.Geometry.prototype.getExtent); -goog.exportProperty( - ol.geom.Geometry.prototype, - 'getType', - ol.geom.Geometry.prototype.getType); - -goog.exportProperty( - ol.geom.Geometry.prototype, - 'applyTransform', - ol.geom.Geometry.prototype.applyTransform); - -goog.exportProperty( - ol.geom.Geometry.prototype, - 'intersectsExtent', - ol.geom.Geometry.prototype.intersectsExtent); - -goog.exportProperty( - ol.geom.Geometry.prototype, - 'translate', - ol.geom.Geometry.prototype.translate); - goog.exportProperty( ol.geom.Geometry.prototype, 'transform', @@ -115436,11 +119332,6 @@ goog.exportProperty( 'clone', ol.geom.GeometryCollection.prototype.clone); -goog.exportProperty( - ol.geom.GeometryCollection.prototype, - 'getExtent', - ol.geom.GeometryCollection.prototype.getExtent); - goog.exportProperty( ol.geom.GeometryCollection.prototype, 'getGeometries', @@ -115516,6 +119407,11 @@ goog.exportProperty( 'clone', ol.geom.LineString.prototype.clone); +goog.exportProperty( + ol.geom.LineString.prototype, + 'forEachSegment', + ol.geom.LineString.prototype.forEachSegment); + goog.exportProperty( ol.geom.LineString.prototype, 'getCoordinateAtM', @@ -115796,16 +119692,16 @@ goog.exportSymbol( ol.geom.Polygon.fromExtent, OPENLAYERS); +goog.exportSymbol( + 'ol.geom.Polygon.fromCircle', + ol.geom.Polygon.fromCircle, + OPENLAYERS); + goog.exportSymbol( 'ol.geom.SimpleGeometry', ol.geom.SimpleGeometry, OPENLAYERS); -goog.exportProperty( - ol.geom.SimpleGeometry.prototype, - 'getExtent', - ol.geom.SimpleGeometry.prototype.getExtent); - goog.exportProperty( ol.geom.SimpleGeometry.prototype, 'getFirstCoordinate', @@ -115831,6 +119727,61 @@ goog.exportProperty( 'translate', ol.geom.SimpleGeometry.prototype.translate); +goog.exportSymbol( + 'ol.format.EsriJSON', + ol.format.EsriJSON, + OPENLAYERS); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readFeature', + ol.format.EsriJSON.prototype.readFeature); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readFeatures', + ol.format.EsriJSON.prototype.readFeatures); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readGeometry', + ol.format.EsriJSON.prototype.readGeometry); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'readProjection', + ol.format.EsriJSON.prototype.readProjection); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeGeometry', + ol.format.EsriJSON.prototype.writeGeometry); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeGeometryObject', + ol.format.EsriJSON.prototype.writeGeometryObject); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeature', + ol.format.EsriJSON.prototype.writeFeature); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeatureObject', + ol.format.EsriJSON.prototype.writeFeatureObject); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeatures', + ol.format.EsriJSON.prototype.writeFeatures); + +goog.exportProperty( + ol.format.EsriJSON.prototype, + 'writeFeaturesObject', + ol.format.EsriJSON.prototype.writeFeaturesObject); + goog.exportSymbol( 'ol.format.Feature', ol.format.Feature, @@ -115961,6 +119912,11 @@ goog.exportProperty( 'readName', ol.format.KML.prototype.readName); +goog.exportProperty( + ol.format.KML.prototype, + 'readNetworkLinks', + ol.format.KML.prototype.readNetworkLinks); + goog.exportProperty( ol.format.KML.prototype, 'readProjection', @@ -116146,6 +120102,16 @@ goog.exportProperty( 'readFeatures', ol.format.WMSGetFeatureInfo.prototype.readFeatures); +goog.exportSymbol( + 'ol.format.WMTSCapabilities', + ol.format.WMTSCapabilities, + OPENLAYERS); + +goog.exportProperty( + ol.format.WMTSCapabilities.prototype, + 'read', + ol.format.WMTSCapabilities.prototype.read); + goog.exportSymbol( 'ol.format.GML2', ol.format.GML2, @@ -116186,11 +120152,6 @@ goog.exportProperty( 'writeFeaturesNode', ol.format.GML.prototype.writeFeaturesNode); -goog.exportSymbol( - 'ol.format.GMLBase', - ol.format.GMLBase, - OPENLAYERS); - goog.exportProperty( ol.format.GMLBase.prototype, 'readFeatures', @@ -116217,13 +120178,13 @@ goog.exportSymbol( OPENLAYERS); goog.exportSymbol( - 'ol.events.condition.mouseMove', - ol.events.condition.mouseMove, + 'ol.events.condition.never', + ol.events.condition.never, OPENLAYERS); goog.exportSymbol( - 'ol.events.condition.never', - ol.events.condition.never, + 'ol.events.condition.pointerMove', + ol.events.condition.pointerMove, OPENLAYERS); goog.exportSymbol( @@ -116231,6 +120192,11 @@ goog.exportSymbol( ol.events.condition.singleClick, OPENLAYERS); +goog.exportSymbol( + 'ol.events.condition.doubleClick', + ol.events.condition.doubleClick, + OPENLAYERS); + goog.exportSymbol( 'ol.events.condition.noModifierKeys', ol.events.condition.noModifierKeys, @@ -116251,35 +120217,10 @@ goog.exportSymbol( ol.events.condition.targetNotEditable, OPENLAYERS); -goog.exportSymbol( - 'ol.events.condition.mouseOnly', - ol.events.condition.mouseOnly, - OPENLAYERS); - -goog.exportSymbol( - 'ol.dom.Input', - ol.dom.Input, - OPENLAYERS); - -goog.exportProperty( - ol.dom.Input.prototype, - 'getChecked', - ol.dom.Input.prototype.getChecked); - -goog.exportProperty( - ol.dom.Input.prototype, - 'getValue', - ol.dom.Input.prototype.getValue); - -goog.exportProperty( - ol.dom.Input.prototype, - 'setValue', - ol.dom.Input.prototype.setValue); - -goog.exportProperty( - ol.dom.Input.prototype, - 'setChecked', - ol.dom.Input.prototype.setChecked); +goog.exportSymbol( + 'ol.events.condition.mouseOnly', + ol.events.condition.mouseOnly, + OPENLAYERS); goog.exportSymbol( 'ol.control.Attribution', @@ -116326,6 +120267,11 @@ goog.exportProperty( 'setMap', ol.control.Control.prototype.setMap); +goog.exportProperty( + ol.control.Control.prototype, + 'setTarget', + ol.control.Control.prototype.setTarget); + goog.exportSymbol( 'ol.control.defaults', ol.control.defaults, @@ -116496,11 +120442,6 @@ goog.exportProperty( 'unByKey', ol.Object.prototype.unByKey); -goog.exportProperty( - ol.Collection.prototype, - 'bindTo', - ol.Collection.prototype.bindTo); - goog.exportProperty( ol.Collection.prototype, 'get', @@ -116528,13 +120469,8 @@ goog.exportProperty( goog.exportProperty( ol.Collection.prototype, - 'unbind', - ol.Collection.prototype.unbind); - -goog.exportProperty( - ol.Collection.prototype, - 'unbindAll', - ol.Collection.prototype.unbindAll); + 'unset', + ol.Collection.prototype.unset); goog.exportProperty( ol.Collection.prototype, @@ -116566,11 +120502,6 @@ goog.exportProperty( 'unByKey', ol.Collection.prototype.unByKey); -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'bindTo', - ol.DeviceOrientation.prototype.bindTo); - goog.exportProperty( ol.DeviceOrientation.prototype, 'get', @@ -116598,13 +120529,8 @@ goog.exportProperty( goog.exportProperty( ol.DeviceOrientation.prototype, - 'unbind', - ol.DeviceOrientation.prototype.unbind); - -goog.exportProperty( - ol.DeviceOrientation.prototype, - 'unbindAll', - ol.DeviceOrientation.prototype.unbindAll); + 'unset', + ol.DeviceOrientation.prototype.unset); goog.exportProperty( ol.DeviceOrientation.prototype, @@ -116636,11 +120562,6 @@ goog.exportProperty( 'unByKey', ol.DeviceOrientation.prototype.unByKey); -goog.exportProperty( - ol.Feature.prototype, - 'bindTo', - ol.Feature.prototype.bindTo); - goog.exportProperty( ol.Feature.prototype, 'get', @@ -116668,13 +120589,8 @@ goog.exportProperty( goog.exportProperty( ol.Feature.prototype, - 'unbind', - ol.Feature.prototype.unbind); - -goog.exportProperty( - ol.Feature.prototype, - 'unbindAll', - ol.Feature.prototype.unbindAll); + 'unset', + ol.Feature.prototype.unset); goog.exportProperty( ol.Feature.prototype, @@ -116706,11 +120622,6 @@ goog.exportProperty( 'unByKey', ol.Feature.prototype.unByKey); -goog.exportProperty( - ol.Geolocation.prototype, - 'bindTo', - ol.Geolocation.prototype.bindTo); - goog.exportProperty( ol.Geolocation.prototype, 'get', @@ -116738,13 +120649,8 @@ goog.exportProperty( goog.exportProperty( ol.Geolocation.prototype, - 'unbind', - ol.Geolocation.prototype.unbind); - -goog.exportProperty( - ol.Geolocation.prototype, - 'unbindAll', - ol.Geolocation.prototype.unbindAll); + 'unset', + ol.Geolocation.prototype.unset); goog.exportProperty( ol.Geolocation.prototype, @@ -116781,11 +120687,6 @@ goog.exportProperty( 'getTileCoord', ol.ImageTile.prototype.getTileCoord); -goog.exportProperty( - ol.Map.prototype, - 'bindTo', - ol.Map.prototype.bindTo); - goog.exportProperty( ol.Map.prototype, 'get', @@ -116813,13 +120714,8 @@ goog.exportProperty( goog.exportProperty( ol.Map.prototype, - 'unbind', - ol.Map.prototype.unbind); - -goog.exportProperty( - ol.Map.prototype, - 'unbindAll', - ol.Map.prototype.unbindAll); + 'unset', + ol.Map.prototype.unset); goog.exportProperty( ol.Map.prototype, @@ -116876,6 +120772,11 @@ goog.exportProperty( 'coordinate', ol.MapBrowserPointerEvent.prototype.coordinate); +goog.exportProperty( + ol.MapBrowserPointerEvent.prototype, + 'dragging', + ol.MapBrowserPointerEvent.prototype.dragging); + goog.exportProperty( ol.MapBrowserPointerEvent.prototype, 'preventDefault', @@ -116896,11 +120797,6 @@ goog.exportProperty( 'frameState', ol.MapBrowserPointerEvent.prototype.frameState); -goog.exportProperty( - ol.Overlay.prototype, - 'bindTo', - ol.Overlay.prototype.bindTo); - goog.exportProperty( ol.Overlay.prototype, 'get', @@ -116928,13 +120824,8 @@ goog.exportProperty( goog.exportProperty( ol.Overlay.prototype, - 'unbind', - ol.Overlay.prototype.unbind); - -goog.exportProperty( - ol.Overlay.prototype, - 'unbindAll', - ol.Overlay.prototype.unbindAll); + 'unset', + ol.Overlay.prototype.unset); goog.exportProperty( ol.Overlay.prototype, @@ -116966,11 +120857,6 @@ goog.exportProperty( 'unByKey', ol.Overlay.prototype.unByKey); -goog.exportProperty( - ol.View.prototype, - 'bindTo', - ol.View.prototype.bindTo); - goog.exportProperty( ol.View.prototype, 'get', @@ -116998,13 +120884,8 @@ goog.exportProperty( goog.exportProperty( ol.View.prototype, - 'unbind', - ol.View.prototype.unbind); - -goog.exportProperty( - ol.View.prototype, - 'unbindAll', - ol.View.prototype.unbindAll); + 'unset', + ol.View.prototype.unset); goog.exportProperty( ol.View.prototype, @@ -117076,86 +120957,6 @@ goog.exportProperty( 'getTileSize', ol.tilegrid.WMTS.prototype.getTileSize); -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getMaxZoom', - ol.tilegrid.XYZ.prototype.getMaxZoom); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getMinZoom', - ol.tilegrid.XYZ.prototype.getMinZoom); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getOrigin', - ol.tilegrid.XYZ.prototype.getOrigin); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getResolution', - ol.tilegrid.XYZ.prototype.getResolution); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getResolutions', - ol.tilegrid.XYZ.prototype.getResolutions); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getTileCoordForCoordAndResolution', - ol.tilegrid.XYZ.prototype.getTileCoordForCoordAndResolution); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getTileCoordForCoordAndZ', - ol.tilegrid.XYZ.prototype.getTileCoordForCoordAndZ); - -goog.exportProperty( - ol.tilegrid.XYZ.prototype, - 'getTileSize', - ol.tilegrid.XYZ.prototype.getTileSize); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getMaxZoom', - ol.tilegrid.Zoomify.prototype.getMaxZoom); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getMinZoom', - ol.tilegrid.Zoomify.prototype.getMinZoom); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getOrigin', - ol.tilegrid.Zoomify.prototype.getOrigin); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getResolution', - ol.tilegrid.Zoomify.prototype.getResolution); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getResolutions', - ol.tilegrid.Zoomify.prototype.getResolutions); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getTileCoordForCoordAndResolution', - ol.tilegrid.Zoomify.prototype.getTileCoordForCoordAndResolution); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getTileCoordForCoordAndZ', - ol.tilegrid.Zoomify.prototype.getTileCoordForCoordAndZ); - -goog.exportProperty( - ol.tilegrid.Zoomify.prototype, - 'getTileSize', - ol.tilegrid.Zoomify.prototype.getTileSize); - goog.exportProperty( ol.style.Circle.prototype, 'getOpacity', @@ -117261,6 +121062,36 @@ goog.exportProperty( 'setScale', ol.style.RegularShape.prototype.setScale); +goog.exportProperty( + ol.source.Source.prototype, + 'get', + ol.source.Source.prototype.get); + +goog.exportProperty( + ol.source.Source.prototype, + 'getKeys', + ol.source.Source.prototype.getKeys); + +goog.exportProperty( + ol.source.Source.prototype, + 'getProperties', + ol.source.Source.prototype.getProperties); + +goog.exportProperty( + ol.source.Source.prototype, + 'set', + ol.source.Source.prototype.set); + +goog.exportProperty( + ol.source.Source.prototype, + 'setProperties', + ol.source.Source.prototype.setProperties); + +goog.exportProperty( + ol.source.Source.prototype, + 'unset', + ol.source.Source.prototype.unset); + goog.exportProperty( ol.source.Source.prototype, 'changed', @@ -117313,893 +121144,483 @@ goog.exportProperty( goog.exportProperty( ol.source.Tile.prototype, - 'changed', - ol.source.Tile.prototype.changed); - -goog.exportProperty( - ol.source.Tile.prototype, - 'getRevision', - ol.source.Tile.prototype.getRevision); - -goog.exportProperty( - ol.source.Tile.prototype, - 'on', - ol.source.Tile.prototype.on); - -goog.exportProperty( - ol.source.Tile.prototype, - 'once', - ol.source.Tile.prototype.once); - -goog.exportProperty( - ol.source.Tile.prototype, - 'un', - ol.source.Tile.prototype.un); - -goog.exportProperty( - ol.source.Tile.prototype, - 'unByKey', - ol.source.Tile.prototype.unByKey); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'getTileGrid', - ol.source.TileImage.prototype.getTileGrid); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'getAttributions', - ol.source.TileImage.prototype.getAttributions); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'getLogo', - ol.source.TileImage.prototype.getLogo); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'getProjection', - ol.source.TileImage.prototype.getProjection); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'getState', - ol.source.TileImage.prototype.getState); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'changed', - ol.source.TileImage.prototype.changed); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'getRevision', - ol.source.TileImage.prototype.getRevision); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'on', - ol.source.TileImage.prototype.on); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'once', - ol.source.TileImage.prototype.once); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'un', - ol.source.TileImage.prototype.un); - -goog.exportProperty( - ol.source.TileImage.prototype, - 'unByKey', - ol.source.TileImage.prototype.unByKey); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getTileLoadFunction', - ol.source.BingMaps.prototype.getTileLoadFunction); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getTileUrlFunction', - ol.source.BingMaps.prototype.getTileUrlFunction); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'setTileLoadFunction', - ol.source.BingMaps.prototype.setTileLoadFunction); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'setTileUrlFunction', - ol.source.BingMaps.prototype.setTileUrlFunction); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getTileGrid', - ol.source.BingMaps.prototype.getTileGrid); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getAttributions', - ol.source.BingMaps.prototype.getAttributions); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getLogo', - ol.source.BingMaps.prototype.getLogo); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getProjection', - ol.source.BingMaps.prototype.getProjection); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getState', - ol.source.BingMaps.prototype.getState); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'changed', - ol.source.BingMaps.prototype.changed); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'getRevision', - ol.source.BingMaps.prototype.getRevision); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'on', - ol.source.BingMaps.prototype.on); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'once', - ol.source.BingMaps.prototype.once); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'un', - ol.source.BingMaps.prototype.un); - -goog.exportProperty( - ol.source.BingMaps.prototype, - 'unByKey', - ol.source.BingMaps.prototype.unByKey); - -goog.exportProperty( - ol.source.Vector.prototype, - 'getAttributions', - ol.source.Vector.prototype.getAttributions); - -goog.exportProperty( - ol.source.Vector.prototype, - 'getLogo', - ol.source.Vector.prototype.getLogo); - -goog.exportProperty( - ol.source.Vector.prototype, - 'getProjection', - ol.source.Vector.prototype.getProjection); - -goog.exportProperty( - ol.source.Vector.prototype, - 'getState', - ol.source.Vector.prototype.getState); - -goog.exportProperty( - ol.source.Vector.prototype, - 'changed', - ol.source.Vector.prototype.changed); - -goog.exportProperty( - ol.source.Vector.prototype, - 'getRevision', - ol.source.Vector.prototype.getRevision); - -goog.exportProperty( - ol.source.Vector.prototype, - 'on', - ol.source.Vector.prototype.on); - -goog.exportProperty( - ol.source.Vector.prototype, - 'once', - ol.source.Vector.prototype.once); - -goog.exportProperty( - ol.source.Vector.prototype, - 'un', - ol.source.Vector.prototype.un); - -goog.exportProperty( - ol.source.Vector.prototype, - 'unByKey', - ol.source.Vector.prototype.unByKey); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'addFeature', - ol.source.Cluster.prototype.addFeature); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'addFeatures', - ol.source.Cluster.prototype.addFeatures); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'clear', - ol.source.Cluster.prototype.clear); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'forEachFeature', - ol.source.Cluster.prototype.forEachFeature); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'forEachFeatureInExtent', - ol.source.Cluster.prototype.forEachFeatureInExtent); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.Cluster.prototype.forEachFeatureIntersectingExtent); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getFeatures', - ol.source.Cluster.prototype.getFeatures); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getFeaturesAtCoordinate', - ol.source.Cluster.prototype.getFeaturesAtCoordinate); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getClosestFeatureToCoordinate', - ol.source.Cluster.prototype.getClosestFeatureToCoordinate); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getExtent', - ol.source.Cluster.prototype.getExtent); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getFeatureById', - ol.source.Cluster.prototype.getFeatureById); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'removeFeature', - ol.source.Cluster.prototype.removeFeature); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getAttributions', - ol.source.Cluster.prototype.getAttributions); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getLogo', - ol.source.Cluster.prototype.getLogo); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getProjection', - ol.source.Cluster.prototype.getProjection); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getState', - ol.source.Cluster.prototype.getState); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'changed', - ol.source.Cluster.prototype.changed); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'getRevision', - ol.source.Cluster.prototype.getRevision); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'on', - ol.source.Cluster.prototype.on); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'once', - ol.source.Cluster.prototype.once); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'un', - ol.source.Cluster.prototype.un); - -goog.exportProperty( - ol.source.Cluster.prototype, - 'unByKey', - ol.source.Cluster.prototype.unByKey); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'addFeature', - ol.source.FormatVector.prototype.addFeature); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'addFeatures', - ol.source.FormatVector.prototype.addFeatures); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'clear', - ol.source.FormatVector.prototype.clear); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'forEachFeature', - ol.source.FormatVector.prototype.forEachFeature); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'forEachFeatureInExtent', - ol.source.FormatVector.prototype.forEachFeatureInExtent); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.FormatVector.prototype.forEachFeatureIntersectingExtent); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getFeatures', - ol.source.FormatVector.prototype.getFeatures); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getFeaturesAtCoordinate', - ol.source.FormatVector.prototype.getFeaturesAtCoordinate); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getClosestFeatureToCoordinate', - ol.source.FormatVector.prototype.getClosestFeatureToCoordinate); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getExtent', - ol.source.FormatVector.prototype.getExtent); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getFeatureById', - ol.source.FormatVector.prototype.getFeatureById); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'removeFeature', - ol.source.FormatVector.prototype.removeFeature); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getAttributions', - ol.source.FormatVector.prototype.getAttributions); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getLogo', - ol.source.FormatVector.prototype.getLogo); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getProjection', - ol.source.FormatVector.prototype.getProjection); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getState', - ol.source.FormatVector.prototype.getState); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'changed', - ol.source.FormatVector.prototype.changed); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'getRevision', - ol.source.FormatVector.prototype.getRevision); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'on', - ol.source.FormatVector.prototype.on); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'once', - ol.source.FormatVector.prototype.once); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'un', - ol.source.FormatVector.prototype.un); - -goog.exportProperty( - ol.source.FormatVector.prototype, - 'unByKey', - ol.source.FormatVector.prototype.unByKey); - -goog.exportProperty( - ol.source.StaticVector.prototype, - 'readFeatures', - ol.source.StaticVector.prototype.readFeatures); + 'get', + ol.source.Tile.prototype.get); goog.exportProperty( - ol.source.StaticVector.prototype, - 'addFeature', - ol.source.StaticVector.prototype.addFeature); + ol.source.Tile.prototype, + 'getKeys', + ol.source.Tile.prototype.getKeys); goog.exportProperty( - ol.source.StaticVector.prototype, - 'addFeatures', - ol.source.StaticVector.prototype.addFeatures); + ol.source.Tile.prototype, + 'getProperties', + ol.source.Tile.prototype.getProperties); goog.exportProperty( - ol.source.StaticVector.prototype, - 'clear', - ol.source.StaticVector.prototype.clear); + ol.source.Tile.prototype, + 'set', + ol.source.Tile.prototype.set); goog.exportProperty( - ol.source.StaticVector.prototype, - 'forEachFeature', - ol.source.StaticVector.prototype.forEachFeature); + ol.source.Tile.prototype, + 'setProperties', + ol.source.Tile.prototype.setProperties); goog.exportProperty( - ol.source.StaticVector.prototype, - 'forEachFeatureInExtent', - ol.source.StaticVector.prototype.forEachFeatureInExtent); + ol.source.Tile.prototype, + 'unset', + ol.source.Tile.prototype.unset); goog.exportProperty( - ol.source.StaticVector.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.StaticVector.prototype.forEachFeatureIntersectingExtent); + ol.source.Tile.prototype, + 'changed', + ol.source.Tile.prototype.changed); goog.exportProperty( - ol.source.StaticVector.prototype, - 'getFeatures', - ol.source.StaticVector.prototype.getFeatures); + ol.source.Tile.prototype, + 'getRevision', + ol.source.Tile.prototype.getRevision); goog.exportProperty( - ol.source.StaticVector.prototype, - 'getFeaturesAtCoordinate', - ol.source.StaticVector.prototype.getFeaturesAtCoordinate); + ol.source.Tile.prototype, + 'on', + ol.source.Tile.prototype.on); goog.exportProperty( - ol.source.StaticVector.prototype, - 'getClosestFeatureToCoordinate', - ol.source.StaticVector.prototype.getClosestFeatureToCoordinate); + ol.source.Tile.prototype, + 'once', + ol.source.Tile.prototype.once); goog.exportProperty( - ol.source.StaticVector.prototype, - 'getExtent', - ol.source.StaticVector.prototype.getExtent); + ol.source.Tile.prototype, + 'un', + ol.source.Tile.prototype.un); goog.exportProperty( - ol.source.StaticVector.prototype, - 'getFeatureById', - ol.source.StaticVector.prototype.getFeatureById); + ol.source.Tile.prototype, + 'unByKey', + ol.source.Tile.prototype.unByKey); goog.exportProperty( - ol.source.StaticVector.prototype, - 'removeFeature', - ol.source.StaticVector.prototype.removeFeature); + ol.source.TileImage.prototype, + 'getTileGrid', + ol.source.TileImage.prototype.getTileGrid); goog.exportProperty( - ol.source.StaticVector.prototype, + ol.source.TileImage.prototype, 'getAttributions', - ol.source.StaticVector.prototype.getAttributions); + ol.source.TileImage.prototype.getAttributions); goog.exportProperty( - ol.source.StaticVector.prototype, + ol.source.TileImage.prototype, 'getLogo', - ol.source.StaticVector.prototype.getLogo); + ol.source.TileImage.prototype.getLogo); goog.exportProperty( - ol.source.StaticVector.prototype, + ol.source.TileImage.prototype, 'getProjection', - ol.source.StaticVector.prototype.getProjection); + ol.source.TileImage.prototype.getProjection); goog.exportProperty( - ol.source.StaticVector.prototype, + ol.source.TileImage.prototype, 'getState', - ol.source.StaticVector.prototype.getState); - -goog.exportProperty( - ol.source.StaticVector.prototype, - 'changed', - ol.source.StaticVector.prototype.changed); - -goog.exportProperty( - ol.source.StaticVector.prototype, - 'getRevision', - ol.source.StaticVector.prototype.getRevision); + ol.source.TileImage.prototype.getState); goog.exportProperty( - ol.source.StaticVector.prototype, - 'on', - ol.source.StaticVector.prototype.on); + ol.source.TileImage.prototype, + 'get', + ol.source.TileImage.prototype.get); goog.exportProperty( - ol.source.StaticVector.prototype, - 'once', - ol.source.StaticVector.prototype.once); + ol.source.TileImage.prototype, + 'getKeys', + ol.source.TileImage.prototype.getKeys); goog.exportProperty( - ol.source.StaticVector.prototype, - 'un', - ol.source.StaticVector.prototype.un); + ol.source.TileImage.prototype, + 'getProperties', + ol.source.TileImage.prototype.getProperties); goog.exportProperty( - ol.source.StaticVector.prototype, - 'unByKey', - ol.source.StaticVector.prototype.unByKey); + ol.source.TileImage.prototype, + 'set', + ol.source.TileImage.prototype.set); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'readFeatures', - ol.source.GeoJSON.prototype.readFeatures); + ol.source.TileImage.prototype, + 'setProperties', + ol.source.TileImage.prototype.setProperties); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'addFeature', - ol.source.GeoJSON.prototype.addFeature); + ol.source.TileImage.prototype, + 'unset', + ol.source.TileImage.prototype.unset); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'addFeatures', - ol.source.GeoJSON.prototype.addFeatures); + ol.source.TileImage.prototype, + 'changed', + ol.source.TileImage.prototype.changed); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'clear', - ol.source.GeoJSON.prototype.clear); + ol.source.TileImage.prototype, + 'getRevision', + ol.source.TileImage.prototype.getRevision); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'forEachFeature', - ol.source.GeoJSON.prototype.forEachFeature); + ol.source.TileImage.prototype, + 'on', + ol.source.TileImage.prototype.on); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'forEachFeatureInExtent', - ol.source.GeoJSON.prototype.forEachFeatureInExtent); + ol.source.TileImage.prototype, + 'once', + ol.source.TileImage.prototype.once); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.GeoJSON.prototype.forEachFeatureIntersectingExtent); + ol.source.TileImage.prototype, + 'un', + ol.source.TileImage.prototype.un); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'getFeatures', - ol.source.GeoJSON.prototype.getFeatures); + ol.source.TileImage.prototype, + 'unByKey', + ol.source.TileImage.prototype.unByKey); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'getFeaturesAtCoordinate', - ol.source.GeoJSON.prototype.getFeaturesAtCoordinate); + ol.source.BingMaps.prototype, + 'getTileLoadFunction', + ol.source.BingMaps.prototype.getTileLoadFunction); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'getClosestFeatureToCoordinate', - ol.source.GeoJSON.prototype.getClosestFeatureToCoordinate); + ol.source.BingMaps.prototype, + 'getTileUrlFunction', + ol.source.BingMaps.prototype.getTileUrlFunction); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'getExtent', - ol.source.GeoJSON.prototype.getExtent); + ol.source.BingMaps.prototype, + 'setTileLoadFunction', + ol.source.BingMaps.prototype.setTileLoadFunction); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'getFeatureById', - ol.source.GeoJSON.prototype.getFeatureById); + ol.source.BingMaps.prototype, + 'setTileUrlFunction', + ol.source.BingMaps.prototype.setTileUrlFunction); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'removeFeature', - ol.source.GeoJSON.prototype.removeFeature); + ol.source.BingMaps.prototype, + 'getTileGrid', + ol.source.BingMaps.prototype.getTileGrid); goog.exportProperty( - ol.source.GeoJSON.prototype, + ol.source.BingMaps.prototype, 'getAttributions', - ol.source.GeoJSON.prototype.getAttributions); + ol.source.BingMaps.prototype.getAttributions); goog.exportProperty( - ol.source.GeoJSON.prototype, + ol.source.BingMaps.prototype, 'getLogo', - ol.source.GeoJSON.prototype.getLogo); + ol.source.BingMaps.prototype.getLogo); goog.exportProperty( - ol.source.GeoJSON.prototype, + ol.source.BingMaps.prototype, 'getProjection', - ol.source.GeoJSON.prototype.getProjection); + ol.source.BingMaps.prototype.getProjection); goog.exportProperty( - ol.source.GeoJSON.prototype, + ol.source.BingMaps.prototype, 'getState', - ol.source.GeoJSON.prototype.getState); - -goog.exportProperty( - ol.source.GeoJSON.prototype, - 'changed', - ol.source.GeoJSON.prototype.changed); + ol.source.BingMaps.prototype.getState); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'getRevision', - ol.source.GeoJSON.prototype.getRevision); + ol.source.BingMaps.prototype, + 'get', + ol.source.BingMaps.prototype.get); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'on', - ol.source.GeoJSON.prototype.on); + ol.source.BingMaps.prototype, + 'getKeys', + ol.source.BingMaps.prototype.getKeys); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'once', - ol.source.GeoJSON.prototype.once); + ol.source.BingMaps.prototype, + 'getProperties', + ol.source.BingMaps.prototype.getProperties); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'un', - ol.source.GeoJSON.prototype.un); + ol.source.BingMaps.prototype, + 'set', + ol.source.BingMaps.prototype.set); goog.exportProperty( - ol.source.GeoJSON.prototype, - 'unByKey', - ol.source.GeoJSON.prototype.unByKey); + ol.source.BingMaps.prototype, + 'setProperties', + ol.source.BingMaps.prototype.setProperties); goog.exportProperty( - ol.source.GPX.prototype, - 'readFeatures', - ol.source.GPX.prototype.readFeatures); + ol.source.BingMaps.prototype, + 'unset', + ol.source.BingMaps.prototype.unset); goog.exportProperty( - ol.source.GPX.prototype, - 'addFeature', - ol.source.GPX.prototype.addFeature); + ol.source.BingMaps.prototype, + 'changed', + ol.source.BingMaps.prototype.changed); goog.exportProperty( - ol.source.GPX.prototype, - 'addFeatures', - ol.source.GPX.prototype.addFeatures); + ol.source.BingMaps.prototype, + 'getRevision', + ol.source.BingMaps.prototype.getRevision); goog.exportProperty( - ol.source.GPX.prototype, - 'clear', - ol.source.GPX.prototype.clear); + ol.source.BingMaps.prototype, + 'on', + ol.source.BingMaps.prototype.on); goog.exportProperty( - ol.source.GPX.prototype, - 'forEachFeature', - ol.source.GPX.prototype.forEachFeature); + ol.source.BingMaps.prototype, + 'once', + ol.source.BingMaps.prototype.once); goog.exportProperty( - ol.source.GPX.prototype, - 'forEachFeatureInExtent', - ol.source.GPX.prototype.forEachFeatureInExtent); + ol.source.BingMaps.prototype, + 'un', + ol.source.BingMaps.prototype.un); goog.exportProperty( - ol.source.GPX.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.GPX.prototype.forEachFeatureIntersectingExtent); + ol.source.BingMaps.prototype, + 'unByKey', + ol.source.BingMaps.prototype.unByKey); goog.exportProperty( - ol.source.GPX.prototype, - 'getFeatures', - ol.source.GPX.prototype.getFeatures); + ol.source.Vector.prototype, + 'getAttributions', + ol.source.Vector.prototype.getAttributions); goog.exportProperty( - ol.source.GPX.prototype, - 'getFeaturesAtCoordinate', - ol.source.GPX.prototype.getFeaturesAtCoordinate); + ol.source.Vector.prototype, + 'getLogo', + ol.source.Vector.prototype.getLogo); goog.exportProperty( - ol.source.GPX.prototype, - 'getClosestFeatureToCoordinate', - ol.source.GPX.prototype.getClosestFeatureToCoordinate); + ol.source.Vector.prototype, + 'getProjection', + ol.source.Vector.prototype.getProjection); goog.exportProperty( - ol.source.GPX.prototype, - 'getExtent', - ol.source.GPX.prototype.getExtent); + ol.source.Vector.prototype, + 'getState', + ol.source.Vector.prototype.getState); goog.exportProperty( - ol.source.GPX.prototype, - 'getFeatureById', - ol.source.GPX.prototype.getFeatureById); + ol.source.Vector.prototype, + 'get', + ol.source.Vector.prototype.get); goog.exportProperty( - ol.source.GPX.prototype, - 'removeFeature', - ol.source.GPX.prototype.removeFeature); + ol.source.Vector.prototype, + 'getKeys', + ol.source.Vector.prototype.getKeys); goog.exportProperty( - ol.source.GPX.prototype, - 'getAttributions', - ol.source.GPX.prototype.getAttributions); + ol.source.Vector.prototype, + 'getProperties', + ol.source.Vector.prototype.getProperties); goog.exportProperty( - ol.source.GPX.prototype, - 'getLogo', - ol.source.GPX.prototype.getLogo); + ol.source.Vector.prototype, + 'set', + ol.source.Vector.prototype.set); goog.exportProperty( - ol.source.GPX.prototype, - 'getProjection', - ol.source.GPX.prototype.getProjection); + ol.source.Vector.prototype, + 'setProperties', + ol.source.Vector.prototype.setProperties); goog.exportProperty( - ol.source.GPX.prototype, - 'getState', - ol.source.GPX.prototype.getState); + ol.source.Vector.prototype, + 'unset', + ol.source.Vector.prototype.unset); goog.exportProperty( - ol.source.GPX.prototype, + ol.source.Vector.prototype, 'changed', - ol.source.GPX.prototype.changed); + ol.source.Vector.prototype.changed); goog.exportProperty( - ol.source.GPX.prototype, + ol.source.Vector.prototype, 'getRevision', - ol.source.GPX.prototype.getRevision); + ol.source.Vector.prototype.getRevision); goog.exportProperty( - ol.source.GPX.prototype, + ol.source.Vector.prototype, 'on', - ol.source.GPX.prototype.on); + ol.source.Vector.prototype.on); goog.exportProperty( - ol.source.GPX.prototype, + ol.source.Vector.prototype, 'once', - ol.source.GPX.prototype.once); + ol.source.Vector.prototype.once); goog.exportProperty( - ol.source.GPX.prototype, + ol.source.Vector.prototype, 'un', - ol.source.GPX.prototype.un); + ol.source.Vector.prototype.un); goog.exportProperty( - ol.source.GPX.prototype, + ol.source.Vector.prototype, 'unByKey', - ol.source.GPX.prototype.unByKey); - -goog.exportProperty( - ol.source.IGC.prototype, - 'readFeatures', - ol.source.IGC.prototype.readFeatures); + ol.source.Vector.prototype.unByKey); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'addFeature', - ol.source.IGC.prototype.addFeature); + ol.source.Cluster.prototype.addFeature); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'addFeatures', - ol.source.IGC.prototype.addFeatures); + ol.source.Cluster.prototype.addFeatures); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'clear', - ol.source.IGC.prototype.clear); + ol.source.Cluster.prototype.clear); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'forEachFeature', - ol.source.IGC.prototype.forEachFeature); + ol.source.Cluster.prototype.forEachFeature); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'forEachFeatureInExtent', - ol.source.IGC.prototype.forEachFeatureInExtent); + ol.source.Cluster.prototype.forEachFeatureInExtent); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'forEachFeatureIntersectingExtent', - ol.source.IGC.prototype.forEachFeatureIntersectingExtent); + ol.source.Cluster.prototype.forEachFeatureIntersectingExtent); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, + 'getFeaturesCollection', + ol.source.Cluster.prototype.getFeaturesCollection); + +goog.exportProperty( + ol.source.Cluster.prototype, 'getFeatures', - ol.source.IGC.prototype.getFeatures); + ol.source.Cluster.prototype.getFeatures); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getFeaturesAtCoordinate', - ol.source.IGC.prototype.getFeaturesAtCoordinate); + ol.source.Cluster.prototype.getFeaturesAtCoordinate); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getFeaturesInExtent', + ol.source.Cluster.prototype.getFeaturesInExtent); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getClosestFeatureToCoordinate', - ol.source.IGC.prototype.getClosestFeatureToCoordinate); + ol.source.Cluster.prototype.getClosestFeatureToCoordinate); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getExtent', - ol.source.IGC.prototype.getExtent); + ol.source.Cluster.prototype.getExtent); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getFeatureById', - ol.source.IGC.prototype.getFeatureById); + ol.source.Cluster.prototype.getFeatureById); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'removeFeature', - ol.source.IGC.prototype.removeFeature); + ol.source.Cluster.prototype.removeFeature); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getAttributions', - ol.source.IGC.prototype.getAttributions); + ol.source.Cluster.prototype.getAttributions); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getLogo', - ol.source.IGC.prototype.getLogo); + ol.source.Cluster.prototype.getLogo); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getProjection', - ol.source.IGC.prototype.getProjection); + ol.source.Cluster.prototype.getProjection); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getState', - ol.source.IGC.prototype.getState); + ol.source.Cluster.prototype.getState); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'get', + ol.source.Cluster.prototype.get); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getKeys', + ol.source.Cluster.prototype.getKeys); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'getProperties', + ol.source.Cluster.prototype.getProperties); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'set', + ol.source.Cluster.prototype.set); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'setProperties', + ol.source.Cluster.prototype.setProperties); + +goog.exportProperty( + ol.source.Cluster.prototype, + 'unset', + ol.source.Cluster.prototype.unset); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'changed', - ol.source.IGC.prototype.changed); + ol.source.Cluster.prototype.changed); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'getRevision', - ol.source.IGC.prototype.getRevision); + ol.source.Cluster.prototype.getRevision); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'on', - ol.source.IGC.prototype.on); + ol.source.Cluster.prototype.on); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'once', - ol.source.IGC.prototype.once); + ol.source.Cluster.prototype.once); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'un', - ol.source.IGC.prototype.un); + ol.source.Cluster.prototype.un); goog.exportProperty( - ol.source.IGC.prototype, + ol.source.Cluster.prototype, 'unByKey', - ol.source.IGC.prototype.unByKey); + ol.source.Cluster.prototype.unByKey); goog.exportProperty( ol.source.Image.prototype, @@ -118221,6 +121642,36 @@ goog.exportProperty( 'getState', ol.source.Image.prototype.getState); +goog.exportProperty( + ol.source.Image.prototype, + 'get', + ol.source.Image.prototype.get); + +goog.exportProperty( + ol.source.Image.prototype, + 'getKeys', + ol.source.Image.prototype.getKeys); + +goog.exportProperty( + ol.source.Image.prototype, + 'getProperties', + ol.source.Image.prototype.getProperties); + +goog.exportProperty( + ol.source.Image.prototype, + 'set', + ol.source.Image.prototype.set); + +goog.exportProperty( + ol.source.Image.prototype, + 'setProperties', + ol.source.Image.prototype.setProperties); + +goog.exportProperty( + ol.source.Image.prototype, + 'unset', + ol.source.Image.prototype.unset); + goog.exportProperty( ol.source.Image.prototype, 'changed', @@ -118271,6 +121722,36 @@ goog.exportProperty( 'getState', ol.source.ImageCanvas.prototype.getState); +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'get', + ol.source.ImageCanvas.prototype.get); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getKeys', + ol.source.ImageCanvas.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'getProperties', + ol.source.ImageCanvas.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'set', + ol.source.ImageCanvas.prototype.set); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'setProperties', + ol.source.ImageCanvas.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageCanvas.prototype, + 'unset', + ol.source.ImageCanvas.prototype.unset); + goog.exportProperty( ol.source.ImageCanvas.prototype, 'changed', @@ -118321,6 +121802,36 @@ goog.exportProperty( 'getState', ol.source.ImageMapGuide.prototype.getState); +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'get', + ol.source.ImageMapGuide.prototype.get); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getKeys', + ol.source.ImageMapGuide.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'getProperties', + ol.source.ImageMapGuide.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'set', + ol.source.ImageMapGuide.prototype.set); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'setProperties', + ol.source.ImageMapGuide.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageMapGuide.prototype, + 'unset', + ol.source.ImageMapGuide.prototype.unset); + goog.exportProperty( ol.source.ImageMapGuide.prototype, 'changed', @@ -118371,6 +121882,36 @@ goog.exportProperty( 'getState', ol.source.ImageStatic.prototype.getState); +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'get', + ol.source.ImageStatic.prototype.get); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getKeys', + ol.source.ImageStatic.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'getProperties', + ol.source.ImageStatic.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'set', + ol.source.ImageStatic.prototype.set); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'setProperties', + ol.source.ImageStatic.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageStatic.prototype, + 'unset', + ol.source.ImageStatic.prototype.unset); + goog.exportProperty( ol.source.ImageStatic.prototype, 'changed', @@ -118421,6 +121962,36 @@ goog.exportProperty( 'getState', ol.source.ImageVector.prototype.getState); +goog.exportProperty( + ol.source.ImageVector.prototype, + 'get', + ol.source.ImageVector.prototype.get); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getKeys', + ol.source.ImageVector.prototype.getKeys); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'getProperties', + ol.source.ImageVector.prototype.getProperties); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'set', + ol.source.ImageVector.prototype.set); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'setProperties', + ol.source.ImageVector.prototype.setProperties); + +goog.exportProperty( + ol.source.ImageVector.prototype, + 'unset', + ol.source.ImageVector.prototype.unset); + goog.exportProperty( ol.source.ImageVector.prototype, 'changed', @@ -118473,148 +122044,63 @@ goog.exportProperty( goog.exportProperty( ol.source.ImageWMS.prototype, - 'changed', - ol.source.ImageWMS.prototype.changed); + 'get', + ol.source.ImageWMS.prototype.get); goog.exportProperty( ol.source.ImageWMS.prototype, - 'getRevision', - ol.source.ImageWMS.prototype.getRevision); + 'getKeys', + ol.source.ImageWMS.prototype.getKeys); goog.exportProperty( ol.source.ImageWMS.prototype, - 'on', - ol.source.ImageWMS.prototype.on); + 'getProperties', + ol.source.ImageWMS.prototype.getProperties); goog.exportProperty( ol.source.ImageWMS.prototype, - 'once', - ol.source.ImageWMS.prototype.once); + 'set', + ol.source.ImageWMS.prototype.set); goog.exportProperty( ol.source.ImageWMS.prototype, - 'un', - ol.source.ImageWMS.prototype.un); + 'setProperties', + ol.source.ImageWMS.prototype.setProperties); goog.exportProperty( ol.source.ImageWMS.prototype, - 'unByKey', - ol.source.ImageWMS.prototype.unByKey); - -goog.exportProperty( - ol.source.KML.prototype, - 'readFeatures', - ol.source.KML.prototype.readFeatures); - -goog.exportProperty( - ol.source.KML.prototype, - 'addFeature', - ol.source.KML.prototype.addFeature); - -goog.exportProperty( - ol.source.KML.prototype, - 'addFeatures', - ol.source.KML.prototype.addFeatures); - -goog.exportProperty( - ol.source.KML.prototype, - 'clear', - ol.source.KML.prototype.clear); - -goog.exportProperty( - ol.source.KML.prototype, - 'forEachFeature', - ol.source.KML.prototype.forEachFeature); - -goog.exportProperty( - ol.source.KML.prototype, - 'forEachFeatureInExtent', - ol.source.KML.prototype.forEachFeatureInExtent); - -goog.exportProperty( - ol.source.KML.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.KML.prototype.forEachFeatureIntersectingExtent); - -goog.exportProperty( - ol.source.KML.prototype, - 'getFeatures', - ol.source.KML.prototype.getFeatures); - -goog.exportProperty( - ol.source.KML.prototype, - 'getFeaturesAtCoordinate', - ol.source.KML.prototype.getFeaturesAtCoordinate); + 'unset', + ol.source.ImageWMS.prototype.unset); goog.exportProperty( - ol.source.KML.prototype, - 'getClosestFeatureToCoordinate', - ol.source.KML.prototype.getClosestFeatureToCoordinate); - -goog.exportProperty( - ol.source.KML.prototype, - 'getExtent', - ol.source.KML.prototype.getExtent); - -goog.exportProperty( - ol.source.KML.prototype, - 'getFeatureById', - ol.source.KML.prototype.getFeatureById); - -goog.exportProperty( - ol.source.KML.prototype, - 'removeFeature', - ol.source.KML.prototype.removeFeature); - -goog.exportProperty( - ol.source.KML.prototype, - 'getAttributions', - ol.source.KML.prototype.getAttributions); - -goog.exportProperty( - ol.source.KML.prototype, - 'getLogo', - ol.source.KML.prototype.getLogo); - -goog.exportProperty( - ol.source.KML.prototype, - 'getProjection', - ol.source.KML.prototype.getProjection); - -goog.exportProperty( - ol.source.KML.prototype, - 'getState', - ol.source.KML.prototype.getState); - -goog.exportProperty( - ol.source.KML.prototype, + ol.source.ImageWMS.prototype, 'changed', - ol.source.KML.prototype.changed); + ol.source.ImageWMS.prototype.changed); goog.exportProperty( - ol.source.KML.prototype, + ol.source.ImageWMS.prototype, 'getRevision', - ol.source.KML.prototype.getRevision); + ol.source.ImageWMS.prototype.getRevision); goog.exportProperty( - ol.source.KML.prototype, + ol.source.ImageWMS.prototype, 'on', - ol.source.KML.prototype.on); + ol.source.ImageWMS.prototype.on); goog.exportProperty( - ol.source.KML.prototype, + ol.source.ImageWMS.prototype, 'once', - ol.source.KML.prototype.once); + ol.source.ImageWMS.prototype.once); goog.exportProperty( - ol.source.KML.prototype, + ol.source.ImageWMS.prototype, 'un', - ol.source.KML.prototype.un); + ol.source.ImageWMS.prototype.un); goog.exportProperty( - ol.source.KML.prototype, + ol.source.ImageWMS.prototype, 'unByKey', - ol.source.KML.prototype.unByKey); + ol.source.ImageWMS.prototype.unByKey); goog.exportProperty( ol.source.XYZ.prototype, @@ -118631,6 +122117,11 @@ goog.exportProperty( 'setTileLoadFunction', ol.source.XYZ.prototype.setTileLoadFunction); +goog.exportProperty( + ol.source.XYZ.prototype, + 'setTileUrlFunction', + ol.source.XYZ.prototype.setTileUrlFunction); + goog.exportProperty( ol.source.XYZ.prototype, 'getTileGrid', @@ -118656,6 +122147,36 @@ goog.exportProperty( 'getState', ol.source.XYZ.prototype.getState); +goog.exportProperty( + ol.source.XYZ.prototype, + 'get', + ol.source.XYZ.prototype.get); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getKeys', + ol.source.XYZ.prototype.getKeys); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'getProperties', + ol.source.XYZ.prototype.getProperties); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'set', + ol.source.XYZ.prototype.set); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'setProperties', + ol.source.XYZ.prototype.setProperties); + +goog.exportProperty( + ol.source.XYZ.prototype, + 'unset', + ol.source.XYZ.prototype.unset); + goog.exportProperty( ol.source.XYZ.prototype, 'changed', @@ -118686,11 +122207,6 @@ goog.exportProperty( 'unByKey', ol.source.XYZ.prototype.unByKey); -goog.exportProperty( - ol.source.MapQuest.prototype, - 'setTileUrlFunction', - ol.source.MapQuest.prototype.setTileUrlFunction); - goog.exportProperty( ol.source.MapQuest.prototype, 'setUrl', @@ -118711,6 +122227,11 @@ goog.exportProperty( 'setTileLoadFunction', ol.source.MapQuest.prototype.setTileLoadFunction); +goog.exportProperty( + ol.source.MapQuest.prototype, + 'setTileUrlFunction', + ol.source.MapQuest.prototype.setTileUrlFunction); + goog.exportProperty( ol.source.MapQuest.prototype, 'getTileGrid', @@ -118736,6 +122257,36 @@ goog.exportProperty( 'getState', ol.source.MapQuest.prototype.getState); +goog.exportProperty( + ol.source.MapQuest.prototype, + 'get', + ol.source.MapQuest.prototype.get); + +goog.exportProperty( + ol.source.MapQuest.prototype, + 'getKeys', + ol.source.MapQuest.prototype.getKeys); + +goog.exportProperty( + ol.source.MapQuest.prototype, + 'getProperties', + ol.source.MapQuest.prototype.getProperties); + +goog.exportProperty( + ol.source.MapQuest.prototype, + 'set', + ol.source.MapQuest.prototype.set); + +goog.exportProperty( + ol.source.MapQuest.prototype, + 'setProperties', + ol.source.MapQuest.prototype.setProperties); + +goog.exportProperty( + ol.source.MapQuest.prototype, + 'unset', + ol.source.MapQuest.prototype.unset); + goog.exportProperty( ol.source.MapQuest.prototype, 'changed', @@ -118766,11 +122317,6 @@ goog.exportProperty( 'unByKey', ol.source.MapQuest.prototype.unByKey); -goog.exportProperty( - ol.source.OSM.prototype, - 'setTileUrlFunction', - ol.source.OSM.prototype.setTileUrlFunction); - goog.exportProperty( ol.source.OSM.prototype, 'setUrl', @@ -118791,6 +122337,11 @@ goog.exportProperty( 'setTileLoadFunction', ol.source.OSM.prototype.setTileLoadFunction); +goog.exportProperty( + ol.source.OSM.prototype, + 'setTileUrlFunction', + ol.source.OSM.prototype.setTileUrlFunction); + goog.exportProperty( ol.source.OSM.prototype, 'getTileGrid', @@ -118818,333 +122369,358 @@ goog.exportProperty( goog.exportProperty( ol.source.OSM.prototype, - 'changed', - ol.source.OSM.prototype.changed); + 'get', + ol.source.OSM.prototype.get); goog.exportProperty( ol.source.OSM.prototype, - 'getRevision', - ol.source.OSM.prototype.getRevision); + 'getKeys', + ol.source.OSM.prototype.getKeys); goog.exportProperty( ol.source.OSM.prototype, - 'on', - ol.source.OSM.prototype.on); + 'getProperties', + ol.source.OSM.prototype.getProperties); goog.exportProperty( ol.source.OSM.prototype, - 'once', - ol.source.OSM.prototype.once); + 'set', + ol.source.OSM.prototype.set); goog.exportProperty( ol.source.OSM.prototype, - 'un', - ol.source.OSM.prototype.un); + 'setProperties', + ol.source.OSM.prototype.setProperties); goog.exportProperty( ol.source.OSM.prototype, - 'unByKey', - ol.source.OSM.prototype.unByKey); - -goog.exportProperty( - ol.source.OSMXML.prototype, - 'readFeatures', - ol.source.OSMXML.prototype.readFeatures); + 'unset', + ol.source.OSM.prototype.unset); goog.exportProperty( - ol.source.OSMXML.prototype, - 'addFeature', - ol.source.OSMXML.prototype.addFeature); + ol.source.OSM.prototype, + 'changed', + ol.source.OSM.prototype.changed); goog.exportProperty( - ol.source.OSMXML.prototype, - 'addFeatures', - ol.source.OSMXML.prototype.addFeatures); + ol.source.OSM.prototype, + 'getRevision', + ol.source.OSM.prototype.getRevision); goog.exportProperty( - ol.source.OSMXML.prototype, - 'clear', - ol.source.OSMXML.prototype.clear); + ol.source.OSM.prototype, + 'on', + ol.source.OSM.prototype.on); goog.exportProperty( - ol.source.OSMXML.prototype, - 'forEachFeature', - ol.source.OSMXML.prototype.forEachFeature); + ol.source.OSM.prototype, + 'once', + ol.source.OSM.prototype.once); goog.exportProperty( - ol.source.OSMXML.prototype, - 'forEachFeatureInExtent', - ol.source.OSMXML.prototype.forEachFeatureInExtent); + ol.source.OSM.prototype, + 'un', + ol.source.OSM.prototype.un); goog.exportProperty( - ol.source.OSMXML.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.OSMXML.prototype.forEachFeatureIntersectingExtent); + ol.source.OSM.prototype, + 'unByKey', + ol.source.OSM.prototype.unByKey); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getFeatures', - ol.source.OSMXML.prototype.getFeatures); + ol.source.Raster.prototype, + 'getAttributions', + ol.source.Raster.prototype.getAttributions); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getFeaturesAtCoordinate', - ol.source.OSMXML.prototype.getFeaturesAtCoordinate); + ol.source.Raster.prototype, + 'getLogo', + ol.source.Raster.prototype.getLogo); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getClosestFeatureToCoordinate', - ol.source.OSMXML.prototype.getClosestFeatureToCoordinate); + ol.source.Raster.prototype, + 'getProjection', + ol.source.Raster.prototype.getProjection); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getExtent', - ol.source.OSMXML.prototype.getExtent); + ol.source.Raster.prototype, + 'getState', + ol.source.Raster.prototype.getState); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getFeatureById', - ol.source.OSMXML.prototype.getFeatureById); + ol.source.Raster.prototype, + 'get', + ol.source.Raster.prototype.get); goog.exportProperty( - ol.source.OSMXML.prototype, - 'removeFeature', - ol.source.OSMXML.prototype.removeFeature); + ol.source.Raster.prototype, + 'getKeys', + ol.source.Raster.prototype.getKeys); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getAttributions', - ol.source.OSMXML.prototype.getAttributions); + ol.source.Raster.prototype, + 'getProperties', + ol.source.Raster.prototype.getProperties); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getLogo', - ol.source.OSMXML.prototype.getLogo); + ol.source.Raster.prototype, + 'set', + ol.source.Raster.prototype.set); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getProjection', - ol.source.OSMXML.prototype.getProjection); + ol.source.Raster.prototype, + 'setProperties', + ol.source.Raster.prototype.setProperties); goog.exportProperty( - ol.source.OSMXML.prototype, - 'getState', - ol.source.OSMXML.prototype.getState); + ol.source.Raster.prototype, + 'unset', + ol.source.Raster.prototype.unset); goog.exportProperty( - ol.source.OSMXML.prototype, + ol.source.Raster.prototype, 'changed', - ol.source.OSMXML.prototype.changed); + ol.source.Raster.prototype.changed); goog.exportProperty( - ol.source.OSMXML.prototype, + ol.source.Raster.prototype, 'getRevision', - ol.source.OSMXML.prototype.getRevision); + ol.source.Raster.prototype.getRevision); goog.exportProperty( - ol.source.OSMXML.prototype, + ol.source.Raster.prototype, 'on', - ol.source.OSMXML.prototype.on); + ol.source.Raster.prototype.on); goog.exportProperty( - ol.source.OSMXML.prototype, + ol.source.Raster.prototype, 'once', - ol.source.OSMXML.prototype.once); + ol.source.Raster.prototype.once); goog.exportProperty( - ol.source.OSMXML.prototype, + ol.source.Raster.prototype, 'un', - ol.source.OSMXML.prototype.un); + ol.source.Raster.prototype.un); goog.exportProperty( - ol.source.OSMXML.prototype, + ol.source.Raster.prototype, 'unByKey', - ol.source.OSMXML.prototype.unByKey); + ol.source.Raster.prototype.unByKey); goog.exportProperty( - ol.source.ServerVector.prototype, - 'addFeature', - ol.source.ServerVector.prototype.addFeature); + ol.source.Stamen.prototype, + 'setUrl', + ol.source.Stamen.prototype.setUrl); goog.exportProperty( - ol.source.ServerVector.prototype, - 'addFeatures', - ol.source.ServerVector.prototype.addFeatures); + ol.source.Stamen.prototype, + 'getTileLoadFunction', + ol.source.Stamen.prototype.getTileLoadFunction); goog.exportProperty( - ol.source.ServerVector.prototype, - 'forEachFeature', - ol.source.ServerVector.prototype.forEachFeature); + ol.source.Stamen.prototype, + 'getTileUrlFunction', + ol.source.Stamen.prototype.getTileUrlFunction); goog.exportProperty( - ol.source.ServerVector.prototype, - 'forEachFeatureInExtent', - ol.source.ServerVector.prototype.forEachFeatureInExtent); + ol.source.Stamen.prototype, + 'setTileLoadFunction', + ol.source.Stamen.prototype.setTileLoadFunction); goog.exportProperty( - ol.source.ServerVector.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.ServerVector.prototype.forEachFeatureIntersectingExtent); + ol.source.Stamen.prototype, + 'setTileUrlFunction', + ol.source.Stamen.prototype.setTileUrlFunction); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getFeatures', - ol.source.ServerVector.prototype.getFeatures); + ol.source.Stamen.prototype, + 'getTileGrid', + ol.source.Stamen.prototype.getTileGrid); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getFeaturesAtCoordinate', - ol.source.ServerVector.prototype.getFeaturesAtCoordinate); + ol.source.Stamen.prototype, + 'getAttributions', + ol.source.Stamen.prototype.getAttributions); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getClosestFeatureToCoordinate', - ol.source.ServerVector.prototype.getClosestFeatureToCoordinate); + ol.source.Stamen.prototype, + 'getLogo', + ol.source.Stamen.prototype.getLogo); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getExtent', - ol.source.ServerVector.prototype.getExtent); + ol.source.Stamen.prototype, + 'getProjection', + ol.source.Stamen.prototype.getProjection); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getFeatureById', - ol.source.ServerVector.prototype.getFeatureById); + ol.source.Stamen.prototype, + 'getState', + ol.source.Stamen.prototype.getState); goog.exportProperty( - ol.source.ServerVector.prototype, - 'removeFeature', - ol.source.ServerVector.prototype.removeFeature); + ol.source.Stamen.prototype, + 'get', + ol.source.Stamen.prototype.get); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getAttributions', - ol.source.ServerVector.prototype.getAttributions); + ol.source.Stamen.prototype, + 'getKeys', + ol.source.Stamen.prototype.getKeys); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getLogo', - ol.source.ServerVector.prototype.getLogo); + ol.source.Stamen.prototype, + 'getProperties', + ol.source.Stamen.prototype.getProperties); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getProjection', - ol.source.ServerVector.prototype.getProjection); + ol.source.Stamen.prototype, + 'set', + ol.source.Stamen.prototype.set); goog.exportProperty( - ol.source.ServerVector.prototype, - 'getState', - ol.source.ServerVector.prototype.getState); + ol.source.Stamen.prototype, + 'setProperties', + ol.source.Stamen.prototype.setProperties); goog.exportProperty( - ol.source.ServerVector.prototype, + ol.source.Stamen.prototype, + 'unset', + ol.source.Stamen.prototype.unset); + +goog.exportProperty( + ol.source.Stamen.prototype, 'changed', - ol.source.ServerVector.prototype.changed); + ol.source.Stamen.prototype.changed); goog.exportProperty( - ol.source.ServerVector.prototype, + ol.source.Stamen.prototype, 'getRevision', - ol.source.ServerVector.prototype.getRevision); + ol.source.Stamen.prototype.getRevision); goog.exportProperty( - ol.source.ServerVector.prototype, + ol.source.Stamen.prototype, 'on', - ol.source.ServerVector.prototype.on); + ol.source.Stamen.prototype.on); goog.exportProperty( - ol.source.ServerVector.prototype, + ol.source.Stamen.prototype, 'once', - ol.source.ServerVector.prototype.once); - -goog.exportProperty( - ol.source.ServerVector.prototype, - 'un', - ol.source.ServerVector.prototype.un); - -goog.exportProperty( - ol.source.ServerVector.prototype, - 'unByKey', - ol.source.ServerVector.prototype.unByKey); + ol.source.Stamen.prototype.once); goog.exportProperty( ol.source.Stamen.prototype, - 'setTileUrlFunction', - ol.source.Stamen.prototype.setTileUrlFunction); + 'un', + ol.source.Stamen.prototype.un); goog.exportProperty( ol.source.Stamen.prototype, - 'setUrl', - ol.source.Stamen.prototype.setUrl); + 'unByKey', + ol.source.Stamen.prototype.unByKey); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getTileLoadFunction', - ol.source.Stamen.prototype.getTileLoadFunction); + ol.source.TileArcGISRest.prototype.getTileLoadFunction); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getTileUrlFunction', - ol.source.Stamen.prototype.getTileUrlFunction); + ol.source.TileArcGISRest.prototype.getTileUrlFunction); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'setTileLoadFunction', - ol.source.Stamen.prototype.setTileLoadFunction); + ol.source.TileArcGISRest.prototype.setTileLoadFunction); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, + 'setTileUrlFunction', + ol.source.TileArcGISRest.prototype.setTileUrlFunction); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, 'getTileGrid', - ol.source.Stamen.prototype.getTileGrid); + ol.source.TileArcGISRest.prototype.getTileGrid); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getAttributions', - ol.source.Stamen.prototype.getAttributions); + ol.source.TileArcGISRest.prototype.getAttributions); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getLogo', - ol.source.Stamen.prototype.getLogo); + ol.source.TileArcGISRest.prototype.getLogo); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getProjection', - ol.source.Stamen.prototype.getProjection); + ol.source.TileArcGISRest.prototype.getProjection); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getState', - ol.source.Stamen.prototype.getState); + ol.source.TileArcGISRest.prototype.getState); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, + 'get', + ol.source.TileArcGISRest.prototype.get); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getKeys', + ol.source.TileArcGISRest.prototype.getKeys); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'getProperties', + ol.source.TileArcGISRest.prototype.getProperties); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'set', + ol.source.TileArcGISRest.prototype.set); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'setProperties', + ol.source.TileArcGISRest.prototype.setProperties); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, + 'unset', + ol.source.TileArcGISRest.prototype.unset); + +goog.exportProperty( + ol.source.TileArcGISRest.prototype, 'changed', - ol.source.Stamen.prototype.changed); + ol.source.TileArcGISRest.prototype.changed); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'getRevision', - ol.source.Stamen.prototype.getRevision); + ol.source.TileArcGISRest.prototype.getRevision); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'on', - ol.source.Stamen.prototype.on); + ol.source.TileArcGISRest.prototype.on); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'once', - ol.source.Stamen.prototype.once); + ol.source.TileArcGISRest.prototype.once); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'un', - ol.source.Stamen.prototype.un); + ol.source.TileArcGISRest.prototype.un); goog.exportProperty( - ol.source.Stamen.prototype, + ol.source.TileArcGISRest.prototype, 'unByKey', - ol.source.Stamen.prototype.unByKey); + ol.source.TileArcGISRest.prototype.unByKey); goog.exportProperty( ol.source.TileDebug.prototype, @@ -119171,6 +122747,36 @@ goog.exportProperty( 'getState', ol.source.TileDebug.prototype.getState); +goog.exportProperty( + ol.source.TileDebug.prototype, + 'get', + ol.source.TileDebug.prototype.get); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getKeys', + ol.source.TileDebug.prototype.getKeys); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'getProperties', + ol.source.TileDebug.prototype.getProperties); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'set', + ol.source.TileDebug.prototype.set); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'setProperties', + ol.source.TileDebug.prototype.setProperties); + +goog.exportProperty( + ol.source.TileDebug.prototype, + 'unset', + ol.source.TileDebug.prototype.unset); + goog.exportProperty( ol.source.TileDebug.prototype, 'changed', @@ -119246,6 +122852,36 @@ goog.exportProperty( 'getState', ol.source.TileJSON.prototype.getState); +goog.exportProperty( + ol.source.TileJSON.prototype, + 'get', + ol.source.TileJSON.prototype.get); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getKeys', + ol.source.TileJSON.prototype.getKeys); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'getProperties', + ol.source.TileJSON.prototype.getProperties); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'set', + ol.source.TileJSON.prototype.set); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'setProperties', + ol.source.TileJSON.prototype.setProperties); + +goog.exportProperty( + ol.source.TileJSON.prototype, + 'unset', + ol.source.TileJSON.prototype.unset); + goog.exportProperty( ol.source.TileJSON.prototype, 'changed', @@ -119301,6 +122937,36 @@ goog.exportProperty( 'getState', ol.source.TileUTFGrid.prototype.getState); +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'get', + ol.source.TileUTFGrid.prototype.get); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getKeys', + ol.source.TileUTFGrid.prototype.getKeys); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'getProperties', + ol.source.TileUTFGrid.prototype.getProperties); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'set', + ol.source.TileUTFGrid.prototype.set); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'setProperties', + ol.source.TileUTFGrid.prototype.setProperties); + +goog.exportProperty( + ol.source.TileUTFGrid.prototype, + 'unset', + ol.source.TileUTFGrid.prototype.unset); + goog.exportProperty( ol.source.TileUTFGrid.prototype, 'changed', @@ -119333,13 +122999,13 @@ goog.exportProperty( goog.exportProperty( ol.source.TileVector.prototype, - 'readFeatures', - ol.source.TileVector.prototype.readFeatures); + 'forEachFeatureIntersectingExtent', + ol.source.TileVector.prototype.forEachFeatureIntersectingExtent); goog.exportProperty( ol.source.TileVector.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.TileVector.prototype.forEachFeatureIntersectingExtent); + 'getFeaturesCollection', + ol.source.TileVector.prototype.getFeaturesCollection); goog.exportProperty( ol.source.TileVector.prototype, @@ -119371,6 +123037,36 @@ goog.exportProperty( 'getState', ol.source.TileVector.prototype.getState); +goog.exportProperty( + ol.source.TileVector.prototype, + 'get', + ol.source.TileVector.prototype.get); + +goog.exportProperty( + ol.source.TileVector.prototype, + 'getKeys', + ol.source.TileVector.prototype.getKeys); + +goog.exportProperty( + ol.source.TileVector.prototype, + 'getProperties', + ol.source.TileVector.prototype.getProperties); + +goog.exportProperty( + ol.source.TileVector.prototype, + 'set', + ol.source.TileVector.prototype.set); + +goog.exportProperty( + ol.source.TileVector.prototype, + 'setProperties', + ol.source.TileVector.prototype.setProperties); + +goog.exportProperty( + ol.source.TileVector.prototype, + 'unset', + ol.source.TileVector.prototype.unset); + goog.exportProperty( ol.source.TileVector.prototype, 'changed', @@ -119448,148 +123144,63 @@ goog.exportProperty( goog.exportProperty( ol.source.TileWMS.prototype, - 'changed', - ol.source.TileWMS.prototype.changed); + 'get', + ol.source.TileWMS.prototype.get); goog.exportProperty( ol.source.TileWMS.prototype, - 'getRevision', - ol.source.TileWMS.prototype.getRevision); + 'getKeys', + ol.source.TileWMS.prototype.getKeys); goog.exportProperty( ol.source.TileWMS.prototype, - 'on', - ol.source.TileWMS.prototype.on); + 'getProperties', + ol.source.TileWMS.prototype.getProperties); goog.exportProperty( ol.source.TileWMS.prototype, - 'once', - ol.source.TileWMS.prototype.once); + 'set', + ol.source.TileWMS.prototype.set); goog.exportProperty( ol.source.TileWMS.prototype, - 'un', - ol.source.TileWMS.prototype.un); + 'setProperties', + ol.source.TileWMS.prototype.setProperties); goog.exportProperty( ol.source.TileWMS.prototype, - 'unByKey', - ol.source.TileWMS.prototype.unByKey); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'readFeatures', - ol.source.TopoJSON.prototype.readFeatures); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'addFeature', - ol.source.TopoJSON.prototype.addFeature); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'addFeatures', - ol.source.TopoJSON.prototype.addFeatures); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'clear', - ol.source.TopoJSON.prototype.clear); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'forEachFeature', - ol.source.TopoJSON.prototype.forEachFeature); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'forEachFeatureInExtent', - ol.source.TopoJSON.prototype.forEachFeatureInExtent); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'forEachFeatureIntersectingExtent', - ol.source.TopoJSON.prototype.forEachFeatureIntersectingExtent); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getFeatures', - ol.source.TopoJSON.prototype.getFeatures); + 'unset', + ol.source.TileWMS.prototype.unset); goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getFeaturesAtCoordinate', - ol.source.TopoJSON.prototype.getFeaturesAtCoordinate); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getClosestFeatureToCoordinate', - ol.source.TopoJSON.prototype.getClosestFeatureToCoordinate); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getExtent', - ol.source.TopoJSON.prototype.getExtent); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getFeatureById', - ol.source.TopoJSON.prototype.getFeatureById); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'removeFeature', - ol.source.TopoJSON.prototype.removeFeature); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getAttributions', - ol.source.TopoJSON.prototype.getAttributions); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getLogo', - ol.source.TopoJSON.prototype.getLogo); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getProjection', - ol.source.TopoJSON.prototype.getProjection); - -goog.exportProperty( - ol.source.TopoJSON.prototype, - 'getState', - ol.source.TopoJSON.prototype.getState); - -goog.exportProperty( - ol.source.TopoJSON.prototype, + ol.source.TileWMS.prototype, 'changed', - ol.source.TopoJSON.prototype.changed); + ol.source.TileWMS.prototype.changed); goog.exportProperty( - ol.source.TopoJSON.prototype, + ol.source.TileWMS.prototype, 'getRevision', - ol.source.TopoJSON.prototype.getRevision); + ol.source.TileWMS.prototype.getRevision); goog.exportProperty( - ol.source.TopoJSON.prototype, + ol.source.TileWMS.prototype, 'on', - ol.source.TopoJSON.prototype.on); + ol.source.TileWMS.prototype.on); goog.exportProperty( - ol.source.TopoJSON.prototype, + ol.source.TileWMS.prototype, 'once', - ol.source.TopoJSON.prototype.once); + ol.source.TileWMS.prototype.once); goog.exportProperty( - ol.source.TopoJSON.prototype, + ol.source.TileWMS.prototype, 'un', - ol.source.TopoJSON.prototype.un); + ol.source.TileWMS.prototype.un); goog.exportProperty( - ol.source.TopoJSON.prototype, + ol.source.TileWMS.prototype, 'unByKey', - ol.source.TopoJSON.prototype.unByKey); + ol.source.TileWMS.prototype.unByKey); goog.exportProperty( ol.source.WMTS.prototype, @@ -119636,6 +123247,36 @@ goog.exportProperty( 'getState', ol.source.WMTS.prototype.getState); +goog.exportProperty( + ol.source.WMTS.prototype, + 'get', + ol.source.WMTS.prototype.get); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getKeys', + ol.source.WMTS.prototype.getKeys); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'getProperties', + ol.source.WMTS.prototype.getProperties); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'set', + ol.source.WMTS.prototype.set); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'setProperties', + ol.source.WMTS.prototype.setProperties); + +goog.exportProperty( + ol.source.WMTS.prototype, + 'unset', + ol.source.WMTS.prototype.unset); + goog.exportProperty( ol.source.WMTS.prototype, 'changed', @@ -119711,6 +123352,36 @@ goog.exportProperty( 'getState', ol.source.Zoomify.prototype.getState); +goog.exportProperty( + ol.source.Zoomify.prototype, + 'get', + ol.source.Zoomify.prototype.get); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getKeys', + ol.source.Zoomify.prototype.getKeys); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'getProperties', + ol.source.Zoomify.prototype.getProperties); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'set', + ol.source.Zoomify.prototype.set); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'setProperties', + ol.source.Zoomify.prototype.setProperties); + +goog.exportProperty( + ol.source.Zoomify.prototype, + 'unset', + ol.source.Zoomify.prototype.unset); + goog.exportProperty( ol.source.Zoomify.prototype, 'changed', @@ -119742,9 +123413,394 @@ goog.exportProperty( ol.source.Zoomify.prototype.unByKey); goog.exportProperty( - ol.layer.Base.prototype, - 'bindTo', - ol.layer.Base.prototype.bindTo); + ol.renderer.Layer.prototype, + 'changed', + ol.renderer.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'getRevision', + ol.renderer.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'on', + ol.renderer.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'once', + ol.renderer.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'un', + ol.renderer.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.Layer.prototype, + 'unByKey', + ol.renderer.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'changed', + ol.renderer.webgl.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'getRevision', + ol.renderer.webgl.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'on', + ol.renderer.webgl.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'once', + ol.renderer.webgl.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'un', + ol.renderer.webgl.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.Layer.prototype, + 'unByKey', + ol.renderer.webgl.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'changed', + ol.renderer.webgl.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'getRevision', + ol.renderer.webgl.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'on', + ol.renderer.webgl.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'once', + ol.renderer.webgl.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'un', + ol.renderer.webgl.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.ImageLayer.prototype, + 'unByKey', + ol.renderer.webgl.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'changed', + ol.renderer.webgl.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'getRevision', + ol.renderer.webgl.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'on', + ol.renderer.webgl.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'once', + ol.renderer.webgl.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'un', + ol.renderer.webgl.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.TileLayer.prototype, + 'unByKey', + ol.renderer.webgl.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'changed', + ol.renderer.webgl.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'getRevision', + ol.renderer.webgl.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'on', + ol.renderer.webgl.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'once', + ol.renderer.webgl.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'un', + ol.renderer.webgl.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.webgl.VectorLayer.prototype, + 'unByKey', + ol.renderer.webgl.VectorLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.dom.Layer.prototype, + 'changed', + ol.renderer.dom.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.dom.Layer.prototype, + 'getRevision', + ol.renderer.dom.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.dom.Layer.prototype, + 'on', + ol.renderer.dom.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.dom.Layer.prototype, + 'once', + ol.renderer.dom.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.dom.Layer.prototype, + 'un', + ol.renderer.dom.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.dom.Layer.prototype, + 'unByKey', + ol.renderer.dom.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.dom.ImageLayer.prototype, + 'changed', + ol.renderer.dom.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.dom.ImageLayer.prototype, + 'getRevision', + ol.renderer.dom.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.dom.ImageLayer.prototype, + 'on', + ol.renderer.dom.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.dom.ImageLayer.prototype, + 'once', + ol.renderer.dom.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.dom.ImageLayer.prototype, + 'un', + ol.renderer.dom.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.dom.ImageLayer.prototype, + 'unByKey', + ol.renderer.dom.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.dom.TileLayer.prototype, + 'changed', + ol.renderer.dom.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.dom.TileLayer.prototype, + 'getRevision', + ol.renderer.dom.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.dom.TileLayer.prototype, + 'on', + ol.renderer.dom.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.dom.TileLayer.prototype, + 'once', + ol.renderer.dom.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.dom.TileLayer.prototype, + 'un', + ol.renderer.dom.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.dom.TileLayer.prototype, + 'unByKey', + ol.renderer.dom.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.dom.VectorLayer.prototype, + 'changed', + ol.renderer.dom.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.dom.VectorLayer.prototype, + 'getRevision', + ol.renderer.dom.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.dom.VectorLayer.prototype, + 'on', + ol.renderer.dom.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.dom.VectorLayer.prototype, + 'once', + ol.renderer.dom.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.dom.VectorLayer.prototype, + 'un', + ol.renderer.dom.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.dom.VectorLayer.prototype, + 'unByKey', + ol.renderer.dom.VectorLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'changed', + ol.renderer.canvas.Layer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'getRevision', + ol.renderer.canvas.Layer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'on', + ol.renderer.canvas.Layer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'once', + ol.renderer.canvas.Layer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'un', + ol.renderer.canvas.Layer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.Layer.prototype, + 'unByKey', + ol.renderer.canvas.Layer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'changed', + ol.renderer.canvas.ImageLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'getRevision', + ol.renderer.canvas.ImageLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'on', + ol.renderer.canvas.ImageLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'once', + ol.renderer.canvas.ImageLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'un', + ol.renderer.canvas.ImageLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.ImageLayer.prototype, + 'unByKey', + ol.renderer.canvas.ImageLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'changed', + ol.renderer.canvas.TileLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'getRevision', + ol.renderer.canvas.TileLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'on', + ol.renderer.canvas.TileLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'once', + ol.renderer.canvas.TileLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'un', + ol.renderer.canvas.TileLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.TileLayer.prototype, + 'unByKey', + ol.renderer.canvas.TileLayer.prototype.unByKey); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'changed', + ol.renderer.canvas.VectorLayer.prototype.changed); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'getRevision', + ol.renderer.canvas.VectorLayer.prototype.getRevision); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'on', + ol.renderer.canvas.VectorLayer.prototype.on); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'once', + ol.renderer.canvas.VectorLayer.prototype.once); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'un', + ol.renderer.canvas.VectorLayer.prototype.un); + +goog.exportProperty( + ol.renderer.canvas.VectorLayer.prototype, + 'unByKey', + ol.renderer.canvas.VectorLayer.prototype.unByKey); goog.exportProperty( ol.layer.Base.prototype, @@ -119773,13 +123829,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Base.prototype, - 'unbind', - ol.layer.Base.prototype.unbind); - -goog.exportProperty( - ol.layer.Base.prototype, - 'unbindAll', - ol.layer.Base.prototype.unbindAll); + 'unset', + ol.layer.Base.prototype.unset); goog.exportProperty( ol.layer.Base.prototype, @@ -119901,11 +123952,6 @@ goog.exportProperty( 'setVisible', ol.layer.Layer.prototype.setVisible); -goog.exportProperty( - ol.layer.Layer.prototype, - 'bindTo', - ol.layer.Layer.prototype.bindTo); - goog.exportProperty( ol.layer.Layer.prototype, 'get', @@ -119933,13 +123979,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Layer.prototype, - 'unbind', - ol.layer.Layer.prototype.unbind); - -goog.exportProperty( - ol.layer.Layer.prototype, - 'unbindAll', - ol.layer.Layer.prototype.unbindAll); + 'unset', + ol.layer.Layer.prototype.unset); goog.exportProperty( ol.layer.Layer.prototype, @@ -119971,6 +124012,11 @@ goog.exportProperty( 'unByKey', ol.layer.Layer.prototype.unByKey); +goog.exportProperty( + ol.layer.Vector.prototype, + 'setMap', + ol.layer.Vector.prototype.setMap); + goog.exportProperty( ol.layer.Vector.prototype, 'setSource', @@ -120066,11 +124112,6 @@ goog.exportProperty( 'setVisible', ol.layer.Vector.prototype.setVisible); -goog.exportProperty( - ol.layer.Vector.prototype, - 'bindTo', - ol.layer.Vector.prototype.bindTo); - goog.exportProperty( ol.layer.Vector.prototype, 'get', @@ -120098,13 +124139,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Vector.prototype, - 'unbind', - ol.layer.Vector.prototype.unbind); - -goog.exportProperty( - ol.layer.Vector.prototype, - 'unbindAll', - ol.layer.Vector.prototype.unbindAll); + 'unset', + ol.layer.Vector.prototype.unset); goog.exportProperty( ol.layer.Vector.prototype, @@ -120156,6 +124192,11 @@ goog.exportProperty( 'setStyle', ol.layer.Heatmap.prototype.setStyle); +goog.exportProperty( + ol.layer.Heatmap.prototype, + 'setMap', + ol.layer.Heatmap.prototype.setMap); + goog.exportProperty( ol.layer.Heatmap.prototype, 'setSource', @@ -120251,11 +124292,6 @@ goog.exportProperty( 'setVisible', ol.layer.Heatmap.prototype.setVisible); -goog.exportProperty( - ol.layer.Heatmap.prototype, - 'bindTo', - ol.layer.Heatmap.prototype.bindTo); - goog.exportProperty( ol.layer.Heatmap.prototype, 'get', @@ -120283,13 +124319,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Heatmap.prototype, - 'unbind', - ol.layer.Heatmap.prototype.unbind); - -goog.exportProperty( - ol.layer.Heatmap.prototype, - 'unbindAll', - ol.layer.Heatmap.prototype.unbindAll); + 'unset', + ol.layer.Heatmap.prototype.unset); goog.exportProperty( ol.layer.Heatmap.prototype, @@ -120321,6 +124352,11 @@ goog.exportProperty( 'unByKey', ol.layer.Heatmap.prototype.unByKey); +goog.exportProperty( + ol.layer.Image.prototype, + 'setMap', + ol.layer.Image.prototype.setMap); + goog.exportProperty( ol.layer.Image.prototype, 'setSource', @@ -120416,11 +124452,6 @@ goog.exportProperty( 'setVisible', ol.layer.Image.prototype.setVisible); -goog.exportProperty( - ol.layer.Image.prototype, - 'bindTo', - ol.layer.Image.prototype.bindTo); - goog.exportProperty( ol.layer.Image.prototype, 'get', @@ -120448,13 +124479,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Image.prototype, - 'unbind', - ol.layer.Image.prototype.unbind); - -goog.exportProperty( - ol.layer.Image.prototype, - 'unbindAll', - ol.layer.Image.prototype.unbindAll); + 'unset', + ol.layer.Image.prototype.unset); goog.exportProperty( ol.layer.Image.prototype, @@ -120576,11 +124602,6 @@ goog.exportProperty( 'setVisible', ol.layer.Group.prototype.setVisible); -goog.exportProperty( - ol.layer.Group.prototype, - 'bindTo', - ol.layer.Group.prototype.bindTo); - goog.exportProperty( ol.layer.Group.prototype, 'get', @@ -120608,13 +124629,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Group.prototype, - 'unbind', - ol.layer.Group.prototype.unbind); - -goog.exportProperty( - ol.layer.Group.prototype, - 'unbindAll', - ol.layer.Group.prototype.unbindAll); + 'unset', + ol.layer.Group.prototype.unset); goog.exportProperty( ol.layer.Group.prototype, @@ -120646,6 +124662,11 @@ goog.exportProperty( 'unByKey', ol.layer.Group.prototype.unByKey); +goog.exportProperty( + ol.layer.Tile.prototype, + 'setMap', + ol.layer.Tile.prototype.setMap); + goog.exportProperty( ol.layer.Tile.prototype, 'setSource', @@ -120741,11 +124762,6 @@ goog.exportProperty( 'setVisible', ol.layer.Tile.prototype.setVisible); -goog.exportProperty( - ol.layer.Tile.prototype, - 'bindTo', - ol.layer.Tile.prototype.bindTo); - goog.exportProperty( ol.layer.Tile.prototype, 'get', @@ -120773,13 +124789,8 @@ goog.exportProperty( goog.exportProperty( ol.layer.Tile.prototype, - 'unbind', - ol.layer.Tile.prototype.unbind); - -goog.exportProperty( - ol.layer.Tile.prototype, - 'unbindAll', - ol.layer.Tile.prototype.unbindAll); + 'unset', + ol.layer.Tile.prototype.unset); goog.exportProperty( ol.layer.Tile.prototype, @@ -120811,11 +124822,6 @@ goog.exportProperty( 'unByKey', ol.layer.Tile.prototype.unByKey); -goog.exportProperty( - ol.interaction.Interaction.prototype, - 'bindTo', - ol.interaction.Interaction.prototype.bindTo); - goog.exportProperty( ol.interaction.Interaction.prototype, 'get', @@ -120843,13 +124849,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.Interaction.prototype, - 'unbind', - ol.interaction.Interaction.prototype.unbind); - -goog.exportProperty( - ol.interaction.Interaction.prototype, - 'unbindAll', - ol.interaction.Interaction.prototype.unbindAll); + 'unset', + ol.interaction.Interaction.prototype.unset); goog.exportProperty( ol.interaction.Interaction.prototype, @@ -120891,11 +124892,6 @@ goog.exportProperty( 'setActive', ol.interaction.DoubleClickZoom.prototype.setActive); -goog.exportProperty( - ol.interaction.DoubleClickZoom.prototype, - 'bindTo', - ol.interaction.DoubleClickZoom.prototype.bindTo); - goog.exportProperty( ol.interaction.DoubleClickZoom.prototype, 'get', @@ -120923,13 +124919,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DoubleClickZoom.prototype, - 'unbind', - ol.interaction.DoubleClickZoom.prototype.unbind); - -goog.exportProperty( - ol.interaction.DoubleClickZoom.prototype, - 'unbindAll', - ol.interaction.DoubleClickZoom.prototype.unbindAll); + 'unset', + ol.interaction.DoubleClickZoom.prototype.unset); goog.exportProperty( ol.interaction.DoubleClickZoom.prototype, @@ -120971,11 +124962,6 @@ goog.exportProperty( 'setActive', ol.interaction.DragAndDrop.prototype.setActive); -goog.exportProperty( - ol.interaction.DragAndDrop.prototype, - 'bindTo', - ol.interaction.DragAndDrop.prototype.bindTo); - goog.exportProperty( ol.interaction.DragAndDrop.prototype, 'get', @@ -121003,13 +124989,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DragAndDrop.prototype, - 'unbind', - ol.interaction.DragAndDrop.prototype.unbind); - -goog.exportProperty( - ol.interaction.DragAndDrop.prototype, - 'unbindAll', - ol.interaction.DragAndDrop.prototype.unbindAll); + 'unset', + ol.interaction.DragAndDrop.prototype.unset); goog.exportProperty( ol.interaction.DragAndDrop.prototype, @@ -121051,11 +125032,6 @@ goog.exportProperty( 'setActive', ol.interaction.Pointer.prototype.setActive); -goog.exportProperty( - ol.interaction.Pointer.prototype, - 'bindTo', - ol.interaction.Pointer.prototype.bindTo); - goog.exportProperty( ol.interaction.Pointer.prototype, 'get', @@ -121083,13 +125059,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.Pointer.prototype, - 'unbind', - ol.interaction.Pointer.prototype.unbind); - -goog.exportProperty( - ol.interaction.Pointer.prototype, - 'unbindAll', - ol.interaction.Pointer.prototype.unbindAll); + 'unset', + ol.interaction.Pointer.prototype.unset); goog.exportProperty( ol.interaction.Pointer.prototype, @@ -121131,11 +125102,6 @@ goog.exportProperty( 'setActive', ol.interaction.DragBox.prototype.setActive); -goog.exportProperty( - ol.interaction.DragBox.prototype, - 'bindTo', - ol.interaction.DragBox.prototype.bindTo); - goog.exportProperty( ol.interaction.DragBox.prototype, 'get', @@ -121163,13 +125129,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DragBox.prototype, - 'unbind', - ol.interaction.DragBox.prototype.unbind); - -goog.exportProperty( - ol.interaction.DragBox.prototype, - 'unbindAll', - ol.interaction.DragBox.prototype.unbindAll); + 'unset', + ol.interaction.DragBox.prototype.unset); goog.exportProperty( ol.interaction.DragBox.prototype, @@ -121211,11 +125172,6 @@ goog.exportProperty( 'setActive', ol.interaction.DragPan.prototype.setActive); -goog.exportProperty( - ol.interaction.DragPan.prototype, - 'bindTo', - ol.interaction.DragPan.prototype.bindTo); - goog.exportProperty( ol.interaction.DragPan.prototype, 'get', @@ -121243,13 +125199,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DragPan.prototype, - 'unbind', - ol.interaction.DragPan.prototype.unbind); - -goog.exportProperty( - ol.interaction.DragPan.prototype, - 'unbindAll', - ol.interaction.DragPan.prototype.unbindAll); + 'unset', + ol.interaction.DragPan.prototype.unset); goog.exportProperty( ol.interaction.DragPan.prototype, @@ -121291,11 +125242,6 @@ goog.exportProperty( 'setActive', ol.interaction.DragRotateAndZoom.prototype.setActive); -goog.exportProperty( - ol.interaction.DragRotateAndZoom.prototype, - 'bindTo', - ol.interaction.DragRotateAndZoom.prototype.bindTo); - goog.exportProperty( ol.interaction.DragRotateAndZoom.prototype, 'get', @@ -121323,13 +125269,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DragRotateAndZoom.prototype, - 'unbind', - ol.interaction.DragRotateAndZoom.prototype.unbind); - -goog.exportProperty( - ol.interaction.DragRotateAndZoom.prototype, - 'unbindAll', - ol.interaction.DragRotateAndZoom.prototype.unbindAll); + 'unset', + ol.interaction.DragRotateAndZoom.prototype.unset); goog.exportProperty( ol.interaction.DragRotateAndZoom.prototype, @@ -121371,11 +125312,6 @@ goog.exportProperty( 'setActive', ol.interaction.DragRotate.prototype.setActive); -goog.exportProperty( - ol.interaction.DragRotate.prototype, - 'bindTo', - ol.interaction.DragRotate.prototype.bindTo); - goog.exportProperty( ol.interaction.DragRotate.prototype, 'get', @@ -121403,13 +125339,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DragRotate.prototype, - 'unbind', - ol.interaction.DragRotate.prototype.unbind); - -goog.exportProperty( - ol.interaction.DragRotate.prototype, - 'unbindAll', - ol.interaction.DragRotate.prototype.unbindAll); + 'unset', + ol.interaction.DragRotate.prototype.unset); goog.exportProperty( ol.interaction.DragRotate.prototype, @@ -121456,11 +125387,6 @@ goog.exportProperty( 'setActive', ol.interaction.DragZoom.prototype.setActive); -goog.exportProperty( - ol.interaction.DragZoom.prototype, - 'bindTo', - ol.interaction.DragZoom.prototype.bindTo); - goog.exportProperty( ol.interaction.DragZoom.prototype, 'get', @@ -121488,13 +125414,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.DragZoom.prototype, - 'unbind', - ol.interaction.DragZoom.prototype.unbind); - -goog.exportProperty( - ol.interaction.DragZoom.prototype, - 'unbindAll', - ol.interaction.DragZoom.prototype.unbindAll); + 'unset', + ol.interaction.DragZoom.prototype.unset); goog.exportProperty( ol.interaction.DragZoom.prototype, @@ -121536,11 +125457,6 @@ goog.exportProperty( 'setActive', ol.interaction.Draw.prototype.setActive); -goog.exportProperty( - ol.interaction.Draw.prototype, - 'bindTo', - ol.interaction.Draw.prototype.bindTo); - goog.exportProperty( ol.interaction.Draw.prototype, 'get', @@ -121568,13 +125484,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.Draw.prototype, - 'unbind', - ol.interaction.Draw.prototype.unbind); - -goog.exportProperty( - ol.interaction.Draw.prototype, - 'unbindAll', - ol.interaction.Draw.prototype.unbindAll); + 'unset', + ol.interaction.Draw.prototype.unset); goog.exportProperty( ol.interaction.Draw.prototype, @@ -121616,11 +125527,6 @@ goog.exportProperty( 'setActive', ol.interaction.KeyboardPan.prototype.setActive); -goog.exportProperty( - ol.interaction.KeyboardPan.prototype, - 'bindTo', - ol.interaction.KeyboardPan.prototype.bindTo); - goog.exportProperty( ol.interaction.KeyboardPan.prototype, 'get', @@ -121648,13 +125554,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.KeyboardPan.prototype, - 'unbind', - ol.interaction.KeyboardPan.prototype.unbind); - -goog.exportProperty( - ol.interaction.KeyboardPan.prototype, - 'unbindAll', - ol.interaction.KeyboardPan.prototype.unbindAll); + 'unset', + ol.interaction.KeyboardPan.prototype.unset); goog.exportProperty( ol.interaction.KeyboardPan.prototype, @@ -121696,11 +125597,6 @@ goog.exportProperty( 'setActive', ol.interaction.KeyboardZoom.prototype.setActive); -goog.exportProperty( - ol.interaction.KeyboardZoom.prototype, - 'bindTo', - ol.interaction.KeyboardZoom.prototype.bindTo); - goog.exportProperty( ol.interaction.KeyboardZoom.prototype, 'get', @@ -121728,13 +125624,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.KeyboardZoom.prototype, - 'unbind', - ol.interaction.KeyboardZoom.prototype.unbind); - -goog.exportProperty( - ol.interaction.KeyboardZoom.prototype, - 'unbindAll', - ol.interaction.KeyboardZoom.prototype.unbindAll); + 'unset', + ol.interaction.KeyboardZoom.prototype.unset); goog.exportProperty( ol.interaction.KeyboardZoom.prototype, @@ -121776,11 +125667,6 @@ goog.exportProperty( 'setActive', ol.interaction.Modify.prototype.setActive); -goog.exportProperty( - ol.interaction.Modify.prototype, - 'bindTo', - ol.interaction.Modify.prototype.bindTo); - goog.exportProperty( ol.interaction.Modify.prototype, 'get', @@ -121808,13 +125694,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.Modify.prototype, - 'unbind', - ol.interaction.Modify.prototype.unbind); - -goog.exportProperty( - ol.interaction.Modify.prototype, - 'unbindAll', - ol.interaction.Modify.prototype.unbindAll); + 'unset', + ol.interaction.Modify.prototype.unset); goog.exportProperty( ol.interaction.Modify.prototype, @@ -121856,11 +125737,6 @@ goog.exportProperty( 'setActive', ol.interaction.MouseWheelZoom.prototype.setActive); -goog.exportProperty( - ol.interaction.MouseWheelZoom.prototype, - 'bindTo', - ol.interaction.MouseWheelZoom.prototype.bindTo); - goog.exportProperty( ol.interaction.MouseWheelZoom.prototype, 'get', @@ -121888,13 +125764,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.MouseWheelZoom.prototype, - 'unbind', - ol.interaction.MouseWheelZoom.prototype.unbind); - -goog.exportProperty( - ol.interaction.MouseWheelZoom.prototype, - 'unbindAll', - ol.interaction.MouseWheelZoom.prototype.unbindAll); + 'unset', + ol.interaction.MouseWheelZoom.prototype.unset); goog.exportProperty( ol.interaction.MouseWheelZoom.prototype, @@ -121936,11 +125807,6 @@ goog.exportProperty( 'setActive', ol.interaction.PinchRotate.prototype.setActive); -goog.exportProperty( - ol.interaction.PinchRotate.prototype, - 'bindTo', - ol.interaction.PinchRotate.prototype.bindTo); - goog.exportProperty( ol.interaction.PinchRotate.prototype, 'get', @@ -121968,13 +125834,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.PinchRotate.prototype, - 'unbind', - ol.interaction.PinchRotate.prototype.unbind); - -goog.exportProperty( - ol.interaction.PinchRotate.prototype, - 'unbindAll', - ol.interaction.PinchRotate.prototype.unbindAll); + 'unset', + ol.interaction.PinchRotate.prototype.unset); goog.exportProperty( ol.interaction.PinchRotate.prototype, @@ -122016,11 +125877,6 @@ goog.exportProperty( 'setActive', ol.interaction.PinchZoom.prototype.setActive); -goog.exportProperty( - ol.interaction.PinchZoom.prototype, - 'bindTo', - ol.interaction.PinchZoom.prototype.bindTo); - goog.exportProperty( ol.interaction.PinchZoom.prototype, 'get', @@ -122048,13 +125904,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.PinchZoom.prototype, - 'unbind', - ol.interaction.PinchZoom.prototype.unbind); - -goog.exportProperty( - ol.interaction.PinchZoom.prototype, - 'unbindAll', - ol.interaction.PinchZoom.prototype.unbindAll); + 'unset', + ol.interaction.PinchZoom.prototype.unset); goog.exportProperty( ol.interaction.PinchZoom.prototype, @@ -122096,11 +125947,6 @@ goog.exportProperty( 'setActive', ol.interaction.Select.prototype.setActive); -goog.exportProperty( - ol.interaction.Select.prototype, - 'bindTo', - ol.interaction.Select.prototype.bindTo); - goog.exportProperty( ol.interaction.Select.prototype, 'get', @@ -122128,13 +125974,8 @@ goog.exportProperty( goog.exportProperty( ol.interaction.Select.prototype, - 'unbind', - ol.interaction.Select.prototype.unbind); - -goog.exportProperty( - ol.interaction.Select.prototype, - 'unbindAll', - ol.interaction.Select.prototype.unbindAll); + 'unset', + ol.interaction.Select.prototype.unset); goog.exportProperty( ol.interaction.Select.prototype, @@ -122166,6 +126007,106 @@ goog.exportProperty( 'unByKey', ol.interaction.Select.prototype.unByKey); +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getActive', + ol.interaction.Snap.prototype.getActive); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'setActive', + ol.interaction.Snap.prototype.setActive); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'get', + ol.interaction.Snap.prototype.get); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getKeys', + ol.interaction.Snap.prototype.getKeys); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getProperties', + ol.interaction.Snap.prototype.getProperties); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'set', + ol.interaction.Snap.prototype.set); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'setProperties', + ol.interaction.Snap.prototype.setProperties); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'unset', + ol.interaction.Snap.prototype.unset); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'changed', + ol.interaction.Snap.prototype.changed); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'getRevision', + ol.interaction.Snap.prototype.getRevision); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'on', + ol.interaction.Snap.prototype.on); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'once', + ol.interaction.Snap.prototype.once); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'un', + ol.interaction.Snap.prototype.un); + +goog.exportProperty( + ol.interaction.Snap.prototype, + 'unByKey', + ol.interaction.Snap.prototype.unByKey); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'get', + ol.geom.Geometry.prototype.get); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getKeys', + ol.geom.Geometry.prototype.getKeys); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'getProperties', + ol.geom.Geometry.prototype.getProperties); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'set', + ol.geom.Geometry.prototype.set); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'setProperties', + ol.geom.Geometry.prototype.setProperties); + +goog.exportProperty( + ol.geom.Geometry.prototype, + 'unset', + ol.geom.Geometry.prototype.unset); + goog.exportProperty( ol.geom.Geometry.prototype, 'changed', @@ -122198,28 +126139,48 @@ goog.exportProperty( goog.exportProperty( ol.geom.SimpleGeometry.prototype, - 'clone', - ol.geom.SimpleGeometry.prototype.clone); + 'getClosestPoint', + ol.geom.SimpleGeometry.prototype.getClosestPoint); goog.exportProperty( ol.geom.SimpleGeometry.prototype, - 'getClosestPoint', - ol.geom.SimpleGeometry.prototype.getClosestPoint); + 'getExtent', + ol.geom.SimpleGeometry.prototype.getExtent); goog.exportProperty( ol.geom.SimpleGeometry.prototype, - 'getType', - ol.geom.SimpleGeometry.prototype.getType); + 'transform', + ol.geom.SimpleGeometry.prototype.transform); goog.exportProperty( ol.geom.SimpleGeometry.prototype, - 'intersectsExtent', - ol.geom.SimpleGeometry.prototype.intersectsExtent); + 'get', + ol.geom.SimpleGeometry.prototype.get); goog.exportProperty( ol.geom.SimpleGeometry.prototype, - 'transform', - ol.geom.SimpleGeometry.prototype.transform); + 'getKeys', + ol.geom.SimpleGeometry.prototype.getKeys); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'getProperties', + ol.geom.SimpleGeometry.prototype.getProperties); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'set', + ol.geom.SimpleGeometry.prototype.set); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'setProperties', + ol.geom.SimpleGeometry.prototype.setProperties); + +goog.exportProperty( + ol.geom.SimpleGeometry.prototype, + 'unset', + ol.geom.SimpleGeometry.prototype.unset); goog.exportProperty( ol.geom.SimpleGeometry.prototype, @@ -122283,8 +126244,38 @@ goog.exportProperty( goog.exportProperty( ol.geom.Circle.prototype, - 'intersectsExtent', - ol.geom.Circle.prototype.intersectsExtent); + 'getExtent', + ol.geom.Circle.prototype.getExtent); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'get', + ol.geom.Circle.prototype.get); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getKeys', + ol.geom.Circle.prototype.getKeys); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'getProperties', + ol.geom.Circle.prototype.getProperties); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'set', + ol.geom.Circle.prototype.set); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'setProperties', + ol.geom.Circle.prototype.setProperties); + +goog.exportProperty( + ol.geom.Circle.prototype, + 'unset', + ol.geom.Circle.prototype.unset); goog.exportProperty( ol.geom.Circle.prototype, @@ -122321,11 +126312,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.GeometryCollection.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getExtent', + ol.geom.GeometryCollection.prototype.getExtent); + goog.exportProperty( ol.geom.GeometryCollection.prototype, 'transform', ol.geom.GeometryCollection.prototype.transform); +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'get', + ol.geom.GeometryCollection.prototype.get); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getKeys', + ol.geom.GeometryCollection.prototype.getKeys); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'getProperties', + ol.geom.GeometryCollection.prototype.getProperties); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'set', + ol.geom.GeometryCollection.prototype.set); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'setProperties', + ol.geom.GeometryCollection.prototype.setProperties); + +goog.exportProperty( + ol.geom.GeometryCollection.prototype, + 'unset', + ol.geom.GeometryCollection.prototype.unset); + goog.exportProperty( ol.geom.GeometryCollection.prototype, 'changed', @@ -122356,11 +126382,6 @@ goog.exportProperty( 'unByKey', ol.geom.GeometryCollection.prototype.unByKey); -goog.exportProperty( - ol.geom.LinearRing.prototype, - 'getExtent', - ol.geom.LinearRing.prototype.getExtent); - goog.exportProperty( ol.geom.LinearRing.prototype, 'getFirstCoordinate', @@ -122393,14 +126414,44 @@ goog.exportProperty( goog.exportProperty( ol.geom.LinearRing.prototype, - 'intersectsExtent', - ol.geom.LinearRing.prototype.intersectsExtent); + 'getExtent', + ol.geom.LinearRing.prototype.getExtent); goog.exportProperty( ol.geom.LinearRing.prototype, 'transform', ol.geom.LinearRing.prototype.transform); +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'get', + ol.geom.LinearRing.prototype.get); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getKeys', + ol.geom.LinearRing.prototype.getKeys); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'getProperties', + ol.geom.LinearRing.prototype.getProperties); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'set', + ol.geom.LinearRing.prototype.set); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'setProperties', + ol.geom.LinearRing.prototype.setProperties); + +goog.exportProperty( + ol.geom.LinearRing.prototype, + 'unset', + ol.geom.LinearRing.prototype.unset); + goog.exportProperty( ol.geom.LinearRing.prototype, 'changed', @@ -122431,11 +126482,6 @@ goog.exportProperty( 'unByKey', ol.geom.LinearRing.prototype.unByKey); -goog.exportProperty( - ol.geom.LineString.prototype, - 'getExtent', - ol.geom.LineString.prototype.getExtent); - goog.exportProperty( ol.geom.LineString.prototype, 'getFirstCoordinate', @@ -122466,11 +126512,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.LineString.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.LineString.prototype, + 'getExtent', + ol.geom.LineString.prototype.getExtent); + goog.exportProperty( ol.geom.LineString.prototype, 'transform', ol.geom.LineString.prototype.transform); +goog.exportProperty( + ol.geom.LineString.prototype, + 'get', + ol.geom.LineString.prototype.get); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getKeys', + ol.geom.LineString.prototype.getKeys); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'getProperties', + ol.geom.LineString.prototype.getProperties); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'set', + ol.geom.LineString.prototype.set); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'setProperties', + ol.geom.LineString.prototype.setProperties); + +goog.exportProperty( + ol.geom.LineString.prototype, + 'unset', + ol.geom.LineString.prototype.unset); + goog.exportProperty( ol.geom.LineString.prototype, 'changed', @@ -122501,11 +126582,6 @@ goog.exportProperty( 'unByKey', ol.geom.LineString.prototype.unByKey); -goog.exportProperty( - ol.geom.MultiLineString.prototype, - 'getExtent', - ol.geom.MultiLineString.prototype.getExtent); - goog.exportProperty( ol.geom.MultiLineString.prototype, 'getFirstCoordinate', @@ -122536,11 +126612,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.MultiLineString.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getExtent', + ol.geom.MultiLineString.prototype.getExtent); + goog.exportProperty( ol.geom.MultiLineString.prototype, 'transform', ol.geom.MultiLineString.prototype.transform); +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'get', + ol.geom.MultiLineString.prototype.get); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getKeys', + ol.geom.MultiLineString.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'getProperties', + ol.geom.MultiLineString.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'set', + ol.geom.MultiLineString.prototype.set); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'setProperties', + ol.geom.MultiLineString.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiLineString.prototype, + 'unset', + ol.geom.MultiLineString.prototype.unset); + goog.exportProperty( ol.geom.MultiLineString.prototype, 'changed', @@ -122571,11 +126682,6 @@ goog.exportProperty( 'unByKey', ol.geom.MultiLineString.prototype.unByKey); -goog.exportProperty( - ol.geom.MultiPoint.prototype, - 'getExtent', - ol.geom.MultiPoint.prototype.getExtent); - goog.exportProperty( ol.geom.MultiPoint.prototype, 'getFirstCoordinate', @@ -122606,11 +126712,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.MultiPoint.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getExtent', + ol.geom.MultiPoint.prototype.getExtent); + goog.exportProperty( ol.geom.MultiPoint.prototype, 'transform', ol.geom.MultiPoint.prototype.transform); +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'get', + ol.geom.MultiPoint.prototype.get); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getKeys', + ol.geom.MultiPoint.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'getProperties', + ol.geom.MultiPoint.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'set', + ol.geom.MultiPoint.prototype.set); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'setProperties', + ol.geom.MultiPoint.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiPoint.prototype, + 'unset', + ol.geom.MultiPoint.prototype.unset); + goog.exportProperty( ol.geom.MultiPoint.prototype, 'changed', @@ -122641,11 +126782,6 @@ goog.exportProperty( 'unByKey', ol.geom.MultiPoint.prototype.unByKey); -goog.exportProperty( - ol.geom.MultiPolygon.prototype, - 'getExtent', - ol.geom.MultiPolygon.prototype.getExtent); - goog.exportProperty( ol.geom.MultiPolygon.prototype, 'getFirstCoordinate', @@ -122676,11 +126812,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.MultiPolygon.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getExtent', + ol.geom.MultiPolygon.prototype.getExtent); + goog.exportProperty( ol.geom.MultiPolygon.prototype, 'transform', ol.geom.MultiPolygon.prototype.transform); +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'get', + ol.geom.MultiPolygon.prototype.get); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getKeys', + ol.geom.MultiPolygon.prototype.getKeys); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'getProperties', + ol.geom.MultiPolygon.prototype.getProperties); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'set', + ol.geom.MultiPolygon.prototype.set); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'setProperties', + ol.geom.MultiPolygon.prototype.setProperties); + +goog.exportProperty( + ol.geom.MultiPolygon.prototype, + 'unset', + ol.geom.MultiPolygon.prototype.unset); + goog.exportProperty( ol.geom.MultiPolygon.prototype, 'changed', @@ -122741,11 +126912,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.Point.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.Point.prototype, + 'getExtent', + ol.geom.Point.prototype.getExtent); + goog.exportProperty( ol.geom.Point.prototype, 'transform', ol.geom.Point.prototype.transform); +goog.exportProperty( + ol.geom.Point.prototype, + 'get', + ol.geom.Point.prototype.get); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getKeys', + ol.geom.Point.prototype.getKeys); + +goog.exportProperty( + ol.geom.Point.prototype, + 'getProperties', + ol.geom.Point.prototype.getProperties); + +goog.exportProperty( + ol.geom.Point.prototype, + 'set', + ol.geom.Point.prototype.set); + +goog.exportProperty( + ol.geom.Point.prototype, + 'setProperties', + ol.geom.Point.prototype.setProperties); + +goog.exportProperty( + ol.geom.Point.prototype, + 'unset', + ol.geom.Point.prototype.unset); + goog.exportProperty( ol.geom.Point.prototype, 'changed', @@ -122776,11 +126982,6 @@ goog.exportProperty( 'unByKey', ol.geom.Point.prototype.unByKey); -goog.exportProperty( - ol.geom.Polygon.prototype, - 'getExtent', - ol.geom.Polygon.prototype.getExtent); - goog.exportProperty( ol.geom.Polygon.prototype, 'getFirstCoordinate', @@ -122811,11 +127012,46 @@ goog.exportProperty( 'getClosestPoint', ol.geom.Polygon.prototype.getClosestPoint); +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getExtent', + ol.geom.Polygon.prototype.getExtent); + goog.exportProperty( ol.geom.Polygon.prototype, 'transform', ol.geom.Polygon.prototype.transform); +goog.exportProperty( + ol.geom.Polygon.prototype, + 'get', + ol.geom.Polygon.prototype.get); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getKeys', + ol.geom.Polygon.prototype.getKeys); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'getProperties', + ol.geom.Polygon.prototype.getProperties); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'set', + ol.geom.Polygon.prototype.set); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'setProperties', + ol.geom.Polygon.prototype.setProperties); + +goog.exportProperty( + ol.geom.Polygon.prototype, + 'unset', + ol.geom.Polygon.prototype.unset); + goog.exportProperty( ol.geom.Polygon.prototype, 'changed', @@ -122861,81 +127097,6 @@ goog.exportProperty( 'readFeatures', ol.format.GML.prototype.readFeatures); -goog.exportProperty( - ol.dom.Input.prototype, - 'bindTo', - ol.dom.Input.prototype.bindTo); - -goog.exportProperty( - ol.dom.Input.prototype, - 'get', - ol.dom.Input.prototype.get); - -goog.exportProperty( - ol.dom.Input.prototype, - 'getKeys', - ol.dom.Input.prototype.getKeys); - -goog.exportProperty( - ol.dom.Input.prototype, - 'getProperties', - ol.dom.Input.prototype.getProperties); - -goog.exportProperty( - ol.dom.Input.prototype, - 'set', - ol.dom.Input.prototype.set); - -goog.exportProperty( - ol.dom.Input.prototype, - 'setProperties', - ol.dom.Input.prototype.setProperties); - -goog.exportProperty( - ol.dom.Input.prototype, - 'unbind', - ol.dom.Input.prototype.unbind); - -goog.exportProperty( - ol.dom.Input.prototype, - 'unbindAll', - ol.dom.Input.prototype.unbindAll); - -goog.exportProperty( - ol.dom.Input.prototype, - 'changed', - ol.dom.Input.prototype.changed); - -goog.exportProperty( - ol.dom.Input.prototype, - 'getRevision', - ol.dom.Input.prototype.getRevision); - -goog.exportProperty( - ol.dom.Input.prototype, - 'on', - ol.dom.Input.prototype.on); - -goog.exportProperty( - ol.dom.Input.prototype, - 'once', - ol.dom.Input.prototype.once); - -goog.exportProperty( - ol.dom.Input.prototype, - 'un', - ol.dom.Input.prototype.un); - -goog.exportProperty( - ol.dom.Input.prototype, - 'unByKey', - ol.dom.Input.prototype.unByKey); - -goog.exportProperty( - ol.control.Control.prototype, - 'bindTo', - ol.control.Control.prototype.bindTo); - goog.exportProperty( ol.control.Control.prototype, 'get', @@ -122963,13 +127124,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Control.prototype, - 'unbind', - ol.control.Control.prototype.unbind); - -goog.exportProperty( - ol.control.Control.prototype, - 'unbindAll', - ol.control.Control.prototype.unbindAll); + 'unset', + ol.control.Control.prototype.unset); goog.exportProperty( ol.control.Control.prototype, @@ -123013,8 +127169,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Attribution.prototype, - 'bindTo', - ol.control.Attribution.prototype.bindTo); + 'setTarget', + ol.control.Attribution.prototype.setTarget); goog.exportProperty( ol.control.Attribution.prototype, @@ -123043,13 +127199,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Attribution.prototype, - 'unbind', - ol.control.Attribution.prototype.unbind); - -goog.exportProperty( - ol.control.Attribution.prototype, - 'unbindAll', - ol.control.Attribution.prototype.unbindAll); + 'unset', + ol.control.Attribution.prototype.unset); goog.exportProperty( ol.control.Attribution.prototype, @@ -123093,8 +127244,8 @@ goog.exportProperty( goog.exportProperty( ol.control.FullScreen.prototype, - 'bindTo', - ol.control.FullScreen.prototype.bindTo); + 'setTarget', + ol.control.FullScreen.prototype.setTarget); goog.exportProperty( ol.control.FullScreen.prototype, @@ -123123,13 +127274,8 @@ goog.exportProperty( goog.exportProperty( ol.control.FullScreen.prototype, - 'unbind', - ol.control.FullScreen.prototype.unbind); - -goog.exportProperty( - ol.control.FullScreen.prototype, - 'unbindAll', - ol.control.FullScreen.prototype.unbindAll); + 'unset', + ol.control.FullScreen.prototype.unset); goog.exportProperty( ol.control.FullScreen.prototype, @@ -123168,8 +127314,8 @@ goog.exportProperty( goog.exportProperty( ol.control.MousePosition.prototype, - 'bindTo', - ol.control.MousePosition.prototype.bindTo); + 'setTarget', + ol.control.MousePosition.prototype.setTarget); goog.exportProperty( ol.control.MousePosition.prototype, @@ -123198,13 +127344,8 @@ goog.exportProperty( goog.exportProperty( ol.control.MousePosition.prototype, - 'unbind', - ol.control.MousePosition.prototype.unbind); - -goog.exportProperty( - ol.control.MousePosition.prototype, - 'unbindAll', - ol.control.MousePosition.prototype.unbindAll); + 'unset', + ol.control.MousePosition.prototype.unset); goog.exportProperty( ol.control.MousePosition.prototype, @@ -123243,8 +127384,8 @@ goog.exportProperty( goog.exportProperty( ol.control.OverviewMap.prototype, - 'bindTo', - ol.control.OverviewMap.prototype.bindTo); + 'setTarget', + ol.control.OverviewMap.prototype.setTarget); goog.exportProperty( ol.control.OverviewMap.prototype, @@ -123273,13 +127414,8 @@ goog.exportProperty( goog.exportProperty( ol.control.OverviewMap.prototype, - 'unbind', - ol.control.OverviewMap.prototype.unbind); - -goog.exportProperty( - ol.control.OverviewMap.prototype, - 'unbindAll', - ol.control.OverviewMap.prototype.unbindAll); + 'unset', + ol.control.OverviewMap.prototype.unset); goog.exportProperty( ol.control.OverviewMap.prototype, @@ -123323,8 +127459,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Rotate.prototype, - 'bindTo', - ol.control.Rotate.prototype.bindTo); + 'setTarget', + ol.control.Rotate.prototype.setTarget); goog.exportProperty( ol.control.Rotate.prototype, @@ -123353,13 +127489,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Rotate.prototype, - 'unbind', - ol.control.Rotate.prototype.unbind); - -goog.exportProperty( - ol.control.Rotate.prototype, - 'unbindAll', - ol.control.Rotate.prototype.unbindAll); + 'unset', + ol.control.Rotate.prototype.unset); goog.exportProperty( ol.control.Rotate.prototype, @@ -123403,8 +127534,8 @@ goog.exportProperty( goog.exportProperty( ol.control.ScaleLine.prototype, - 'bindTo', - ol.control.ScaleLine.prototype.bindTo); + 'setTarget', + ol.control.ScaleLine.prototype.setTarget); goog.exportProperty( ol.control.ScaleLine.prototype, @@ -123433,13 +127564,8 @@ goog.exportProperty( goog.exportProperty( ol.control.ScaleLine.prototype, - 'unbind', - ol.control.ScaleLine.prototype.unbind); - -goog.exportProperty( - ol.control.ScaleLine.prototype, - 'unbindAll', - ol.control.ScaleLine.prototype.unbindAll); + 'unset', + ol.control.ScaleLine.prototype.unset); goog.exportProperty( ol.control.ScaleLine.prototype, @@ -123483,8 +127609,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Zoom.prototype, - 'bindTo', - ol.control.Zoom.prototype.bindTo); + 'setTarget', + ol.control.Zoom.prototype.setTarget); goog.exportProperty( ol.control.Zoom.prototype, @@ -123513,13 +127639,8 @@ goog.exportProperty( goog.exportProperty( ol.control.Zoom.prototype, - 'unbind', - ol.control.Zoom.prototype.unbind); - -goog.exportProperty( - ol.control.Zoom.prototype, - 'unbindAll', - ol.control.Zoom.prototype.unbindAll); + 'unset', + ol.control.Zoom.prototype.unset); goog.exportProperty( ol.control.Zoom.prototype, @@ -123558,8 +127679,8 @@ goog.exportProperty( goog.exportProperty( ol.control.ZoomSlider.prototype, - 'bindTo', - ol.control.ZoomSlider.prototype.bindTo); + 'setTarget', + ol.control.ZoomSlider.prototype.setTarget); goog.exportProperty( ol.control.ZoomSlider.prototype, @@ -123588,13 +127709,8 @@ goog.exportProperty( goog.exportProperty( ol.control.ZoomSlider.prototype, - 'unbind', - ol.control.ZoomSlider.prototype.unbind); - -goog.exportProperty( - ol.control.ZoomSlider.prototype, - 'unbindAll', - ol.control.ZoomSlider.prototype.unbindAll); + 'unset', + ol.control.ZoomSlider.prototype.unset); goog.exportProperty( ol.control.ZoomSlider.prototype, @@ -123638,8 +127754,8 @@ goog.exportProperty( goog.exportProperty( ol.control.ZoomToExtent.prototype, - 'bindTo', - ol.control.ZoomToExtent.prototype.bindTo); + 'setTarget', + ol.control.ZoomToExtent.prototype.setTarget); goog.exportProperty( ol.control.ZoomToExtent.prototype, @@ -123668,13 +127784,8 @@ goog.exportProperty( goog.exportProperty( ol.control.ZoomToExtent.prototype, - 'unbind', - ol.control.ZoomToExtent.prototype.unbind); - -goog.exportProperty( - ol.control.ZoomToExtent.prototype, - 'unbindAll', - ol.control.ZoomToExtent.prototype.unbindAll); + 'unset', + ol.control.ZoomToExtent.prototype.unset); goog.exportProperty( ol.control.ZoomToExtent.prototype, diff --git a/VIPSWeb/static/js/3rdparty/ol.js b/VIPSWeb/static/js/3rdparty/ol.js index 298612c249275da74639b84e2f2caccbb66135eb..a94b31e3c22b41d990ce7f66690448349b262333 100644 --- a/VIPSWeb/static/js/3rdparty/ol.js +++ b/VIPSWeb/static/js/3rdparty/ol.js @@ -1,212 +1,206 @@ // OpenLayers 3. See http://openlayers.org/ // License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md -// Version: v3.1.1 +// Version: v3.8.2 (function (root, factory) { - if (typeof define === "function" && define.amd) { - define([], factory); - } else if (typeof exports === "object") { + if (typeof exports === "object") { module.exports = factory(); + } else if (typeof define === "function" && define.amd) { + define([], factory); } else { root.ol = factory(); } }(this, function () { var OPENLAYERS = {}; - var l,aa=aa||{},ba=this;function m(b){return void 0!==b}function t(b,c,d){b=b.split(".");d=d||ba;b[0]in d||!d.execScript||d.execScript("var "+b[0]);for(var e;b.length&&(e=b.shift());)!b.length&&m(c)?d[e]=c:d[e]?d=d[e]:d=d[e]={}}function ca(){}function da(b){b.Ja=function(){return b.lf?b.lf:b.lf=new b}} + var l,aa=aa||{},ba=this;function m(b){return void 0!==b}function u(b,c,d){b=b.split(".");d=d||ba;b[0]in d||!d.execScript||d.execScript("var "+b[0]);for(var e;b.length&&(e=b.shift());)!b.length&&m(c)?d[e]=c:d[e]?d=d[e]:d=d[e]={}}function ca(){}function da(b){b.Pa=function(){return b.Dg?b.Dg:b.Dg=new b}} function ea(b){var c=typeof b;if("object"==c)if(b){if(b instanceof Array)return"array";if(b instanceof Object)return c;var d=Object.prototype.toString.call(b);if("[object Window]"==d)return"object";if("[object Array]"==d||"number"==typeof b.length&&"undefined"!=typeof b.splice&&"undefined"!=typeof b.propertyIsEnumerable&&!b.propertyIsEnumerable("splice"))return"array";if("[object Function]"==d||"undefined"!=typeof b.call&&"undefined"!=typeof b.propertyIsEnumerable&&!b.propertyIsEnumerable("call"))return"function"}else return"null"; else if("function"==c&&"undefined"==typeof b.call)return"object";return c}function fa(b){return null===b}function ga(b){return"array"==ea(b)}function ha(b){var c=ea(b);return"array"==c||"object"==c&&"number"==typeof b.length}function ia(b){return"string"==typeof b}function ja(b){return"number"==typeof b}function ka(b){return"function"==ea(b)}function la(b){var c=typeof b;return"object"==c&&null!=b||"function"==c}function ma(b){return b[na]||(b[na]=++oa)} -var na="closure_uid_"+(1E9*Math.random()>>>0),oa=0;function pa(b,c,d){return b.call.apply(b.bind,arguments)}function ra(b,c,d){if(!b)throw Error();if(2<arguments.length){var e=Array.prototype.slice.call(arguments,2);return function(){var d=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(d,e);return b.apply(c,d)}}return function(){return b.apply(c,arguments)}} -function sa(b,c,d){sa=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?pa:ra;return sa.apply(null,arguments)}function ta(b,c){var d=Array.prototype.slice.call(arguments,1);return function(){var c=d.slice();c.push.apply(c,arguments);return b.apply(this,c)}}var ua=Date.now||function(){return+new Date}; -function u(b,c){function d(){}d.prototype=c.prototype;b.S=c.prototype;b.prototype=new d;b.prototype.constructor=b;b.Vl=function(b,d,g){for(var h=Array(arguments.length-2),k=2;k<arguments.length;k++)h[k-2]=arguments[k];return c.prototype[d].apply(b,h)}};var va,wa;function xa(b){if(Error.captureStackTrace)Error.captureStackTrace(this,xa);else{var c=Error().stack;c&&(this.stack=c)}b&&(this.message=String(b))}u(xa,Error);xa.prototype.name="CustomError";var ya;function za(b,c){for(var d=b.split("%s"),e="",f=Array.prototype.slice.call(arguments,1);f.length&&1<d.length;)e+=d.shift()+f.shift();return e+d.join("%s")}var Aa=String.prototype.trim?function(b){return b.trim()}:function(b){return b.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}; -function Ba(b){if(!Ca.test(b))return b;-1!=b.indexOf("&")&&(b=b.replace(Da,"&"));-1!=b.indexOf("<")&&(b=b.replace(Ea,"<"));-1!=b.indexOf(">")&&(b=b.replace(Fa,">"));-1!=b.indexOf('"')&&(b=b.replace(Ga,"""));-1!=b.indexOf("'")&&(b=b.replace(Ha,"'"));-1!=b.indexOf("\x00")&&(b=b.replace(Ia,"�"));return b}var Da=/&/g,Ea=/</g,Fa=/>/g,Ga=/"/g,Ha=/'/g,Ia=/\x00/g,Ca=/[\x00&<>"']/; -function Ja(b){return String(b).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")}function Ma(b){b=m(void 0)?b.toFixed(void 0):String(b);var c=b.indexOf(".");-1==c&&(c=b.length);c=Math.max(0,2-c);return Array(c+1).join("0")+b} -function Na(b,c){for(var d=0,e=Aa(String(b)).split("."),f=Aa(String(c)).split("."),g=Math.max(e.length,f.length),h=0;0==d&&h<g;h++){var k=e[h]||"",n=f[h]||"",p=RegExp("(\\d*)(\\D*)","g"),q=RegExp("(\\d*)(\\D*)","g");do{var r=p.exec(k)||["","",""],s=q.exec(n)||["","",""];if(0==r[0].length&&0==s[0].length)break;d=Oa(0==r[1].length?0:parseInt(r[1],10),0==s[1].length?0:parseInt(s[1],10))||Oa(0==r[2].length,0==s[2].length)||Oa(r[2],s[2])}while(0==d)}return d}function Oa(b,c){return b<c?-1:b>c?1:0} -function Qa(){return"transform".replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})}function Ra(b){var c=ia(void 0)?Ja(void 0):"\\s";return b.replace(new RegExp("(^"+(c?"|["+c+"]+":"")+")([a-z])","g"),function(b,c,f){return c+f.toUpperCase()})};var Sa=Array.prototype;function Ta(b,c,d){Sa.forEach.call(b,c,d)}function Ua(b,c){return Sa.filter.call(b,c,void 0)}function Va(b,c,d){return Sa.map.call(b,c,d)}function Wa(b,c){return Sa.some.call(b,c,void 0)}function Xa(b){var c;a:{c=Ya;for(var d=b.length,e=ia(b)?b.split(""):b,f=0;f<d;f++)if(f in e&&c.call(void 0,e[f],f,b)){c=f;break a}c=-1}return 0>c?null:ia(b)?b.charAt(c):b[c]}function Za(b,c){return 0<=Sa.indexOf.call(b,c,void 0)} -function $a(b){if(!ga(b))for(var c=b.length-1;0<=c;c--)delete b[c];b.length=0}function ab(b,c){var d=Sa.indexOf.call(b,c,void 0),e;(e=0<=d)&&Sa.splice.call(b,d,1);return e}function bb(b){return Sa.concat.apply(Sa,arguments)}function cb(b){var c=b.length;if(0<c){for(var d=Array(c),e=0;e<c;e++)d[e]=b[e];return d}return[]}function db(b,c){for(var d=1;d<arguments.length;d++){var e=arguments[d];if(ha(e)){var f=b.length||0,g=e.length||0;b.length=f+g;for(var h=0;h<g;h++)b[f+h]=e[h]}else b.push(e)}} -function eb(b,c,d,e){Sa.splice.apply(b,fb(arguments,1))}function fb(b,c,d){return 2>=arguments.length?Sa.slice.call(b,c):Sa.slice.call(b,c,d)}function gb(b,c){b.sort(c||hb)}function ib(b,c){if(!ha(b)||!ha(c)||b.length!=c.length)return!1;for(var d=b.length,e=jb,f=0;f<d;f++)if(!e(b[f],c[f]))return!1;return!0}function hb(b,c){return b>c?1:b<c?-1:0}function jb(b,c){return b===c};var kb;a:{var lb=ba.navigator;if(lb){var mb=lb.userAgent;if(mb){kb=mb;break a}}kb=""}function nb(b){return-1!=kb.indexOf(b)};function ob(b,c,d){for(var e in b)c.call(d,b[e],e,b)}function pb(b,c){for(var d in b)if(c.call(void 0,b[d],d,b))return!0;return!1}function qb(b){var c=0,d;for(d in b)c++;return c}function rb(b){var c=[],d=0,e;for(e in b)c[d++]=b[e];return c}function sb(b){var c=[],d=0,e;for(e in b)c[d++]=e;return c}function tb(b,c){return c in b}function ub(b){var c=wb,d;for(d in c)if(b.call(void 0,c[d],d,c))return d}function xb(b){for(var c in b)return!1;return!0}function yb(b){for(var c in b)delete b[c]} -function zb(b,c){c in b&&delete b[c]}function Ab(b,c,d){if(c in b)throw Error('The object already contains the key "'+c+'"');b[c]=d}function x(b,c,d){return c in b?b[c]:d}function Bb(b,c){var d=[];return c in b?b[c]:b[c]=d}function Cb(b){var c={},d;for(d in b)c[d]=b[d];return c}var Db="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "); -function Eb(b,c){for(var d,e,f=1;f<arguments.length;f++){e=arguments[f];for(d in e)b[d]=e[d];for(var g=0;g<Db.length;g++)d=Db[g],Object.prototype.hasOwnProperty.call(e,d)&&(b[d]=e[d])}}function Fb(b){var c=arguments.length;if(1==c&&ga(arguments[0]))return Fb.apply(null,arguments[0]);for(var d={},e=0;e<c;e++)d[arguments[e]]=!0;return d};var Gb=nb("Opera")||nb("OPR"),Hb=nb("Trident")||nb("MSIE"),Ib=nb("Gecko")&&-1==kb.toLowerCase().indexOf("webkit")&&!(nb("Trident")||nb("MSIE")),Jb=-1!=kb.toLowerCase().indexOf("webkit"),Kb=nb("Macintosh"),Lb=nb("Windows"),Mb=nb("Linux")||nb("CrOS"),Nb,Ob=ba.navigator||null;Nb=!!Ob&&-1!=(Ob.appVersion||"").indexOf("X11");function Pb(){var b=ba.document;return b?b.documentMode:void 0} -var Qb=function(){var b="",c;if(Gb&&ba.opera)return b=ba.opera.version,ka(b)?b():b;Ib?c=/rv\:([^\);]+)(\)|;)/:Hb?c=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:Jb&&(c=/WebKit\/(\S+)/);c&&(b=(b=c.exec(kb))?b[1]:"");return Hb&&(c=Pb(),c>parseFloat(b))?String(c):b}(),Rb={};function Tb(b){return Rb[b]||(Rb[b]=0<=Na(Qb,b))}var Ub=ba.document,Vb=Ub&&Hb?Pb()||("CSS1Compat"==Ub.compatMode?parseInt(Qb,10):5):void 0;var Wb="https:"===ba.location.protocol,Xb=Hb&&!Tb("9.0")&&""!==Qb;function Yb(b,c,d){return Math.min(Math.max(b,c),d)}function Zb(b,c){var d=b%c;return 0>d*c?d+c:d}function $b(b,c,d){return b+d*(c-b)}function bc(b){return b*Math.PI/180};function cc(b){return function(c){if(m(c))return[Yb(c[0],b[0],b[2]),Yb(c[1],b[1],b[3])]}}function dc(b){return b};function ec(b,c,d){var e=b.length;if(b[0]<=c)return 0;if(!(c<=b[e-1]))if(0<d)for(d=1;d<e;++d){if(b[d]<c)return d-1}else if(0>d)for(d=1;d<e;++d){if(b[d]<=c)return d}else for(d=1;d<e;++d){if(b[d]==c)return d;if(b[d]<c)return b[d-1]-c<c-b[d]?d-1:d}return e-1};function fc(b){return function(c,d,e){if(m(c))return c=ec(b,c,e),c=Yb(c+d,0,b.length-1),b[c]}}function gc(b,c,d){return function(e,f,g){if(m(e))return g=0<g?0:0>g?1:.5,e=Math.floor(Math.log(c/e)/Math.log(b)+g),f=Math.max(e+f,0),m(d)&&(f=Math.min(f,d)),c/Math.pow(b,f)}};function hc(b){if(m(b))return 0}function ic(b,c){if(m(b))return b+c}function jc(b){var c=2*Math.PI/b;return function(b,e){if(m(b))return b=Math.floor((b+e)/c+.5)*c}}function kc(){var b=bc(5);return function(c,d){if(m(c))return Math.abs(c+d)<=b?0:c+d}};function lc(b,c,d){this.center=b;this.resolution=c;this.rotation=d};var mc=!Hb||Hb&&9<=Vb,nc=!Hb||Hb&&9<=Vb,oc=Hb&&!Tb("9");!Jb||Tb("528");Ib&&Tb("1.9b")||Hb&&Tb("8")||Gb&&Tb("9.5")||Jb&&Tb("528");Ib&&!Tb("8")||Hb&&Tb("9");function pc(){0!=qc&&(rc[ma(this)]=this);this.Sa=this.Sa;this.la=this.la}var qc=0,rc={};pc.prototype.Sa=!1;pc.prototype.hc=function(){if(!this.Sa&&(this.Sa=!0,this.M(),0!=qc)){var b=ma(this);delete rc[b]}};function sc(b,c){var d=ta(tc,c);b.la||(b.la=[]);b.la.push(m(void 0)?sa(d,void 0):d)}pc.prototype.M=function(){if(this.la)for(;this.la.length;)this.la.shift()()};function tc(b){b&&"function"==typeof b.hc&&b.hc()};function uc(b,c){this.type=b;this.b=this.target=c;this.f=!1;this.Zf=!0}uc.prototype.hc=function(){};uc.prototype.lb=function(){this.f=!0};uc.prototype.preventDefault=function(){this.Zf=!1};function vc(b){b.lb()}function wc(b){b.preventDefault()};var xc=Hb?"focusout":"DOMFocusOut";function yc(b){yc[" "](b);return b}yc[" "]=ca;function zc(b,c){uc.call(this,b?b.type:"");this.relatedTarget=this.b=this.target=null;this.i=this.e=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.n=this.d=this.c=this.o=!1;this.state=null;this.g=!1;this.a=null;b&&Ac(this,b,c)}u(zc,uc);var Bc=[1,4,2]; -function Ac(b,c,d){b.a=c;var e=b.type=c.type;b.target=c.target||c.srcElement;b.b=d;if(d=c.relatedTarget){if(Ib){var f;a:{try{yc(d.nodeName);f=!0;break a}catch(g){}f=!1}f||(d=null)}}else"mouseover"==e?d=c.fromElement:"mouseout"==e&&(d=c.toElement);b.relatedTarget=d;Object.defineProperties?Object.defineProperties(b,{offsetX:{configurable:!0,enumerable:!0,get:b.bf,set:b.hl},offsetY:{configurable:!0,enumerable:!0,get:b.cf,set:b.il}}):(b.offsetX=b.bf(),b.offsetY=b.cf());b.clientX=void 0!==c.clientX?c.clientX: -c.pageX;b.clientY=void 0!==c.clientY?c.clientY:c.pageY;b.screenX=c.screenX||0;b.screenY=c.screenY||0;b.button=c.button;b.e=c.keyCode||0;b.i=c.charCode||("keypress"==e?c.keyCode:0);b.o=c.ctrlKey;b.c=c.altKey;b.d=c.shiftKey;b.n=c.metaKey;b.g=Kb?c.metaKey:c.ctrlKey;b.state=c.state;c.defaultPrevented&&b.preventDefault()}function Cc(b){return(mc?0==b.a.button:"click"==b.type?!0:!!(b.a.button&Bc[0]))&&!(Jb&&Kb&&b.o)}l=zc.prototype; -l.lb=function(){zc.S.lb.call(this);this.a.stopPropagation?this.a.stopPropagation():this.a.cancelBubble=!0};l.preventDefault=function(){zc.S.preventDefault.call(this);var b=this.a;if(b.preventDefault)b.preventDefault();else if(b.returnValue=!1,oc)try{if(b.ctrlKey||112<=b.keyCode&&123>=b.keyCode)b.keyCode=-1}catch(c){}};l.fh=function(){return this.a};l.bf=function(){return Jb||void 0!==this.a.offsetX?this.a.offsetX:this.a.layerX}; -l.hl=function(b){Object.defineProperties(this,{offsetX:{writable:!0,enumerable:!0,configurable:!0,value:b}})};l.cf=function(){return Jb||void 0!==this.a.offsetY?this.a.offsetY:this.a.layerY};l.il=function(b){Object.defineProperties(this,{offsetY:{writable:!0,enumerable:!0,configurable:!0,value:b}})};var Dc="closure_listenable_"+(1E6*Math.random()|0);function Ec(b){return!(!b||!b[Dc])}var Fc=0;function Gc(b,c,d,e,f){this.Xb=b;this.a=null;this.src=c;this.type=d;this.vc=!!e;this.qd=f;this.key=++Fc;this.uc=this.Xc=!1}function Hc(b){b.uc=!0;b.Xb=null;b.a=null;b.src=null;b.qd=null};function Ic(b){this.src=b;this.a={};this.c=0}Ic.prototype.add=function(b,c,d,e,f){var g=b.toString();b=this.a[g];b||(b=this.a[g]=[],this.c++);var h=Jc(b,c,e,f);-1<h?(c=b[h],d||(c.Xc=!1)):(c=new Gc(c,this.src,g,!!e,f),c.Xc=d,b.push(c));return c};Ic.prototype.remove=function(b,c,d,e){b=b.toString();if(!(b in this.a))return!1;var f=this.a[b];c=Jc(f,c,d,e);return-1<c?(Hc(f[c]),Sa.splice.call(f,c,1),0==f.length&&(delete this.a[b],this.c--),!0):!1}; -function Kc(b,c){var d=c.type;if(!(d in b.a))return!1;var e=ab(b.a[d],c);e&&(Hc(c),0==b.a[d].length&&(delete b.a[d],b.c--));return e}function Lc(b,c,d,e,f){b=b.a[c.toString()];c=-1;b&&(c=Jc(b,d,e,f));return-1<c?b[c]:null}function Mc(b,c,d){var e=m(c),f=e?c.toString():"",g=m(d);return pb(b.a,function(b){for(var c=0;c<b.length;++c)if(!(e&&b[c].type!=f||g&&b[c].vc!=d))return!0;return!1})} -function Jc(b,c,d,e){for(var f=0;f<b.length;++f){var g=b[f];if(!g.uc&&g.Xb==c&&g.vc==!!d&&g.qd==e)return f}return-1};var Nc="closure_lm_"+(1E6*Math.random()|0),Oc={},Pc=0;function z(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)z(b,c[g],d,e,f);return null}d=Qc(d);return Ec(b)?b.La(c,d,e,f):Rc(b,c,d,!1,e,f)}function Rc(b,c,d,e,f,g){if(!c)throw Error("Invalid event type");var h=!!f,k=Sc(b);k||(b[Nc]=k=new Ic(b));d=k.add(c,d,e,f,g);if(d.a)return d;e=Tc();d.a=e;e.src=b;e.Xb=d;b.addEventListener?b.addEventListener(c.toString(),e,h):b.attachEvent(Uc(c.toString()),e);Pc++;return d} -function Tc(){var b=Vc,c=nc?function(d){return b.call(c.src,c.Xb,d)}:function(d){d=b.call(c.src,c.Xb,d);if(!d)return d};return c}function Wc(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)Wc(b,c[g],d,e,f);return null}d=Qc(d);return Ec(b)?b.gb.add(String(c),d,!0,e,f):Rc(b,c,d,!0,e,f)}function Xc(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)Xc(b,c[g],d,e,f);else d=Qc(d),Ec(b)?b.Fe(c,d,e,f):b&&(b=Sc(b))&&(c=Lc(b,c,d,!!e,f))&&Yc(c)} -function Yc(b){if(ja(b)||!b||b.uc)return!1;var c=b.src;if(Ec(c))return Kc(c.gb,b);var d=b.type,e=b.a;c.removeEventListener?c.removeEventListener(d,e,b.vc):c.detachEvent&&c.detachEvent(Uc(d),e);Pc--;(d=Sc(c))?(Kc(d,b),0==d.c&&(d.src=null,c[Nc]=null)):Hc(b);return!0}function Uc(b){return b in Oc?Oc[b]:Oc[b]="on"+b}function Zc(b,c,d,e){var f=!0;if(b=Sc(b))if(c=b.a[c.toString()])for(c=c.concat(),b=0;b<c.length;b++){var g=c[b];g&&g.vc==d&&!g.uc&&(g=$c(g,e),f=f&&!1!==g)}return f} -function $c(b,c){var d=b.Xb,e=b.qd||b.src;b.Xc&&Yc(b);return d.call(e,c)} -function Vc(b,c){if(b.uc)return!0;if(!nc){var d;if(!(d=c))a:{d=["window","event"];for(var e=ba,f;f=d.shift();)if(null!=e[f])e=e[f];else{d=null;break a}d=e}f=d;d=new zc(f,this);e=!0;if(!(0>f.keyCode||void 0!=f.returnValue)){a:{var g=!1;if(0==f.keyCode)try{f.keyCode=-1;break a}catch(h){g=!0}if(g||void 0==f.returnValue)f.returnValue=!0}f=[];for(g=d.b;g;g=g.parentNode)f.push(g);for(var g=b.type,k=f.length-1;!d.f&&0<=k;k--){d.b=f[k];var n=Zc(f[k],g,!0,d),e=e&&n}for(k=0;!d.f&&k<f.length;k++)d.b=f[k],n= -Zc(f[k],g,!1,d),e=e&&n}return e}return $c(b,new zc(c,this))}function Sc(b){b=b[Nc];return b instanceof Ic?b:null}var ad="__closure_events_fn_"+(1E9*Math.random()>>>0);function Qc(b){if(ka(b))return b;b[ad]||(b[ad]=function(c){return b.handleEvent(c)});return b[ad]};function bd(b){return function(){return b}}var cd=bd(!1),dd=bd(!0);function ed(b){return b}function gd(b){var c;c=c||0;return function(){return b.apply(this,Array.prototype.slice.call(arguments,0,c))}}function hd(b){var c=arguments,d=c.length;return function(){for(var b,f=0;f<d;f++)b=c[f].apply(this,arguments);return b}}function id(b){var c=arguments,d=c.length;return function(){for(var b=0;b<d;b++)if(!c[b].apply(this,arguments))return!1;return!0}};function jd(){pc.call(this);this.gb=new Ic(this);this.Ig=this;this.Vd=null}u(jd,pc);jd.prototype[Dc]=!0;l=jd.prototype;l.addEventListener=function(b,c,d,e){z(this,b,c,d,e)};l.removeEventListener=function(b,c,d,e){Xc(this,b,c,d,e)}; -l.dispatchEvent=function(b){var c,d=this.Vd;if(d)for(c=[];d;d=d.Vd)c.push(d);var d=this.Ig,e=b.type||b;if(ia(b))b=new uc(b,d);else if(b instanceof uc)b.target=b.target||d;else{var f=b;b=new uc(e,d);Eb(b,f)}var f=!0,g;if(c)for(var h=c.length-1;!b.f&&0<=h;h--)g=b.b=c[h],f=kd(g,e,!0,b)&&f;b.f||(g=b.b=d,f=kd(g,e,!0,b)&&f,b.f||(f=kd(g,e,!1,b)&&f));if(c)for(h=0;!b.f&&h<c.length;h++)g=b.b=c[h],f=kd(g,e,!1,b)&&f;return f}; -l.M=function(){jd.S.M.call(this);if(this.gb){var b=this.gb,c=0,d;for(d in b.a){for(var e=b.a[d],f=0;f<e.length;f++)++c,Hc(e[f]);delete b.a[d];b.c--}}this.Vd=null};l.La=function(b,c,d,e){return this.gb.add(String(b),c,!1,d,e)};l.Fe=function(b,c,d,e){return this.gb.remove(String(b),c,d,e)}; -function kd(b,c,d,e){c=b.gb.a[String(c)];if(!c)return!0;c=c.concat();for(var f=!0,g=0;g<c.length;++g){var h=c[g];if(h&&!h.uc&&h.vc==d){var k=h.Xb,n=h.qd||h.src;h.Xc&&Kc(b.gb,h);f=!1!==k.call(n,e)&&f}}return f&&0!=e.Zf}function ld(b,c,d){return Mc(b.gb,m(c)?String(c):void 0,d)};function md(){jd.call(this);this.c=0}u(md,jd);function nd(b){Yc(b)}l=md.prototype;l.l=function(){++this.c;this.dispatchEvent("change")};l.A=function(){return this.c};l.u=function(b,c,d){return z(this,b,c,!1,d)};l.B=function(b,c,d){return Wc(this,b,c,!1,d)};l.v=function(b,c,d){Xc(this,b,c,!1,d)};l.C=nd;function od(b,c,d){uc.call(this,b);this.key=c;this.oldValue=d}u(od,uc);function pd(b,c,d,e){this.source=b;this.target=c;this.b=d;this.c=e;this.d=this.a=ed}pd.prototype.e=function(b,c){var d=rd(this.source,this.b);this.a=b;this.d=c;sd(this.source,this.b,d)};function td(b){md.call(this);ma(this);this.o={};this.Ra={};this.ec={};m(b)&&this.G(b)}u(td,md);var ud={},vd={},wd={};function xd(b){return ud.hasOwnProperty(b)?ud[b]:ud[b]="change:"+b} -function rd(b,c){var d=vd.hasOwnProperty(c)?vd[c]:vd[c]="get"+(String(c.charAt(0)).toUpperCase()+String(c.substr(1)).toLowerCase()),d=x(b,d);return m(d)?d.call(b):b.get(c)}l=td.prototype;l.O=function(b,c,d){d=d||b;this.P(b);var e=xd(d);this.ec[b]=z(c,e,function(c){sd(this,b,c.oldValue)},void 0,this);c=new pd(this,c,b,d);this.Ra[b]=c;sd(this,b,this.o[b]);return c};l.get=function(b){var c,d=this.Ra;d.hasOwnProperty(b)?(b=d[b],c=rd(b.target,b.c),c=b.d(c)):this.o.hasOwnProperty(b)&&(c=this.o[b]);return c}; -l.I=function(){var b=this.Ra,c;if(xb(this.o)){if(xb(b))return[];c=b}else if(xb(b))c=this.o;else{c={};for(var d in this.o)c[d]=!0;for(d in b)c[d]=!0}return sb(c)};l.L=function(){var b={},c;for(c in this.o)b[c]=this.o[c];for(c in this.Ra)b[c]=this.get(c);return b};function sd(b,c,d){var e;e=xd(c);b.dispatchEvent(new od(e,c,d));b.dispatchEvent(new od("propertychange",c,d))} -l.set=function(b,c){var d=this.Ra;if(d.hasOwnProperty(b)){var e=d[b];c=e.a(c);var d=e.target,e=e.c,f=c,g=wd.hasOwnProperty(e)?wd[e]:wd[e]="set"+(String(e.charAt(0)).toUpperCase()+String(e.substr(1)).toLowerCase()),g=x(d,g);m(g)?g.call(d,f):d.set(e,f)}else d=this.o[b],this.o[b]=c,sd(this,b,d)};l.G=function(b){for(var c in b)this.set(c,b[c])};l.P=function(b){var c=this.ec,d=c[b];d&&(delete c[b],Yc(d),c=this.get(b),delete this.Ra[b],this.o[b]=c)};l.R=function(){for(var b in this.ec)this.P(b)};function yd(b,c){b[0]+=c[0];b[1]+=c[1];return b}function zd(b,c){var d=b[0],e=b[1],f=c[0],g=c[1],h=f[0],f=f[1],k=g[0],g=g[1],n=k-h,p=g-f,d=0===n&&0===p?0:(n*(d-h)+p*(e-f))/(n*n+p*p||0);0>=d||(1<=d?(h=k,f=g):(h+=d*n,f+=d*p));return[h,f]}function Ad(b,c){var d=Zb(b+180,360)-180,e=Math.abs(Math.round(3600*d));return Math.floor(e/3600)+"\u00b0 "+Math.floor(e/60%60)+"\u2032 "+Math.floor(e%60)+"\u2033 "+c.charAt(0>d?1:0)} -function Bd(b,c,d){return m(b)?c.replace("{x}",b[0].toFixed(d)).replace("{y}",b[1].toFixed(d)):""}function Cd(b,c){for(var d=!0,e=b.length-1;0<=e;--e)if(b[e]!=c[e]){d=!1;break}return d}function Dd(b,c){var d=Math.cos(c),e=Math.sin(c),f=b[1]*d+b[0]*e;b[0]=b[0]*d-b[1]*e;b[1]=f;return b}function Ed(b,c){var d=b[0]-c[0],e=b[1]-c[1];return d*d+e*e}function Fd(b,c){return Bd(b,"{x}, {y}",c)};function Gd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}Gd.prototype.a=4;Gd.prototype.set=function(b,c){c=c||0;for(var d=0;d<b.length&&c+d<this.length;d++)this[c+d]=b[d]};Gd.prototype.toString=Array.prototype.join;"undefined"==typeof Float32Array&&(Gd.BYTES_PER_ELEMENT=4,Gd.prototype.BYTES_PER_ELEMENT=Gd.prototype.a,Gd.prototype.set=Gd.prototype.set,Gd.prototype.toString=Gd.prototype.toString,t("Float32Array",Gd,void 0));function Hd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}Hd.prototype.a=8;Hd.prototype.set=function(b,c){c=c||0;for(var d=0;d<b.length&&c+d<this.length;d++)this[c+d]=b[d]};Hd.prototype.toString=Array.prototype.join;if("undefined"==typeof Float64Array){try{Hd.BYTES_PER_ELEMENT=8}catch(Id){}Hd.prototype.BYTES_PER_ELEMENT=Hd.prototype.a;Hd.prototype.set=Hd.prototype.set;Hd.prototype.toString=Hd.prototype.toString;t("Float64Array",Hd,void 0)};function Jd(b,c,d,e,f){b[0]=c;b[1]=d;b[2]=e;b[3]=f};function Kd(){var b=Array(16);Ld(b,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return b}function Md(){var b=Array(16);Ld(b,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return b}function Ld(b,c,d,e,f,g,h,k,n,p,q,r,s,v,y,C,F){b[0]=c;b[1]=d;b[2]=e;b[3]=f;b[4]=g;b[5]=h;b[6]=k;b[7]=n;b[8]=p;b[9]=q;b[10]=r;b[11]=s;b[12]=v;b[13]=y;b[14]=C;b[15]=F} -function Nd(b,c){b[0]=c[0];b[1]=c[1];b[2]=c[2];b[3]=c[3];b[4]=c[4];b[5]=c[5];b[6]=c[6];b[7]=c[7];b[8]=c[8];b[9]=c[9];b[10]=c[10];b[11]=c[11];b[12]=c[12];b[13]=c[13];b[14]=c[14];b[15]=c[15]}function Od(b){b[0]=1;b[1]=0;b[2]=0;b[3]=0;b[4]=0;b[5]=1;b[6]=0;b[7]=0;b[8]=0;b[9]=0;b[10]=1;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1} -function Pd(b,c,d){var e=b[0],f=b[1],g=b[2],h=b[3],k=b[4],n=b[5],p=b[6],q=b[7],r=b[8],s=b[9],v=b[10],y=b[11],C=b[12],F=b[13],G=b[14];b=b[15];var w=c[0],U=c[1],N=c[2],Y=c[3],T=c[4],qa=c[5],vb=c[6],Ka=c[7],ac=c[8],Sb=c[9],La=c[10],Pa=c[11],Ud=c[12],qd=c[13],fd=c[14];c=c[15];d[0]=e*w+k*U+r*N+C*Y;d[1]=f*w+n*U+s*N+F*Y;d[2]=g*w+p*U+v*N+G*Y;d[3]=h*w+q*U+y*N+b*Y;d[4]=e*T+k*qa+r*vb+C*Ka;d[5]=f*T+n*qa+s*vb+F*Ka;d[6]=g*T+p*qa+v*vb+G*Ka;d[7]=h*T+q*qa+y*vb+b*Ka;d[8]=e*ac+k*Sb+r*La+C*Pa;d[9]=f*ac+n*Sb+s*La+F*Pa; -d[10]=g*ac+p*Sb+v*La+G*Pa;d[11]=h*ac+q*Sb+y*La+b*Pa;d[12]=e*Ud+k*qd+r*fd+C*c;d[13]=f*Ud+n*qd+s*fd+F*c;d[14]=g*Ud+p*qd+v*fd+G*c;d[15]=h*Ud+q*qd+y*fd+b*c}function Qd(b,c,d){var e=b[1]*c+b[5]*d+0*b[9]+b[13],f=b[2]*c+b[6]*d+0*b[10]+b[14],g=b[3]*c+b[7]*d+0*b[11]+b[15];b[12]=b[0]*c+b[4]*d+0*b[8]+b[12];b[13]=e;b[14]=f;b[15]=g}function Rd(b,c,d){Ld(b,b[0]*c,b[1]*c,b[2]*c,b[3]*c,b[4]*d,b[5]*d,b[6]*d,b[7]*d,1*b[8],1*b[9],1*b[10],1*b[11],b[12],b[13],b[14],b[15])} -function Sd(b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],n=b[6],p=b[7],q=Math.cos(c),r=Math.sin(c);b[0]=d*q+h*r;b[1]=e*q+k*r;b[2]=f*q+n*r;b[3]=g*q+p*r;b[4]=d*-r+h*q;b[5]=e*-r+k*q;b[6]=f*-r+n*q;b[7]=g*-r+p*q}new Float64Array(3);new Float64Array(3);new Float64Array(4);new Float64Array(4);new Float64Array(4);new Float64Array(16);function Td(b){for(var c=Vd(),d=0,e=b.length;d<e;++d){var f=c,g=b[d];g[0]<f[0]&&(f[0]=g[0]);g[0]>f[2]&&(f[2]=g[0]);g[1]<f[1]&&(f[1]=g[1]);g[1]>f[3]&&(f[3]=g[1])}return c}function Wd(b,c,d){var e=Math.min.apply(null,b),f=Math.min.apply(null,c);b=Math.max.apply(null,b);c=Math.max.apply(null,c);return Xd(e,f,b,c,d)}function Yd(b,c,d){return m(d)?(d[0]=b[0]-c,d[1]=b[1]-c,d[2]=b[2]+c,d[3]=b[3]+c,d):[b[0]-c,b[1]-c,b[2]+c,b[3]+c]} -function Zd(b,c,d){c=c<b[0]?b[0]-c:b[2]<c?c-b[2]:0;b=d<b[1]?b[1]-d:b[3]<d?d-b[3]:0;return c*c+b*b}function $d(b,c){return b[0]<=c[0]&&c[2]<=b[2]&&b[1]<=c[1]&&c[3]<=b[3]}function ae(b,c,d){return b[0]<=c&&c<=b[2]&&b[1]<=d&&d<=b[3]}function be(b,c){var d=b[1],e=b[2],f=b[3],g=c[0],h=c[1],k=0;g<b[0]?k=k|16:g>e&&(k=k|4);h<d?k|=8:h>f&&(k|=2);0===k&&(k=1);return k}function Vd(){return[Infinity,Infinity,-Infinity,-Infinity]}function Xd(b,c,d,e,f){return m(f)?(f[0]=b,f[1]=c,f[2]=d,f[3]=e,f):[b,c,d,e]} -function ce(b,c){var d=b[0],e=b[1];return Xd(d,e,d,e,c)}function de(b,c){return b[0]==c[0]&&b[2]==c[2]&&b[1]==c[1]&&b[3]==c[3]}function ee(b,c){c[0]<b[0]&&(b[0]=c[0]);c[2]>b[2]&&(b[2]=c[2]);c[1]<b[1]&&(b[1]=c[1]);c[3]>b[3]&&(b[3]=c[3]);return b}function fe(b,c,d,e,f){for(;d<e;d+=f){var g=b,h=c[d],k=c[d+1];g[0]=Math.min(g[0],h);g[1]=Math.min(g[1],k);g[2]=Math.max(g[2],h);g[3]=Math.max(g[3],k)}return b} -function ge(b,c){var d;return(d=c.call(void 0,he(b)))||(d=c.call(void 0,ie(b)))||(d=c.call(void 0,je(b)))?d:(d=c.call(void 0,ie(b)))?d:!1}function he(b){return[b[0],b[1]]}function ie(b){return[b[2],b[1]]}function ke(b){return[(b[0]+b[2])/2,(b[1]+b[3])/2]}function le(b,c){var d;"bottom-left"===c?d=he(b):"bottom-right"===c?d=ie(b):"top-left"===c?d=me(b):"top-right"===c&&(d=je(b));return d} -function ne(b,c,d,e){var f=c*e[0]/2;e=c*e[1]/2;c=Math.cos(d);d=Math.sin(d);f=[-f,-f,f,f];e=[-e,e,-e,e];var g,h,k;for(g=0;4>g;++g)h=f[g],k=e[g],f[g]=b[0]+h*c-k*d,e[g]=b[1]+h*d+k*c;return Wd(f,e,void 0)}function oe(b){return b[3]-b[1]}function pe(b,c,d){d=m(d)?d:Vd();qe(b,c)&&(d[0]=b[0]>c[0]?b[0]:c[0],d[1]=b[1]>c[1]?b[1]:c[1],d[2]=b[2]<c[2]?b[2]:c[2],d[3]=b[3]<c[3]?b[3]:c[3]);return d}function me(b){return[b[0],b[3]]}function je(b){return[b[2],b[3]]}function re(b){return b[2]-b[0]} -function qe(b,c){return b[0]<=c[2]&&b[2]>=c[0]&&b[1]<=c[3]&&b[3]>=c[1]}function se(b){return b[2]<b[0]||b[3]<b[1]}function te(b,c){return m(c)?(c[0]=b[0],c[1]=b[1],c[2]=b[2],c[3]=b[3],c):b}function ue(b,c){var d=(b[2]-b[0])/2*(c-1),e=(b[3]-b[1])/2*(c-1);b[0]-=d;b[2]+=d;b[1]-=e;b[3]+=e}function we(b,c,d){b=[b[0],b[1],b[0],b[3],b[2],b[1],b[2],b[3]];c(b,b,2);return Wd([b[0],b[2],b[4],b[6]],[b[1],b[3],b[5],b[7]],d)};/* +var na="closure_uid_"+(1E9*Math.random()>>>0),oa=0;function pa(b,c,d){return b.call.apply(b.bind,arguments)}function qa(b,c,d){if(!b)throw Error();if(2<arguments.length){var e=Array.prototype.slice.call(arguments,2);return function(){var d=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(d,e);return b.apply(c,d)}}return function(){return b.apply(c,arguments)}} +function ra(b,c,d){ra=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?pa:qa;return ra.apply(null,arguments)}function sa(b,c){var d=Array.prototype.slice.call(arguments,1);return function(){var c=d.slice();c.push.apply(c,arguments);return b.apply(this,c)}}var ua=Date.now||function(){return+new Date}; +function w(b,c){function d(){}d.prototype=c.prototype;b.aa=c.prototype;b.prototype=new d;b.prototype.constructor=b;b.bp=function(b,d,g){for(var h=Array(arguments.length-2),k=2;k<arguments.length;k++)h[k-2]=arguments[k];return c.prototype[d].apply(b,h)}};var va,wa;function xa(b){if(Error.captureStackTrace)Error.captureStackTrace(this,xa);else{var c=Error().stack;c&&(this.stack=c)}b&&(this.message=String(b))}w(xa,Error);xa.prototype.name="CustomError";var ya;function za(b,c){var d=b.length-c.length;return 0<=d&&b.indexOf(c,d)==d}function Ba(b,c){for(var d=b.split("%s"),e="",f=Array.prototype.slice.call(arguments,1);f.length&&1<d.length;)e+=d.shift()+f.shift();return e+d.join("%s")}var Ca=String.prototype.trim?function(b){return b.trim()}:function(b){return b.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}; +function Da(b){if(!Ea.test(b))return b;-1!=b.indexOf("&")&&(b=b.replace(Fa,"&"));-1!=b.indexOf("<")&&(b=b.replace(Ga,"<"));-1!=b.indexOf(">")&&(b=b.replace(Ha,">"));-1!=b.indexOf('"')&&(b=b.replace(Ja,"""));-1!=b.indexOf("'")&&(b=b.replace(Ka,"'"));-1!=b.indexOf("\x00")&&(b=b.replace(La,"�"));return b}var Fa=/&/g,Ga=/</g,Ha=/>/g,Ja=/"/g,Ka=/'/g,La=/\x00/g,Ea=/[\x00&<>"']/; +function Ma(b){b=m(void 0)?b.toFixed(void 0):String(b);var c=b.indexOf(".");-1==c&&(c=b.length);c=Math.max(0,2-c);return Array(c+1).join("0")+b} +function Na(b,c){for(var d=0,e=Ca(String(b)).split("."),f=Ca(String(c)).split("."),g=Math.max(e.length,f.length),h=0;0==d&&h<g;h++){var k=e[h]||"",n=f[h]||"",p=RegExp("(\\d*)(\\D*)","g"),q=RegExp("(\\d*)(\\D*)","g");do{var r=p.exec(k)||["","",""],t=q.exec(n)||["","",""];if(0==r[0].length&&0==t[0].length)break;d=Oa(0==r[1].length?0:parseInt(r[1],10),0==t[1].length?0:parseInt(t[1],10))||Oa(0==r[2].length,0==t[2].length)||Oa(r[2],t[2])}while(0==d)}return d}function Oa(b,c){return b<c?-1:b>c?1:0};var Qa=Array.prototype;function Ra(b,c){return Qa.indexOf.call(b,c,void 0)}function Sa(b,c,d){Qa.forEach.call(b,c,d)}function Ta(b,c){return Qa.filter.call(b,c,void 0)}function Ua(b,c,d){return Qa.map.call(b,c,d)}function Va(b,c){return Qa.some.call(b,c,void 0)}function Wa(b,c){var d=Xa(b,c,void 0);return 0>d?null:ia(b)?b.charAt(d):b[d]}function Xa(b,c,d){for(var e=b.length,f=ia(b)?b.split(""):b,g=0;g<e;g++)if(g in f&&c.call(d,f[g],g,b))return g;return-1}function Ya(b,c){return 0<=Ra(b,c)} +function Za(b,c){var d=Ra(b,c),e;(e=0<=d)&&Qa.splice.call(b,d,1);return e}function ab(b){return Qa.concat.apply(Qa,arguments)}function bb(b){var c=b.length;if(0<c){for(var d=Array(c),e=0;e<c;e++)d[e]=b[e];return d}return[]}function cb(b,c){for(var d=1;d<arguments.length;d++){var e=arguments[d];if(ha(e)){var f=b.length||0,g=e.length||0;b.length=f+g;for(var h=0;h<g;h++)b[f+h]=e[h]}else b.push(e)}}function db(b,c,d,e){Qa.splice.apply(b,eb(arguments,1))} +function eb(b,c,d){return 2>=arguments.length?Qa.slice.call(b,c):Qa.slice.call(b,c,d)}function gb(b,c){b.sort(c||hb)}function ib(b,c){if(!ha(b)||!ha(c)||b.length!=c.length)return!1;for(var d=b.length,e=jb,f=0;f<d;f++)if(!e(b[f],c[f]))return!1;return!0}function hb(b,c){return b>c?1:b<c?-1:0}function jb(b,c){return b===c} +function kb(b){for(var c=[],d=0;d<arguments.length;d++){var e=arguments[d];if(ga(e))for(var f=0;f<e.length;f+=8192)for(var g=kb.apply(null,eb(e,f,f+8192)),h=0;h<g.length;h++)c.push(g[h]);else c.push(e)}return c};var lb;a:{var mb=ba.navigator;if(mb){var nb=mb.userAgent;if(nb){lb=nb;break a}}lb=""}function ob(b){return-1!=lb.indexOf(b)};function pb(b,c,d){for(var e in b)c.call(d,b[e],e,b)}function qb(b,c){for(var d in b)if(c.call(void 0,b[d],d,b))return!0;return!1}function rb(b){var c=0,d;for(d in b)c++;return c}function sb(b){var c=[],d=0,e;for(e in b)c[d++]=b[e];return c}function tb(b){var c=[],d=0,e;for(e in b)c[d++]=e;return c}function ub(b,c){return c in b}function vb(b,c){for(var d in b)if(b[d]==c)return!0;return!1}function wb(b,c){for(var d in b)if(c.call(void 0,b[d],d,b))return d} +function xb(b){for(var c in b)return!1;return!0}function yb(b){for(var c in b)delete b[c]}function zb(b,c){c in b&&delete b[c]}function Ab(b,c,d){return c in b?b[c]:d}function Bb(b,c){var d=[];return c in b?b[c]:b[c]=d}function Cb(b){var c={},d;for(d in b)c[d]=b[d];return c}function Eb(b){var c=ea(b);if("object"==c||"array"==c){if(b.clone)return b.clone();var c="array"==c?[]:{},d;for(d in b)c[d]=Eb(b[d]);return c}return b}var Fb="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "); +function Gb(b,c){for(var d,e,f=1;f<arguments.length;f++){e=arguments[f];for(d in e)b[d]=e[d];for(var g=0;g<Fb.length;g++)d=Fb[g],Object.prototype.hasOwnProperty.call(e,d)&&(b[d]=e[d])}}function Hb(b){var c=arguments.length;if(1==c&&ga(arguments[0]))return Hb.apply(null,arguments[0]);for(var d={},e=0;e<c;e++)d[arguments[e]]=!0;return d};var Ib=ob("Opera")||ob("OPR"),Jb=ob("Trident")||ob("MSIE"),Kb=ob("Gecko")&&-1==lb.toLowerCase().indexOf("webkit")&&!(ob("Trident")||ob("MSIE")),Lb=-1!=lb.toLowerCase().indexOf("webkit"),Mb=ob("Macintosh"),Nb=ob("Windows"),Ob=ob("Linux")||ob("CrOS");function Pb(){var b=ba.document;return b?b.documentMode:void 0} +var Qb=function(){var b="",c;if(Ib&&ba.opera)return b=ba.opera.version,ka(b)?b():b;Kb?c=/rv\:([^\);]+)(\)|;)/:Jb?c=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:Lb&&(c=/WebKit\/(\S+)/);c&&(b=(b=c.exec(lb))?b[1]:"");return Jb&&(c=Pb(),c>parseFloat(b))?String(c):b}(),Rb={};function Sb(b){return Rb[b]||(Rb[b]=0<=Na(Qb,b))}var Tb=ba.document,Ub=Tb&&Jb?Pb()||("CSS1Compat"==Tb.compatMode?parseInt(Qb,10):5):void 0;var Vb=Jb&&!Sb("9.0")&&""!==Qb;function Wb(b,c,d){return Math.min(Math.max(b,c),d)}function Xb(b,c){var d=b%c;return 0>d*c?d+c:d}function Yb(b,c,d){return b+d*(c-b)}function Zb(b){return b*Math.PI/180};function $b(b){return function(c){if(m(c))return[Wb(c[0],b[0],b[2]),Wb(c[1],b[1],b[3])]}}function ac(b){return b};function bc(b,c,d){var e=b.length;if(b[0]<=c)return 0;if(!(c<=b[e-1]))if(0<d)for(d=1;d<e;++d){if(b[d]<c)return d-1}else if(0>d)for(d=1;d<e;++d){if(b[d]<=c)return d}else for(d=1;d<e;++d){if(b[d]==c)return d;if(b[d]<c)return b[d-1]-c<c-b[d]?d-1:d}return e-1};function cc(b){return function(c,d,e){if(m(c))return c=bc(b,c,e),c=Wb(c+d,0,b.length-1),b[c]}}function dc(b,c,d){return function(e,f,g){if(m(e))return g=0<g?0:0>g?1:.5,e=Math.floor(Math.log(c/e)/Math.log(b)+g),f=Math.max(e+f,0),m(d)&&(f=Math.min(f,d)),c/Math.pow(b,f)}};function ec(b){if(m(b))return 0}function fc(b,c){if(m(b))return b+c}function gc(b){var c=2*Math.PI/b;return function(b,e){if(m(b))return b=Math.floor((b+e)/c+.5)*c}}function hc(){var b=Zb(5);return function(c,d){if(m(c))return Math.abs(c+d)<=b?0:c+d}};function ic(b,c,d){this.center=b;this.resolution=c;this.rotation=d};var jc=!Jb||Jb&&9<=Ub,kc=!Jb||Jb&&9<=Ub,mc=Jb&&!Sb("9");!Lb||Sb("528");Kb&&Sb("1.9b")||Jb&&Sb("8")||Ib&&Sb("9.5")||Lb&&Sb("528");Kb&&!Sb("8")||Jb&&Sb("9");function nc(){0!=oc&&(pc[ma(this)]=this);this.fa=this.fa;this.da=this.da}var oc=0,pc={};nc.prototype.fa=!1;nc.prototype.jd=function(){if(!this.fa&&(this.fa=!0,this.X(),0!=oc)){var b=ma(this);delete pc[b]}};function qc(b,c){var d=sa(rc,c);b.fa?d.call(void 0):(b.da||(b.da=[]),b.da.push(m(void 0)?ra(d,void 0):d))}nc.prototype.X=function(){if(this.da)for(;this.da.length;)this.da.shift()()};function rc(b){b&&"function"==typeof b.jd&&b.jd()};function sc(b,c){this.type=b;this.c=this.target=c;this.i=!1;this.Dh=!0}sc.prototype.ob=function(){this.i=!0};sc.prototype.preventDefault=function(){this.Dh=!1};function tc(b){b.ob()}function uc(b){b.preventDefault()};var vc=Jb?"focusout":"DOMFocusOut";function wc(b){wc[" "](b);return b}wc[" "]=ca;function xc(b,c){sc.call(this,b?b.type:"");this.relatedTarget=this.c=this.target=null;this.B=this.g=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.v=this.f=this.b=this.l=!1;this.state=null;this.j=!1;this.a=null;b&&yc(this,b,c)}w(xc,sc);var zc=[1,4,2]; +function yc(b,c,d){b.a=c;var e=b.type=c.type;b.target=c.target||c.srcElement;b.c=d;if(d=c.relatedTarget){if(Kb){var f;a:{try{wc(d.nodeName);f=!0;break a}catch(g){}f=!1}f||(d=null)}}else"mouseover"==e?d=c.fromElement:"mouseout"==e&&(d=c.toElement);b.relatedTarget=d;Object.defineProperties?Object.defineProperties(b,{offsetX:{configurable:!0,enumerable:!0,get:b.sg,set:b.to},offsetY:{configurable:!0,enumerable:!0,get:b.tg,set:b.uo}}):(b.offsetX=b.sg(),b.offsetY=b.tg());b.clientX=void 0!==c.clientX?c.clientX: +c.pageX;b.clientY=void 0!==c.clientY?c.clientY:c.pageY;b.screenX=c.screenX||0;b.screenY=c.screenY||0;b.button=c.button;b.g=c.keyCode||0;b.B=c.charCode||("keypress"==e?c.keyCode:0);b.l=c.ctrlKey;b.b=c.altKey;b.f=c.shiftKey;b.v=c.metaKey;b.j=Mb?c.metaKey:c.ctrlKey;b.state=c.state;c.defaultPrevented&&b.preventDefault()}function Ac(b){return(jc?0==b.a.button:"click"==b.type?!0:!!(b.a.button&zc[0]))&&!(Lb&&Mb&&b.l)}l=xc.prototype; +l.ob=function(){xc.aa.ob.call(this);this.a.stopPropagation?this.a.stopPropagation():this.a.cancelBubble=!0};l.preventDefault=function(){xc.aa.preventDefault.call(this);var b=this.a;if(b.preventDefault)b.preventDefault();else if(b.returnValue=!1,mc)try{if(b.ctrlKey||112<=b.keyCode&&123>=b.keyCode)b.keyCode=-1}catch(c){}};l.ij=function(){return this.a};l.sg=function(){return Lb||void 0!==this.a.offsetX?this.a.offsetX:this.a.layerX}; +l.to=function(b){Object.defineProperties(this,{offsetX:{writable:!0,enumerable:!0,configurable:!0,value:b}})};l.tg=function(){return Lb||void 0!==this.a.offsetY?this.a.offsetY:this.a.layerY};l.uo=function(b){Object.defineProperties(this,{offsetY:{writable:!0,enumerable:!0,configurable:!0,value:b}})};var Bc="closure_listenable_"+(1E6*Math.random()|0);function Cc(b){return!(!b||!b[Bc])}var Dc=0;function Ec(b,c,d,e,f){this.listener=b;this.a=null;this.src=c;this.type=d;this.ad=!!e;this.ee=f;this.key=++Dc;this.Rc=this.Ld=!1}function Fc(b){b.Rc=!0;b.listener=null;b.a=null;b.src=null;b.ee=null};function Gc(b){this.src=b;this.a={};this.b=0}Gc.prototype.add=function(b,c,d,e,f){var g=b.toString();b=this.a[g];b||(b=this.a[g]=[],this.b++);var h=Ic(b,c,e,f);-1<h?(c=b[h],d||(c.Ld=!1)):(c=new Ec(c,this.src,g,!!e,f),c.Ld=d,b.push(c));return c};Gc.prototype.remove=function(b,c,d,e){b=b.toString();if(!(b in this.a))return!1;var f=this.a[b];c=Ic(f,c,d,e);return-1<c?(Fc(f[c]),Qa.splice.call(f,c,1),0==f.length&&(delete this.a[b],this.b--),!0):!1}; +function Jc(b,c){var d=c.type;if(!(d in b.a))return!1;var e=Za(b.a[d],c);e&&(Fc(c),0==b.a[d].length&&(delete b.a[d],b.b--));return e}function Kc(b,c,d,e,f){b=b.a[c.toString()];c=-1;b&&(c=Ic(b,d,e,f));return-1<c?b[c]:null}function Lc(b,c,d){var e=m(c),f=e?c.toString():"",g=m(d);return qb(b.a,function(b){for(var c=0;c<b.length;++c)if(!(e&&b[c].type!=f||g&&b[c].ad!=d))return!0;return!1})} +function Ic(b,c,d,e){for(var f=0;f<b.length;++f){var g=b[f];if(!g.Rc&&g.listener==c&&g.ad==!!d&&g.ee==e)return f}return-1};var Mc="closure_lm_"+(1E6*Math.random()|0),Nc={},Oc=0;function x(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)x(b,c[g],d,e,f);return null}d=Pc(d);return Cc(b)?b.Ra(c,d,e,f):Qc(b,c,d,!1,e,f)}function Qc(b,c,d,e,f,g){if(!c)throw Error("Invalid event type");var h=!!f,k=Rc(b);k||(b[Mc]=k=new Gc(b));d=k.add(c,d,e,f,g);if(d.a)return d;e=Sc();d.a=e;e.src=b;e.listener=d;b.addEventListener?b.addEventListener(c.toString(),e,h):b.attachEvent(Tc(c.toString()),e);Oc++;return d} +function Sc(){var b=Uc,c=kc?function(d){return b.call(c.src,c.listener,d)}:function(d){d=b.call(c.src,c.listener,d);if(!d)return d};return c}function Vc(b,c,d,e,f){if(ga(c)){for(var g=0;g<c.length;g++)Vc(b,c[g],d,e,f);return null}d=Pc(d);return Cc(b)?b.jb.add(String(c),d,!0,e,f):Qc(b,c,d,!0,e,f)}function Wc(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)Wc(b,c[g],d,e,f);else d=Pc(d),Cc(b)?b.Nf(c,d,e,f):b&&(b=Rc(b))&&(c=Kc(b,c,d,!!e,f))&&Xc(c)} +function Xc(b){if(ja(b)||!b||b.Rc)return!1;var c=b.src;if(Cc(c))return Jc(c.jb,b);var d=b.type,e=b.a;c.removeEventListener?c.removeEventListener(d,e,b.ad):c.detachEvent&&c.detachEvent(Tc(d),e);Oc--;(d=Rc(c))?(Jc(d,b),0==d.b&&(d.src=null,c[Mc]=null)):Fc(b);return!0}function Tc(b){return b in Nc?Nc[b]:Nc[b]="on"+b}function Yc(b,c,d,e){var f=!0;if(b=Rc(b))if(c=b.a[c.toString()])for(c=c.concat(),b=0;b<c.length;b++){var g=c[b];g&&g.ad==d&&!g.Rc&&(g=Zc(g,e),f=f&&!1!==g)}return f} +function Zc(b,c){var d=b.listener,e=b.ee||b.src;b.Ld&&Xc(b);return d.call(e,c)} +function Uc(b,c){if(b.Rc)return!0;if(!kc){var d;if(!(d=c))a:{d=["window","event"];for(var e=ba,f;f=d.shift();)if(null!=e[f])e=e[f];else{d=null;break a}d=e}f=d;d=new xc(f,this);e=!0;if(!(0>f.keyCode||void 0!=f.returnValue)){a:{var g=!1;if(0==f.keyCode)try{f.keyCode=-1;break a}catch(h){g=!0}if(g||void 0==f.returnValue)f.returnValue=!0}f=[];for(g=d.c;g;g=g.parentNode)f.push(g);for(var g=b.type,k=f.length-1;!d.i&&0<=k;k--){d.c=f[k];var n=Yc(f[k],g,!0,d),e=e&&n}for(k=0;!d.i&&k<f.length;k++)d.c=f[k],n= +Yc(f[k],g,!1,d),e=e&&n}return e}return Zc(b,new xc(c,this))}function Rc(b){b=b[Mc];return b instanceof Gc?b:null}var $c="__closure_events_fn_"+(1E9*Math.random()>>>0);function Pc(b){if(ka(b))return b;b[$c]||(b[$c]=function(c){return b.handleEvent(c)});return b[$c]};function ad(){nc.call(this);this.jb=new Gc(this);this.Hd=this;this.Ma=null}w(ad,nc);ad.prototype[Bc]=!0;l=ad.prototype;l.addEventListener=function(b,c,d,e){x(this,b,c,d,e)};l.removeEventListener=function(b,c,d,e){Wc(this,b,c,d,e)}; +function C(b,c){var d,e=b.Ma;if(e)for(d=[];e;e=e.Ma)d.push(e);var e=b.Hd,f=c,g=f.type||f;if(ia(f))f=new sc(f,e);else if(f instanceof sc)f.target=f.target||e;else{var h=f,f=new sc(g,e);Gb(f,h)}var h=!0,k;if(d)for(var n=d.length-1;!f.i&&0<=n;n--)k=f.c=d[n],h=bd(k,g,!0,f)&&h;f.i||(k=f.c=e,h=bd(k,g,!0,f)&&h,f.i||(h=bd(k,g,!1,f)&&h));if(d)for(n=0;!f.i&&n<d.length;n++)k=f.c=d[n],h=bd(k,g,!1,f)&&h;return h} +l.X=function(){ad.aa.X.call(this);if(this.jb){var b=this.jb,c=0,d;for(d in b.a){for(var e=b.a[d],f=0;f<e.length;f++)++c,Fc(e[f]);delete b.a[d];b.b--}}this.Ma=null};l.Ra=function(b,c,d,e){return this.jb.add(String(b),c,!1,d,e)};l.Nf=function(b,c,d,e){return this.jb.remove(String(b),c,d,e)}; +function bd(b,c,d,e){c=b.jb.a[String(c)];if(!c)return!0;c=c.concat();for(var f=!0,g=0;g<c.length;++g){var h=c[g];if(h&&!h.Rc&&h.ad==d){var k=h.listener,n=h.ee||h.src;h.Ld&&Jc(b.jb,h);f=!1!==k.call(n,e)&&f}}return f&&0!=e.Dh}function cd(b,c,d){return Lc(b.jb,m(c)?String(c):void 0,d)};function dd(){ad.call(this);this.a=0}w(dd,ad);function ed(b){Xc(b)}l=dd.prototype;l.s=function(){++this.a;C(this,"change")};l.L=function(){return this.a};l.D=function(b,c,d){return x(this,b,c,!1,d)};l.M=function(b,c,d){return Vc(this,b,c,!1,d)};l.J=function(b,c,d){Wc(this,b,c,!1,d)};l.N=ed;function fd(b,c,d){sc.call(this,b);this.key=c;this.oldValue=d}w(fd,sc);function gd(b){dd.call(this);ma(this);this.B={};m(b)&&this.I(b)}w(gd,dd);var hd={};function id(b){return hd.hasOwnProperty(b)?hd[b]:hd[b]="change:"+b}l=gd.prototype;l.get=function(b){var c;this.B.hasOwnProperty(b)&&(c=this.B[b]);return c};l.O=function(){return tb(this.B)};l.P=function(){var b={},c;for(c in this.B)b[c]=this.B[c];return b};function jd(b,c,d){var e;e=id(c);C(b,new fd(e,c,d));C(b,new fd("propertychange",c,d))} +l.set=function(b,c){var d=this.B[b];this.B[b]=c;jd(this,b,d)};l.I=function(b){for(var c in b)this.set(c,b[c])};l.S=function(b){if(b in this.B){var c=this.B[b];delete this.B[b];jd(this,b,c)}};function kd(b,c,d){m(d)||(d=[0,0]);d[0]=b[0]+2*c;d[1]=b[1]+2*c;return d}function ld(b,c,d){m(d)||(d=[0,0]);d[0]=b[0]*c+.5|0;d[1]=b[1]*c+.5|0;return d}function md(b,c){if(ga(b))return b;m(c)?(c[0]=b,c[1]=b):c=[b,b];return c};function nd(b,c){b[0]+=c[0];b[1]+=c[1];return b}function od(b,c){var d=b[0],e=b[1],f=c[0],g=c[1],h=f[0],f=f[1],k=g[0],g=g[1],n=k-h,p=g-f,d=0===n&&0===p?0:(n*(d-h)+p*(e-f))/(n*n+p*p||0);0>=d||(1<=d?(h=k,f=g):(h+=d*n,f+=d*p));return[h,f]}function pd(b,c){var d=Xb(b+180,360)-180,e=Math.abs(Math.round(3600*d));return Math.floor(e/3600)+"\u00b0 "+Ma(Math.floor(e/60%60))+"\u2032 "+Ma(Math.floor(e%60))+"\u2033 "+c.charAt(0>d?1:0)} +function qd(b,c,d){return m(b)?c.replace("{x}",b[0].toFixed(d)).replace("{y}",b[1].toFixed(d)):""}function rd(b,c){for(var d=!0,e=b.length-1;0<=e;--e)if(b[e]!=c[e]){d=!1;break}return d}function sd(b,c){var d=Math.cos(c),e=Math.sin(c),f=b[1]*d+b[0]*e;b[0]=b[0]*d-b[1]*e;b[1]=f;return b}function td(b,c){var d=b[0]-c[0],e=b[1]-c[1];return d*d+e*e}function ud(b,c){return td(b,od(b,c))}function vd(b,c){return qd(b,"{x}, {y}",c)};function wd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}wd.prototype.a=4;wd.prototype.set=function(b,c){c=c||0;for(var d=0;d<b.length&&c+d<this.length;d++)this[c+d]=b[d]};wd.prototype.toString=Array.prototype.join;"undefined"==typeof Float32Array&&(wd.BYTES_PER_ELEMENT=4,wd.prototype.BYTES_PER_ELEMENT=wd.prototype.a,wd.prototype.set=wd.prototype.set,wd.prototype.toString=wd.prototype.toString,u("Float32Array",wd,void 0));function xd(b){this.length=b.length||b;for(var c=0;c<this.length;c++)this[c]=b[c]||0}xd.prototype.a=8;xd.prototype.set=function(b,c){c=c||0;for(var d=0;d<b.length&&c+d<this.length;d++)this[c+d]=b[d]};xd.prototype.toString=Array.prototype.join;if("undefined"==typeof Float64Array){try{xd.BYTES_PER_ELEMENT=8}catch(yd){}xd.prototype.BYTES_PER_ELEMENT=xd.prototype.a;xd.prototype.set=xd.prototype.set;xd.prototype.toString=xd.prototype.toString;u("Float64Array",xd,void 0)};function zd(b,c,d,e,f){b[0]=c;b[1]=d;b[2]=e;b[3]=f};function Ad(){var b=Array(16);Bd(b,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return b}function Cd(){var b=Array(16);Bd(b,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return b}function Bd(b,c,d,e,f,g,h,k,n,p,q,r,t,v,B,z,E){b[0]=c;b[1]=d;b[2]=e;b[3]=f;b[4]=g;b[5]=h;b[6]=k;b[7]=n;b[8]=p;b[9]=q;b[10]=r;b[11]=t;b[12]=v;b[13]=B;b[14]=z;b[15]=E} +function Dd(b,c){b[0]=c[0];b[1]=c[1];b[2]=c[2];b[3]=c[3];b[4]=c[4];b[5]=c[5];b[6]=c[6];b[7]=c[7];b[8]=c[8];b[9]=c[9];b[10]=c[10];b[11]=c[11];b[12]=c[12];b[13]=c[13];b[14]=c[14];b[15]=c[15]}function Ed(b){b[0]=1;b[1]=0;b[2]=0;b[3]=0;b[4]=0;b[5]=1;b[6]=0;b[7]=0;b[8]=0;b[9]=0;b[10]=1;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1} +function Fd(b,c,d){var e=b[0],f=b[1],g=b[2],h=b[3],k=b[4],n=b[5],p=b[6],q=b[7],r=b[8],t=b[9],v=b[10],B=b[11],z=b[12],E=b[13],A=b[14];b=b[15];var y=c[0],J=c[1],L=c[2],H=c[3],S=c[4],ta=c[5],Pa=c[6],R=c[7],Aa=c[8],fb=c[9],Ia=c[10],Db=c[11],$a=c[12],Hc=c[13],lc=c[14];c=c[15];d[0]=e*y+k*J+r*L+z*H;d[1]=f*y+n*J+t*L+E*H;d[2]=g*y+p*J+v*L+A*H;d[3]=h*y+q*J+B*L+b*H;d[4]=e*S+k*ta+r*Pa+z*R;d[5]=f*S+n*ta+t*Pa+E*R;d[6]=g*S+p*ta+v*Pa+A*R;d[7]=h*S+q*ta+B*Pa+b*R;d[8]=e*Aa+k*fb+r*Ia+z*Db;d[9]=f*Aa+n*fb+t*Ia+E*Db;d[10]= +g*Aa+p*fb+v*Ia+A*Db;d[11]=h*Aa+q*fb+B*Ia+b*Db;d[12]=e*$a+k*Hc+r*lc+z*c;d[13]=f*$a+n*Hc+t*lc+E*c;d[14]=g*$a+p*Hc+v*lc+A*c;d[15]=h*$a+q*Hc+B*lc+b*c} +function Gd(b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],n=b[6],p=b[7],q=b[8],r=b[9],t=b[10],v=b[11],B=b[12],z=b[13],E=b[14],A=b[15],y=d*k-e*h,J=d*n-f*h,L=d*p-g*h,H=e*n-f*k,S=e*p-g*k,ta=f*p-g*n,Pa=q*z-r*B,R=q*E-t*B,Aa=q*A-v*B,fb=r*E-t*z,Ia=r*A-v*z,Db=t*A-v*E,$a=y*Db-J*Ia+L*fb+H*Aa-S*R+ta*Pa;0!=$a&&($a=1/$a,c[0]=(k*Db-n*Ia+p*fb)*$a,c[1]=(-e*Db+f*Ia-g*fb)*$a,c[2]=(z*ta-E*S+A*H)*$a,c[3]=(-r*ta+t*S-v*H)*$a,c[4]=(-h*Db+n*Aa-p*R)*$a,c[5]=(d*Db-f*Aa+g*R)*$a,c[6]=(-B*ta+E*L-A*J)*$a,c[7]=(q*ta-t*L+v* +J)*$a,c[8]=(h*Ia-k*Aa+p*Pa)*$a,c[9]=(-d*Ia+e*Aa-g*Pa)*$a,c[10]=(B*S-z*L+A*y)*$a,c[11]=(-q*S+r*L-v*y)*$a,c[12]=(-h*fb+k*R-n*Pa)*$a,c[13]=(d*fb-e*R+f*Pa)*$a,c[14]=(-B*H+z*J-E*y)*$a,c[15]=(q*H-r*J+t*y)*$a)}function Hd(b,c,d){var e=b[1]*c+b[5]*d+0*b[9]+b[13],f=b[2]*c+b[6]*d+0*b[10]+b[14],g=b[3]*c+b[7]*d+0*b[11]+b[15];b[12]=b[0]*c+b[4]*d+0*b[8]+b[12];b[13]=e;b[14]=f;b[15]=g} +function Id(b,c,d){Bd(b,b[0]*c,b[1]*c,b[2]*c,b[3]*c,b[4]*d,b[5]*d,b[6]*d,b[7]*d,1*b[8],1*b[9],1*b[10],1*b[11],b[12],b[13],b[14],b[15])}function Jd(b,c){var d=b[0],e=b[1],f=b[2],g=b[3],h=b[4],k=b[5],n=b[6],p=b[7],q=Math.cos(c),r=Math.sin(c);b[0]=d*q+h*r;b[1]=e*q+k*r;b[2]=f*q+n*r;b[3]=g*q+p*r;b[4]=d*-r+h*q;b[5]=e*-r+k*q;b[6]=f*-r+n*q;b[7]=g*-r+p*q}new Float64Array(3);new Float64Array(3);new Float64Array(4);new Float64Array(4);new Float64Array(4);new Float64Array(16);function Kd(b){for(var c=Ld(),d=0,e=b.length;d<e;++d)Md(c,b[d]);return c}function Nd(b,c,d){var e=Math.min.apply(null,b),f=Math.min.apply(null,c);b=Math.max.apply(null,b);c=Math.max.apply(null,c);return Od(e,f,b,c,d)}function Pd(b,c,d){return m(d)?(d[0]=b[0]-c,d[1]=b[1]-c,d[2]=b[2]+c,d[3]=b[3]+c,d):[b[0]-c,b[1]-c,b[2]+c,b[3]+c]}function Qd(b,c){return m(c)?(c[0]=b[0],c[1]=b[1],c[2]=b[2],c[3]=b[3],c):b.slice()} +function Rd(b,c,d){c=c<b[0]?b[0]-c:b[2]<c?c-b[2]:0;b=d<b[1]?b[1]-d:b[3]<d?d-b[3]:0;return c*c+b*b}function Sd(b,c){return Td(b,c[0],c[1])}function Ud(b,c){return b[0]<=c[0]&&c[2]<=b[2]&&b[1]<=c[1]&&c[3]<=b[3]}function Td(b,c,d){return b[0]<=c&&c<=b[2]&&b[1]<=d&&d<=b[3]}function Vd(b,c){var d=b[1],e=b[2],f=b[3],g=c[0],h=c[1],k=0;g<b[0]?k=k|16:g>e&&(k=k|4);h<d?k|=8:h>f&&(k|=2);0===k&&(k=1);return k}function Ld(){return[Infinity,Infinity,-Infinity,-Infinity]} +function Od(b,c,d,e,f){return m(f)?(f[0]=b,f[1]=c,f[2]=d,f[3]=e,f):[b,c,d,e]}function Wd(b,c){var d=b[0],e=b[1];return Od(d,e,d,e,c)}function Xd(b,c){return b[0]==c[0]&&b[2]==c[2]&&b[1]==c[1]&&b[3]==c[3]}function Yd(b,c){c[0]<b[0]&&(b[0]=c[0]);c[2]>b[2]&&(b[2]=c[2]);c[1]<b[1]&&(b[1]=c[1]);c[3]>b[3]&&(b[3]=c[3]);return b}function Md(b,c){c[0]<b[0]&&(b[0]=c[0]);c[0]>b[2]&&(b[2]=c[0]);c[1]<b[1]&&(b[1]=c[1]);c[1]>b[3]&&(b[3]=c[1])} +function Zd(b,c,d,e,f){for(;d<e;d+=f){var g=b,h=c[d],k=c[d+1];g[0]=Math.min(g[0],h);g[1]=Math.min(g[1],k);g[2]=Math.max(g[2],h);g[3]=Math.max(g[3],k)}return b}function $d(b,c,d){var e;return(e=c.call(d,ae(b)))||(e=c.call(d,be(b)))||(e=c.call(d,ce(b)))?e:(e=c.call(d,de(b)))?e:!1}function ae(b){return[b[0],b[1]]}function be(b){return[b[2],b[1]]}function ee(b){return[(b[0]+b[2])/2,(b[1]+b[3])/2]} +function fe(b,c,d,e){var f=c*e[0]/2;e=c*e[1]/2;c=Math.cos(d);d=Math.sin(d);f=[-f,-f,f,f];e=[-e,e,-e,e];var g,h,k;for(g=0;4>g;++g)h=f[g],k=e[g],f[g]=b[0]+h*c-k*d,e[g]=b[1]+h*d+k*c;return Nd(f,e,void 0)}function ge(b){return b[3]-b[1]}function he(b,c,d){d=m(d)?d:Ld();ie(b,c)&&(d[0]=b[0]>c[0]?b[0]:c[0],d[1]=b[1]>c[1]?b[1]:c[1],d[2]=b[2]<c[2]?b[2]:c[2],d[3]=b[3]<c[3]?b[3]:c[3]);return d}function de(b){return[b[0],b[3]]}function ce(b){return[b[2],b[3]]}function je(b){return b[2]-b[0]} +function ie(b,c){return b[0]<=c[2]&&b[2]>=c[0]&&b[1]<=c[3]&&b[3]>=c[1]}function ke(b){return b[2]<b[0]||b[3]<b[1]}function le(b,c){var d=(b[2]-b[0])/2*(c-1),e=(b[3]-b[1])/2*(c-1);b[0]-=d;b[2]+=d;b[1]-=e;b[3]+=e}function me(b,c,d){b=[b[0],b[1],b[0],b[3],b[2],b[1],b[2],b[3]];c(b,b,2);return Nd([b[0],b[2],b[4],b[6]],[b[1],b[3],b[5],b[7]],d)};function ne(b){return function(){return b}}var oe=ne(!1),pe=ne(!0),qe=ne(null);function re(b){return b}function se(b){var c;c=c||0;return function(){return b.apply(this,Array.prototype.slice.call(arguments,0,c))}}function te(b){var c=arguments,d=c.length;return function(){for(var b,f=0;f<d;f++)b=c[f].apply(this,arguments);return b}}function ue(b){var c=arguments,d=c.length;return function(){for(var b=0;b<d;b++)if(!c[b].apply(this,arguments))return!1;return!0}};/* Latitude/longitude spherical geodesy formulae taken from http://www.movable-type.co.uk/scripts/latlong.html - Licenced under CC-BY-3.0. + Licensed under CC-BY-3.0. */ -function xe(b){this.radius=b}function ye(b,c){var d=bc(b[1]),e=bc(c[1]),f=(e-d)/2,g=bc(c[0]-b[0])/2,d=Math.sin(f)*Math.sin(f)+Math.sin(g)*Math.sin(g)*Math.cos(d)*Math.cos(e);return 2*ze.radius*Math.atan2(Math.sqrt(d),Math.sqrt(1-d))}xe.prototype.offset=function(b,c,d){var e=bc(b[1]);c/=this.radius;var f=Math.asin(Math.sin(e)*Math.cos(c)+Math.cos(e)*Math.sin(c)*Math.cos(d));return[180*(bc(b[0])+Math.atan2(Math.sin(d)*Math.sin(c)*Math.cos(e),Math.cos(c)-Math.sin(e)*Math.sin(f)))/Math.PI,180*f/Math.PI]};var ze=new xe(6370997);var Ae={};Ae.degrees=2*Math.PI*ze.radius/360;Ae.ft=.3048;Ae.m=1;function Be(b){this.a=b.code;this.c=b.units;this.g=m(b.extent)?b.extent:null;this.d=m(b.worldExtent)?b.worldExtent:null;this.b=m(b.axisOrientation)?b.axisOrientation:"enu";this.f=m(b.global)?b.global:!1;this.e=null}l=Be.prototype;l.gh=function(){return this.a};l.D=function(){return this.g};l.rj=function(){return this.c};l.ie=function(){return Ae[this.c]};l.Mh=function(){return this.d};function Ce(b){return b.b}l.wi=function(){return this.f}; -l.sj=function(b){this.g=b};l.pl=function(b){this.d=b};l.je=function(b,c){if("degrees"==this.c)return b;var d=De(this,Ee("EPSG:4326")),e=[c[0]-b/2,c[1],c[0]+b/2,c[1],c[0],c[1]-b/2,c[0],c[1]+b/2],e=d(e,e,2),d=(ye(e.slice(0,2),e.slice(2,4))+ye(e.slice(4,6),e.slice(6,8)))/2,e=this.ie();m(e)&&(d/=e);return d};var Fe={},Ge={};function He(b){Ie(b);Ta(b,function(c){Ta(b,function(b){c!==b&&Je(c,b,Ke)})})}function Le(){var b=Me,c=Ne,d=Oe;Ta(Pe,function(e){Ta(b,function(b){Je(e,b,c);Je(b,e,d)})})} -function Qe(b){Fe[b.a]=b;Je(b,b,Ke)}function Ie(b){var c=[];Ta(b,function(b){c.push(Qe(b))})}function Re(b){return null!=b?ia(b)?Ee(b):b:Ee("EPSG:3857")}function Je(b,c,d){b=b.a;c=c.a;b in Ge||(Ge[b]={});Ge[b][c]=d}function Se(b,c,d,e){b=Ee(b);c=Ee(c);Je(b,c,Te(d));Je(c,b,Te(e))}function Te(b){return function(c,d,e){var f=c.length;e=m(e)?e:2;d=m(d)?d:Array(f);var g,h;for(h=0;h<f;h+=e)for(g=b([c[h],c[h+1]]),d[h]=g[0],d[h+1]=g[1],g=e-1;2<=g;--g)d[h+g]=c[h+g];return d}} -function Ee(b){var c;if(b instanceof Be)c=b;else if(ia(b)){if(c=Fe[b],!m(c)&&"function"==typeof proj4){var d=proj4.defs(b);if(m(d)){c=d.units;!m(c)&&m(d.to_meter)&&(c=d.to_meter.toString(),Ae[c]=d.to_meter);c=new Be({code:b,units:c,axisOrientation:d.axis});Qe(c);var e,f,g;for(e in Fe)f=proj4.defs(e),m(f)&&(g=Ee(e),f===d?He([g,c]):(f=proj4(e,b),Se(g,c,f.forward,f.inverse)))}else c=null}}else c=null;return c}function Ue(b,c){return b===c?!0:b.c!=c.c?!1:De(b,c)===Ke} -function Ve(b,c){var d=Ee(b),e=Ee(c);return De(d,e)}function De(b,c){var d=b.a,e=c.a,f;d in Ge&&e in Ge[d]&&(f=Ge[d][e]);m(f)||(f=We);return f}function We(b,c){if(m(c)&&b!==c){for(var d=0,e=b.length;d<e;++d)c[d]=b[d];b=c}return b}function Ke(b,c){var d;if(m(c)){d=0;for(var e=b.length;d<e;++d)c[d]=b[d];d=c}else d=b.slice();return d}function Xe(b,c,d){c=Ve(c,d);return we(b,c)};function A(b){td.call(this);b=m(b)?b:{};this.j=[0,0];var c={};c.center=m(b.center)?b.center:null;this.q=Re(b.projection);var d,e,f,g=m(b.minZoom)?b.minZoom:0;d=m(b.maxZoom)?b.maxZoom:28;var h=m(b.zoomFactor)?b.zoomFactor:2;if(m(b.resolutions))d=b.resolutions,e=d[0],f=d[d.length-1],d=fc(d);else{e=Re(b.projection);f=e.D();var k=(null===f?360*Ae.degrees/Ae[e.c]:Math.max(re(f),oe(f)))/256/Math.pow(2,0),n=k/Math.pow(2,28);e=b.maxResolution;m(e)?g=0:e=k/Math.pow(h,g);f=b.minResolution;m(f)||(f=m(b.maxZoom)? -m(b.maxResolution)?e/Math.pow(h,d):k/Math.pow(h,d):n);d=g+Math.floor(Math.log(e/f)/Math.log(h));f=e/Math.pow(h,d-g);d=gc(h,e,d-g)}this.f=e;this.F=f;this.p=g;g=m(b.extent)?cc(b.extent):dc;(m(b.enableRotation)?b.enableRotation:1)?(e=b.constrainRotation,e=m(e)&&!0!==e?!1===e?ic:ja(e)?jc(e):ic:kc()):e=hc;this.t=new lc(g,d,e);m(b.resolution)?c.resolution=b.resolution:m(b.zoom)&&(c.resolution=this.constrainResolution(this.f,b.zoom-this.p));c.rotation=m(b.rotation)?b.rotation:0;this.G(c)}u(A,td); -A.prototype.i=function(b){return this.t.center(b)};A.prototype.constrainResolution=function(b,c,d){return this.t.resolution(b,c||0,d||0)};A.prototype.constrainRotation=function(b,c){return this.t.rotation(b,c||0)};A.prototype.a=function(){return this.get("center")};A.prototype.getCenter=A.prototype.a;A.prototype.g=function(b){var c=this.a(),d=this.b();return[c[0]-d*b[0]/2,c[1]-d*b[1]/2,c[0]+d*b[0]/2,c[1]+d*b[1]/2]};A.prototype.J=function(){return this.q};A.prototype.b=function(){return this.get("resolution")}; -A.prototype.getResolution=A.prototype.b;A.prototype.n=function(b,c){return Math.max(re(b)/c[0],oe(b)/c[1])};function Ye(b){var c=b.f,d=Math.log(c/b.F)/Math.log(2);return function(b){return c/Math.pow(2,b*d)}}A.prototype.e=function(){return this.get("rotation")};A.prototype.getRotation=A.prototype.e;function Ze(b){var c=b.f,d=Math.log(c/b.F)/Math.log(2);return function(b){return Math.log(c/b)/Math.log(2)/d}} -function $e(b){var c=b.a(),d=b.q,e=b.b();b=b.e();return{center:c.slice(),projection:m(d)?d:null,resolution:e,rotation:m(b)?b:0}}l=A.prototype;l.Oh=function(){var b,c=this.b();if(m(c)){var d,e=0;do{d=this.constrainResolution(this.f,e);if(d==c){b=e;break}++e}while(d>this.F)}return m(b)?this.p+b:b};l.ge=function(b,c){if(!se(b)){this.Oa(ke(b));var d=this.n(b,c),e=this.constrainResolution(d,0,0);e<d&&(e=this.constrainResolution(e,-1,0));this.d(e)}}; -l.dh=function(b,c,d){var e=m(d)?d:{};d=m(e.padding)?e.padding:[0,0,0,0];var f=m(e.constrainResolution)?e.constrainResolution:!0,g=m(e.nearest)?e.nearest:!1,h;m(e.minResolution)?h=e.minResolution:m(e.maxZoom)?h=this.constrainResolution(this.f,e.maxZoom-this.p,0):h=0;var k=b.k,n=this.e(),e=Math.cos(-n),n=Math.sin(-n),p=Infinity,q=Infinity,r=-Infinity,s=-Infinity;b=b.s;for(var v=0,y=k.length;v<y;v+=b)var C=k[v]*e-k[v+1]*n,F=k[v]*n+k[v+1]*e,p=Math.min(p,C),q=Math.min(q,F),r=Math.max(r,C),s=Math.max(s, -F);c=this.n([p,q,r,s],[c[0]-d[1]-d[3],c[1]-d[0]-d[2]]);c=isNaN(c)?h:Math.max(c,h);f&&(h=this.constrainResolution(c,0,0),!g&&h<c&&(h=this.constrainResolution(h,-1,0)),c=h);this.d(c);n=-n;g=(p+r)/2+(d[1]-d[3])/2*c;d=(q+s)/2+(d[0]-d[2])/2*c;this.Oa([g*e-d*n,d*e+g*n])};l.Yg=function(b,c,d){var e=this.e(),f=Math.cos(-e),e=Math.sin(-e),g=b[0]*f-b[1]*e;b=b[1]*f+b[0]*e;var h=this.b(),g=g+(c[0]/2-d[0])*h;b+=(d[1]-c[1]/2)*h;e=-e;this.Oa([g*f-b*e,b*f+g*e])};function af(b){return null!=b.a()&&m(b.b())} -l.rotate=function(b,c){if(m(c)){var d,e=this.a();m(e)&&(d=[e[0]-c[0],e[1]-c[1]],Dd(d,b-this.e()),yd(d,c));this.Oa(d)}this.r(b)};l.Oa=function(b){this.set("center",b)};A.prototype.setCenter=A.prototype.Oa;function bf(b,c){b.j[1]+=c}A.prototype.d=function(b){this.set("resolution",b)};A.prototype.setResolution=A.prototype.d;A.prototype.r=function(b){this.set("rotation",b)};A.prototype.setRotation=A.prototype.r;A.prototype.Q=function(b){b=this.constrainResolution(this.f,b-this.p,0);this.d(b)};function cf(b){return 1-Math.pow(1-b,3)};function df(b){return 3*b*b-2*b*b*b}function ef(b){return b}function ff(b){return.5>b?df(2*b):1-df(2*(b-.5))};function gf(b){var c=b.source,d=m(b.start)?b.start:ua(),e=c[0],f=c[1],g=m(b.duration)?b.duration:1E3,h=m(b.easing)?b.easing:df;return function(b,c){if(c.time<d)return c.animate=!0,c.viewHints[0]+=1,!0;if(c.time<d+g){var p=1-h((c.time-d)/g),q=e-c.viewState.center[0],r=f-c.viewState.center[1];c.animate=!0;c.viewState.center[0]+=p*q;c.viewState.center[1]+=p*r;c.viewHints[0]+=1;return!0}return!1}} -function hf(b){var c=m(b.rotation)?b.rotation:0,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:df,g=m(b.anchor)?b.anchor:null;return function(b,k){if(k.time<d)return k.animate=!0,k.viewHints[0]+=1,!0;if(k.time<d+e){var n=1-f((k.time-d)/e),n=(c-k.viewState.rotation)*n;k.animate=!0;k.viewState.rotation+=n;if(null!==g){var p=k.viewState.center;p[0]-=g[0];p[1]-=g[1];Dd(p,n);yd(p,g)}k.viewHints[0]+=1;return!0}return!1}} -function jf(b){var c=b.resolution,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:df;return function(b,h){if(h.time<d)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<d+e){var k=1-f((h.time-d)/e),n=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*n;h.viewHints[0]+=1;return!0}return!1}};function kf(b,c,d,e){return m(e)?(e[0]=b,e[1]=c,e[2]=d,e):[b,c,d]}function lf(b,c,d){return b+"/"+c+"/"+d}function mf(b){var c=b[0],d=Array(c),e=1<<c-1,f,g;for(f=0;f<c;++f)g=48,b[1]&e&&(g+=1),b[2]&e&&(g+=2),d[f]=String.fromCharCode(g),e>>=1;return d.join("")}function nf(b){return lf(b[0],b[1],b[2])};function of(b,c,d,e){this.a=b;this.d=c;this.b=d;this.c=e}function pf(b,c,d,e,f){return m(f)?(f.a=b,f.d=c,f.b=d,f.c=e,f):new of(b,c,d,e)}of.prototype.contains=function(b){return qf(this,b[1],b[2])};function rf(b,c){return b.a<=c.a&&c.d<=b.d&&b.b<=c.b&&c.c<=b.c}function qf(b,c,d){return b.a<=c&&c<=b.d&&b.b<=d&&d<=b.c}function sf(b,c){return b.a==c.a&&b.b==c.b&&b.d==c.d&&b.c==c.c};function tf(b){this.c=b.html;this.a=m(b.tileRanges)?b.tileRanges:null}tf.prototype.b=function(){return this.c};var uf=!Hb||Hb&&9<=Vb;!Ib&&!Hb||Hb&&Hb&&9<=Vb||Ib&&Tb("1.9.1");Hb&&Tb("9");Fb("area base br col command embed hr img input keygen link meta param source track wbr".split(" "));Fb("action","cite","data","formaction","href","manifest","poster","src");Fb("embed","iframe","link","object","script","style","template");function vf(b,c){this.x=m(b)?b:0;this.y=m(c)?c:0}l=vf.prototype;l.clone=function(){return new vf(this.x,this.y)};l.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};l.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};l.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};l.scale=function(b,c){var d=ja(c)?c:b;this.x*=b;this.y*=d;return this};function wf(b,c){this.width=b;this.height=c}l=wf.prototype;l.clone=function(){return new wf(this.width,this.height)};l.ia=function(){return!(this.width*this.height)};l.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};l.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this}; -l.scale=function(b,c){var d=ja(c)?c:b;this.width*=b;this.height*=d;return this};function xf(b){return b?new yf(zf(b)):ya||(ya=new yf)}function Af(b){var c=document;return ia(b)?c.getElementById(b):b}function Cf(b,c){ob(c,function(c,e){"style"==e?b.style.cssText=c:"class"==e?b.className=c:"for"==e?b.htmlFor=c:e in Df?b.setAttribute(Df[e],c):0==e.lastIndexOf("aria-",0)||0==e.lastIndexOf("data-",0)?b.setAttribute(e,c):b[e]=c})} -var Df={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};function Ef(b){b=b.document.documentElement;return new wf(b.clientWidth,b.clientHeight)} -function Ff(b,c,d){var e=arguments,f=document,g=e[0],h=e[1];if(!uf&&h&&(h.name||h.type)){g=["<",g];h.name&&g.push(' name="',Ba(h.name),'"');if(h.type){g.push(' type="',Ba(h.type),'"');var k={};Eb(k,h);delete k.type;h=k}g.push(">");g=g.join("")}g=f.createElement(g);h&&(ia(h)?g.className=h:ga(h)?g.className=h.join(" "):Cf(g,h));2<e.length&&Gf(f,g,e,2);return g} -function Gf(b,c,d,e){function f(d){d&&c.appendChild(ia(d)?b.createTextNode(d):d)}for(;e<d.length;e++){var g=d[e];!ha(g)||la(g)&&0<g.nodeType?f(g):Ta(Hf(g)?cb(g):g,f)}}function If(b){return document.createElement(b)}function Jf(b,c){Gf(zf(b),b,arguments,1)}function Kf(b){for(var c;c=b.firstChild;)b.removeChild(c)}function Lf(b,c){c.parentNode&&c.parentNode.insertBefore(b,c.nextSibling)}function Mf(b,c,d){b.insertBefore(c,b.childNodes[d]||null)} -function Nf(b){b&&b.parentNode&&b.parentNode.removeChild(b)}function Of(b){if(void 0!=b.firstElementChild)b=b.firstElementChild;else for(b=b.firstChild;b&&1!=b.nodeType;)b=b.nextSibling;return b}function Pf(b,c){if(b.contains&&1==c.nodeType)return b==c||b.contains(c);if("undefined"!=typeof b.compareDocumentPosition)return b==c||Boolean(b.compareDocumentPosition(c)&16);for(;c&&b!=c;)c=c.parentNode;return c==b}function zf(b){return 9==b.nodeType?b:b.ownerDocument||b.document} -function Qf(b,c){if("textContent"in b)b.textContent=c;else if(3==b.nodeType)b.data=c;else if(b.firstChild&&3==b.firstChild.nodeType){for(;b.lastChild!=b.firstChild;)b.removeChild(b.lastChild);b.firstChild.data=c}else Kf(b),b.appendChild(zf(b).createTextNode(String(c)))}function Hf(b){if(b&&"number"==typeof b.length){if(la(b))return"function"==typeof b.item||"string"==typeof b.item;if(ka(b))return"function"==typeof b.item}return!1}function yf(b){this.a=b||ba.document||document} -function Rf(){return!0}function Sf(b){var c=b.a;b=Jb?c.body||c.documentElement:c.documentElement;c=c.parentWindow||c.defaultView;return Hb&&Tb("10")&&c.pageYOffset!=b.scrollTop?new vf(b.scrollLeft,b.scrollTop):new vf(c.pageXOffset||b.scrollLeft,c.pageYOffset||b.scrollTop)}yf.prototype.appendChild=function(b,c){b.appendChild(c)};yf.prototype.contains=Pf;function Tf(b,c){var d=If("CANVAS");m(b)&&(d.width=b);m(c)&&(d.height=c);return d.getContext("2d")} -var Uf=function(){var b;return function(){if(!m(b))if(ba.getComputedStyle){var c=If("P"),d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate(1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Nf(c);b=d&&"none"!==d}else b=!1;return b}}(),Vf=function(){var b;return function(){if(!m(b))if(ba.getComputedStyle){var c=If("P"), -d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate3d(1px,1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Nf(c);b=d&&"none"!==d}else b=!1;return b}}();function Wf(b,c){var d=b.style;d.WebkitTransform=c;d.MozTransform=c;d.a=c;d.msTransform=c;d.transform=c;Hb&&!Xb&&(b.style.transformOrigin="0 0")} -function Xf(b,c){var d;if(Vf()){if(m(6)){var e=Array(16);for(d=0;16>d;++d)e[d]=c[d].toFixed(6);d=e.join(",")}else d=c.join(",");Wf(b,"matrix3d("+d+")")}else if(Uf()){e=[c[0],c[1],c[4],c[5],c[12],c[13]];if(m(6)){var f=Array(6);for(d=0;6>d;++d)f[d]=e[d].toFixed(6);d=f.join(",")}else d=e.join(",");Wf(b,"matrix("+d+")")}else b.style.left=Math.round(c[12])+"px",b.style.top=Math.round(c[13])+"px"};var Yf=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function Zf(b,c){var d,e,f=Yf.length;for(e=0;e<f;++e)try{if(d=b.getContext(Yf[e],c),null!==d)return d}catch(g){}return null};var $f,ag=ba.devicePixelRatio||1,bg="ArrayBuffer"in ba,cg=!1,dg=function(){if(!("HTMLCanvasElement"in ba))return!1;try{var b=Tf();if(null===b)return!1;m(b.setLineDash)&&(cg=!0);return!0}catch(c){return!1}}(),eg="DeviceOrientationEvent"in ba,fg="geolocation"in ba.navigator,gg="ontouchstart"in ba,hg="PointerEvent"in ba,ig=!!ba.navigator.msPointerEnabled,jg=!1,kg,lg=[]; -if("WebGLRenderingContext"in ba)try{var mg=If("CANVAS"),ng=Zf(mg,{bh:!0});null!==ng&&(jg=!0,kg=ng.getParameter(ng.MAX_TEXTURE_SIZE),lg=ng.getSupportedExtensions())}catch(og){}$f=jg;wa=lg;va=kg;function pg(b,c,d){uc.call(this,b,d);this.element=c}u(pg,uc);function B(b){td.call(this);this.a=b||[];qg(this)}u(B,td);l=B.prototype;l.clear=function(){for(;0<this.Ib();)this.pop()};l.qf=function(b){var c,d;c=0;for(d=b.length;c<d;++c)this.push(b[c]);return this};l.forEach=function(b,c){Ta(this.a,b,c)};l.Mi=function(){return this.a};l.item=function(b){return this.a[b]};l.Ib=function(){return this.get("length")};l.rd=function(b,c){eb(this.a,b,0,c);qg(this);this.dispatchEvent(new pg("add",c,this))}; -l.pop=function(){return this.De(this.Ib()-1)};l.push=function(b){var c=this.a.length;this.rd(c,b);return c};l.remove=function(b){var c=this.a,d,e;d=0;for(e=c.length;d<e;++d)if(c[d]===b)return this.De(d)};l.De=function(b){var c=this.a[b];Sa.splice.call(this.a,b,1);qg(this);this.dispatchEvent(new pg("remove",c,this));return c}; -l.bl=function(b,c){var d=this.Ib();if(b<d)d=this.a[b],this.a[b]=c,this.dispatchEvent(new pg("remove",d,this)),this.dispatchEvent(new pg("add",c,this));else{for(;d<b;++d)this.rd(d,void 0);this.rd(b,c)}};function qg(b){b.set("length",b.a.length)};var rg=/^#(?:[0-9a-f]{3}){1,2}$/i,sg=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i,tg=/^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;function ug(b){return ga(b)?b:vg(b)}function wg(b){if(!ia(b)){var c=b[0];c!=(c|0)&&(c=c+.5|0);var d=b[1];d!=(d|0)&&(d=d+.5|0);var e=b[2];e!=(e|0)&&(e=e+.5|0);b="rgba("+c+","+d+","+e+","+b[3]+")"}return b} -var vg=function(){var b={},c=0;return function(d){var e;if(b.hasOwnProperty(d))e=b[d];else{if(1024<=c){e=0;for(var f in b)0===(e++&3)&&(delete b[f],--c)}var g,h;rg.exec(d)?(h=3==d.length-1?1:2,e=parseInt(d.substr(1+0*h,h),16),f=parseInt(d.substr(1+1*h,h),16),g=parseInt(d.substr(1+2*h,h),16),1==h&&(e=(e<<4)+e,f=(f<<4)+f,g=(g<<4)+g),e=[e,f,g,1]):(h=tg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),h=Number(h[4]),e=[e,f,g,h],e=xg(e,e)):(h=sg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]), -e=[e,f,g,1],e=xg(e,e)):e=void 0;b[d]=e;++c}return e}}();function xg(b,c){var d=m(c)?c:[];d[0]=Yb(b[0]+.5|0,0,255);d[1]=Yb(b[1]+.5|0,0,255);d[2]=Yb(b[2]+.5|0,0,255);d[3]=Yb(b[3],0,1);return d};function yg(){this.g=Kd();this.c=void 0;this.a=Kd();this.d=void 0;this.b=Kd();this.f=void 0;this.e=Kd();this.i=void 0;this.o=Kd()} -function zg(b,c,d,e,f){var g=!1;m(c)&&c!==b.c&&(g=b.a,Od(g),g[12]=c,g[13]=c,g[14]=c,g[15]=1,b.c=c,g=!0);if(m(d)&&d!==b.d){g=b.b;Od(g);g[0]=d;g[5]=d;g[10]=d;g[15]=1;var h=-.5*d+.5;g[12]=h;g[13]=h;g[14]=h;g[15]=1;b.d=d;g=!0}m(e)&&e!==b.f&&(g=Math.cos(e),h=Math.sin(e),Ld(b.e,.213+.787*g-.213*h,.213-.213*g+.143*h,.213-.213*g-.787*h,0,.715-.715*g-.715*h,.715+.285*g+.14*h,.715-.715*g+.715*h,0,.072-.072*g+.928*h,.072-.072*g-.283*h,.072+.928*g+.072*h,0,0,0,0,1),b.f=e,g=!0);m(f)&&f!==b.i&&(Ld(b.o,.213+.787* -f,.213-.213*f,.213-.213*f,0,.715-.715*f,.715+.285*f,.715-.715*f,0,.072-.072*f,.072-.072*f,.072+.928*f,0,0,0,0,1),b.i=f,g=!0);g&&(g=b.g,Od(g),m(d)&&Pd(g,b.b,g),m(c)&&Pd(g,b.a,g),m(f)&&Pd(g,b.o,g),m(e)&&Pd(g,b.e,g));return b.g};function Ag(b){if(b.classList)return b.classList;b=b.className;return ia(b)&&b.match(/\S+/g)||[]}function Bg(b,c){return b.classList?b.classList.contains(c):Za(Ag(b),c)}function Cg(b,c){b.classList?b.classList.add(c):Bg(b,c)||(b.className+=0<b.className.length?" "+c:c)}function Dg(b,c){b.classList?b.classList.remove(c):Bg(b,c)&&(b.className=Ua(Ag(b),function(b){return b!=c}).join(" "))}function Eg(b,c){Bg(b,c)?Dg(b,c):Cg(b,c)};function Fg(b,c,d,e){this.top=b;this.right=c;this.bottom=d;this.left=e}l=Fg.prototype;l.clone=function(){return new Fg(this.top,this.right,this.bottom,this.left)};l.contains=function(b){return this&&b?b instanceof Fg?b.left>=this.left&&b.right<=this.right&&b.top>=this.top&&b.bottom<=this.bottom:b.x>=this.left&&b.x<=this.right&&b.y>=this.top&&b.y<=this.bottom:!1}; +function ve(b){this.radius=b}ve.prototype.b=function(b){for(var c=0,d=b.length,e=b[d-1][0],f=b[d-1][1],g=0;g<d;g++)var h=b[g][0],k=b[g][1],c=c+Zb(h-e)*(2+Math.sin(Zb(f))+Math.sin(Zb(k))),e=h,f=k;return c*this.radius*this.radius/2};ve.prototype.a=function(b,c){var d=Zb(b[1]),e=Zb(c[1]),f=(e-d)/2,g=Zb(c[0]-b[0])/2,d=Math.sin(f)*Math.sin(f)+Math.sin(g)*Math.sin(g)*Math.cos(d)*Math.cos(e);return 2*this.radius*Math.atan2(Math.sqrt(d),Math.sqrt(1-d))}; +ve.prototype.offset=function(b,c,d){var e=Zb(b[1]);c/=this.radius;var f=Math.asin(Math.sin(e)*Math.cos(c)+Math.cos(e)*Math.sin(c)*Math.cos(d));return[180*(Zb(b[0])+Math.atan2(Math.sin(d)*Math.sin(c)*Math.cos(e),Math.cos(c)-Math.sin(e)*Math.sin(f)))/Math.PI,180*f/Math.PI]};var we=new ve(6370997);var xe={};xe.degrees=2*Math.PI*we.radius/360;xe.ft=.3048;xe.m=1;xe["us-ft"]=1200/3937; +function ye(b){this.a=b.code;this.b=b.units;this.i=m(b.extent)?b.extent:null;this.j=m(b.worldExtent)?b.worldExtent:null;this.f=m(b.axisOrientation)?b.axisOrientation:"enu";this.g=(this.c=m(b.global)?b.global:!1)&&null!==this.i;this.B=m(b.getPointResolution)?b.getPointResolution:this.Jj;this.l=null;if("function"==typeof proj4){var c=b.code,d=proj4.defs(c);if(m(d)){m(d.axis)&&!m(b.axisOrientation)&&(this.f=d.axis);m(b.units)||(b=d.units,!m(b)&&m(d.to_meter)&&(b=d.to_meter.toString(),xe[b]=d.to_meter), +this.b=b);b=ze;var e,f;for(e in b)f=proj4.defs(e),m(f)&&(b=Ae(e),f===d?Be([b,this]):(f=proj4(e,c),Ce(b,this,f.forward,f.inverse)))}}}l=ye.prototype;l.jj=function(){return this.a};l.R=function(){return this.i};l.Yl=function(){return this.b};l.Vd=function(){return xe[this.b]};l.Uj=function(){return this.j};function De(b){return b.f}l.Ik=function(){return this.c};l.po=function(b){this.g=(this.c=b)&&null!==this.i};l.Zl=function(b){this.i=b;this.g=this.c&&null!==b};l.Bo=function(b){this.j=b}; +l.oo=function(b){this.B=b};l.Jj=function(b,c){if("degrees"==this.b)return b;var d=Ee(this,Ae("EPSG:4326")),e=[c[0]-b/2,c[1],c[0]+b/2,c[1],c[0],c[1]-b/2,c[0],c[1]+b/2],e=d(e,e,2),d=(we.a(e.slice(0,2),e.slice(2,4))+we.a(e.slice(4,6),e.slice(6,8)))/2,e=this.Vd();m(e)&&(d/=e);return d};l.getPointResolution=function(b,c){return this.B(b,c)};var ze={},Fe={};function Be(b){Ge(b);Sa(b,function(c){Sa(b,function(b){c!==b&&He(c,b,Ie)})})} +function Je(){var b=Ke,c=Le,d=Me;Sa(Ne,function(e){Sa(b,function(b){He(e,b,c);He(b,e,d)})})}function Oe(b){ze[b.a]=b;He(b,b,Ie)}function Ge(b){var c=[];Sa(b,function(b){c.push(Oe(b))})}function Pe(b){return null!=b?ia(b)?Ae(b):b:Ae("EPSG:3857")}function He(b,c,d){b=b.a;c=c.a;b in Fe||(Fe[b]={});Fe[b][c]=d}function Ce(b,c,d,e){b=Ae(b);c=Ae(c);He(b,c,Re(d));He(c,b,Re(e))} +function Re(b){return function(c,d,e){var f=c.length;e=m(e)?e:2;d=m(d)?d:Array(f);var g,h;for(h=0;h<f;h+=e)for(g=b([c[h],c[h+1]]),d[h]=g[0],d[h+1]=g[1],g=e-1;2<=g;--g)d[h+g]=c[h+g];return d}}function Ae(b){var c;b instanceof ye?c=b:ia(b)?(c=ze[b],!m(c)&&"function"==typeof proj4&&m(proj4.defs(b))&&(c=new ye({code:b}),Oe(c))):c=null;return c}function Se(b,c){return b===c?!0:b.a===c.a?!0:b.b!=c.b?!1:Ee(b,c)===Ie}function Te(b,c){var d=Ae(b),e=Ae(c);return Ee(d,e)} +function Ee(b,c){var d=b.a,e=c.a,f;d in Fe&&e in Fe[d]&&(f=Fe[d][e]);m(f)||(f=Ue);return f}function Ue(b,c){if(m(c)&&b!==c){for(var d=0,e=b.length;d<e;++d)c[d]=b[d];b=c}return b}function Ie(b,c){var d;if(m(c)){d=0;for(var e=b.length;d<e;++d)c[d]=b[d];d=c}else d=b.slice();return d}function Ve(b,c,d){return Te(c,d)(b,void 0,b.length)}function We(b,c,d){c=Te(c,d);return me(b,c)};function Xe(){gd.call(this);this.v=Ld();this.A=-1;this.g={};this.l=this.i=0}w(Xe,gd);l=Xe.prototype;l.$a=function(b,c){var d=m(c)?c:[NaN,NaN];this.Ya(b[0],b[1],d,Infinity);return d};l.Ye=function(b){return this.ic(b[0],b[1])};l.ic=oe;l.R=function(b){this.A!=this.a&&(this.v=this.Md(this.v),this.A=this.a);var c=this.v;m(b)?(b[0]=c[0],b[1]=c[1],b[2]=c[2],b[3]=c[3]):b=c;return b};l.transform=function(b,c){this.va(Te(b,c));return this};function Ye(b,c,d,e,f,g){var h=f[0],k=f[1],n=f[4],p=f[5],q=f[12];f=f[13];for(var r=m(g)?g:[],t=0;c<d;c+=e){var v=b[c],B=b[c+1];r[t++]=h*v+n*B+q;r[t++]=k*v+p*B+f}m(g)&&r.length!=t&&(r.length=t);return r};function Ze(){Xe.call(this);this.b="XY";this.H=2;this.o=null}w(Ze,Xe);function $e(b){if("XY"==b)return 2;if("XYZ"==b||"XYM"==b)return 3;if("XYZM"==b)return 4}l=Ze.prototype;l.ic=oe;l.Md=function(b){var c=this.o,d=this.o.length,e=this.H;b=Od(Infinity,Infinity,-Infinity,-Infinity,b);return Zd(b,c,0,d,e)};l.Bb=function(){return this.o.slice(0,this.H)};l.Cb=function(){return this.o.slice(this.o.length-this.H)};l.Db=function(){return this.b}; +l.mf=function(b){this.l!=this.a&&(yb(this.g),this.i=0,this.l=this.a);if(0>b||0!==this.i&&b<=this.i)return this;var c=b.toString();if(this.g.hasOwnProperty(c))return this.g[c];var d=this.Gc(b);if(d.o.length<this.o.length)return this.g[c]=d;this.i=b;return this};l.Gc=function(){return this};function af(b,c,d){b.H=$e(c);b.b=c;b.o=d} +function bf(b,c,d,e){if(m(c))d=$e(c);else{for(c=0;c<e;++c){if(0===d.length){b.b="XY";b.H=2;return}d=d[0]}d=d.length;c=2==d?"XY":3==d?"XYZ":4==d?"XYZM":void 0}b.b=c;b.H=d}l.va=function(b){null!==this.o&&(b(this.o,this.o,this.H),this.s())};l.Ua=function(b,c){var d=this.o;if(null!==d){var e=d.length,f=this.H,g=m(d)?d:[],h=0,k,n;for(k=0;k<e;k+=f)for(g[h++]=d[k]+b,g[h++]=d[k+1]+c,n=k+2;n<k+f;++n)g[h++]=d[n];m(d)&&g.length!=h&&(g.length=h);this.s()}};function cf(b,c,d,e){for(var f=0,g=b[d-e],h=b[d-e+1];c<d;c+=e)var k=b[c],n=b[c+1],f=f+(h*k-g*n),g=k,h=n;return f/2}function df(b,c,d,e){var f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],f=f+cf(b,c,k,e);c=k}return f};function ef(b,c,d,e,f,g){var h=f-d,k=g-e;if(0!==h||0!==k){var n=((b-d)*h+(c-e)*k)/(h*h+k*k);1<n?(d=f,e=g):0<n&&(d+=h*n,e+=k*n)}return ff(b,c,d,e)}function ff(b,c,d,e){b=d-b;c=e-c;return b*b+c*c};function gf(b,c,d,e,f,g,h){var k=b[c],n=b[c+1],p=b[d]-k,q=b[d+1]-n;if(0!==p||0!==q)if(g=((f-k)*p+(g-n)*q)/(p*p+q*q),1<g)c=d;else if(0<g){for(f=0;f<e;++f)h[f]=Yb(b[c+f],b[d+f],g);h.length=e;return}for(f=0;f<e;++f)h[f]=b[c+f];h.length=e}function hf(b,c,d,e,f){var g=b[c],h=b[c+1];for(c+=e;c<d;c+=e){var k=b[c],n=b[c+1],g=ff(g,h,k,n);g>f&&(f=g);g=k;h=n}return f}function jf(b,c,d,e,f){var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];f=hf(b,c,k,e,f);c=k}return f} +function kf(b,c,d,e,f,g,h,k,n,p,q){if(c==d)return p;var r;if(0===f){r=ff(h,k,b[c],b[c+1]);if(r<p){for(q=0;q<e;++q)n[q]=b[c+q];n.length=e;return r}return p}for(var t=m(q)?q:[NaN,NaN],v=c+e;v<d;)if(gf(b,v-e,v,e,h,k,t),r=ff(h,k,t[0],t[1]),r<p){p=r;for(q=0;q<e;++q)n[q]=t[q];n.length=e;v+=e}else v+=e*Math.max((Math.sqrt(r)-Math.sqrt(p))/f|0,1);if(g&&(gf(b,d-e,c,e,h,k,t),r=ff(h,k,t[0],t[1]),r<p)){p=r;for(q=0;q<e;++q)n[q]=t[q];n.length=e}return p} +function lf(b,c,d,e,f,g,h,k,n,p,q){q=m(q)?q:[NaN,NaN];var r,t;r=0;for(t=d.length;r<t;++r){var v=d[r];p=kf(b,c,v,e,f,g,h,k,n,p,q);c=v}return p};function mf(b,c){var d=0,e,f;e=0;for(f=c.length;e<f;++e)b[d++]=c[e];return d}function nf(b,c,d,e){var f,g;f=0;for(g=d.length;f<g;++f){var h=d[f],k;for(k=0;k<e;++k)b[c++]=h[k]}return c}function of(b,c,d,e,f){f=m(f)?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h)c=nf(b,c,d[h],e),f[g++]=c;f.length=g;return f};function pf(b,c,d,e,f){f=m(f)?f:[];for(var g=0;c<d;c+=e)f[g++]=b.slice(c,c+e);f.length=g;return f}function qf(b,c,d,e,f){f=m(f)?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h){var n=d[h];f[g++]=pf(b,c,n,e,f[g]);c=n}f.length=g;return f};function rf(b,c,d,e,f,g,h){var k=(d-c)/e;if(3>k){for(;c<d;c+=e)g[h++]=b[c],g[h++]=b[c+1];return h}var n=Array(k);n[0]=1;n[k-1]=1;d=[c,d-e];for(var p=0,q;0<d.length;){var r=d.pop(),t=d.pop(),v=0,B=b[t],z=b[t+1],E=b[r],A=b[r+1];for(q=t+e;q<r;q+=e){var y=ef(b[q],b[q+1],B,z,E,A);y>v&&(p=q,v=y)}v>f&&(n[(p-c)/e]=1,t+e<p&&d.push(t,p),p+e<r&&d.push(p,r))}for(q=0;q<k;++q)n[q]&&(g[h++]=b[c+q*e],g[h++]=b[c+q*e+1]);return h} +function sf(b,c,d,e,f,g,h,k){var n,p;n=0;for(p=d.length;n<p;++n){var q=d[n];a:{var r=b,t=q,v=e,B=f,z=g;if(c!=t){var E=B*Math.round(r[c]/B),A=B*Math.round(r[c+1]/B);c+=v;z[h++]=E;z[h++]=A;var y=void 0,J=void 0;do if(y=B*Math.round(r[c]/B),J=B*Math.round(r[c+1]/B),c+=v,c==t){z[h++]=y;z[h++]=J;break a}while(y==E&&J==A);for(;c<t;){var L,H;L=B*Math.round(r[c]/B);H=B*Math.round(r[c+1]/B);c+=v;if(L!=y||H!=J){var S=y-E,ta=J-A,Pa=L-E,R=H-A;S*R==ta*Pa&&(0>S&&Pa<S||S==Pa||0<S&&Pa>S)&&(0>ta&&R<ta||ta==R||0<ta&& +R>ta)||(z[h++]=y,z[h++]=J,E=y,A=J);y=L;J=H}}z[h++]=y;z[h++]=J}}k.push(h);c=q}return h};function tf(b,c){Ze.call(this);this.c=this.j=-1;this.ja(b,c)}w(tf,Ze);l=tf.prototype;l.clone=function(){var b=new tf(null);uf(b,this.b,this.o.slice());return b};l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;this.c!=this.a&&(this.j=Math.sqrt(hf(this.o,0,this.o.length,this.H,0)),this.c=this.a);return kf(this.o,0,this.o.length,this.H,this.j,!0,b,c,d,e)};l.El=function(){return cf(this.o,0,this.o.length,this.H)};l.W=function(){return pf(this.o,0,this.o.length,this.H)}; +l.Gc=function(b){var c=[];c.length=rf(this.o,0,this.o.length,this.H,b,c,0);b=new tf(null);uf(b,"XY",c);return b};l.V=function(){return"LinearRing"};l.ja=function(b,c){null===b?uf(this,"XY",null):(bf(this,c,b,1),null===this.o&&(this.o=[]),this.o.length=nf(this.o,0,b,this.H),this.s())};function uf(b,c,d){af(b,c,d);b.s()};function D(b,c){Ze.call(this);this.ja(b,c)}w(D,Ze);l=D.prototype;l.clone=function(){var b=new D(null);vf(b,this.b,this.o.slice());return b};l.Ya=function(b,c,d,e){var f=this.o;b=ff(b,c,f[0],f[1]);if(b<e){e=this.H;for(c=0;c<e;++c)d[c]=f[c];d.length=e;return b}return e};l.W=function(){return null===this.o?[]:this.o.slice()};l.Md=function(b){return Wd(this.o,b)};l.V=function(){return"Point"};l.sa=function(b){return Td(b,this.o[0],this.o[1])}; +l.ja=function(b,c){null===b?vf(this,"XY",null):(bf(this,c,b,0),null===this.o&&(this.o=[]),this.o.length=mf(this.o,b),this.s())};function vf(b,c,d){af(b,c,d);b.s()};function wf(b,c,d,e,f){return!$d(f,function(f){return!xf(b,c,d,e,f[0],f[1])})}function xf(b,c,d,e,f,g){for(var h=!1,k=b[d-e],n=b[d-e+1];c<d;c+=e){var p=b[c],q=b[c+1];n>g!=q>g&&f<(p-k)*(g-n)/(q-n)+k&&(h=!h);k=p;n=q}return h}function yf(b,c,d,e,f,g){if(0===d.length||!xf(b,c,d[0],e,f,g))return!1;var h;c=1;for(h=d.length;c<h;++c)if(xf(b,d[c-1],d[c],e,f,g))return!1;return!0};function zf(b,c,d,e,f,g,h){var k,n,p,q,r,t=f[g+1],v=[],B=d[0];p=b[B-e];r=b[B-e+1];for(k=c;k<B;k+=e){q=b[k];n=b[k+1];if(t<=r&&n<=t||r<=t&&t<=n)p=(t-r)/(n-r)*(q-p)+p,v.push(p);p=q;r=n}B=NaN;r=-Infinity;v.sort();p=v[0];k=1;for(n=v.length;k<n;++k){q=v[k];var z=Math.abs(q-p);z>r&&(p=(p+q)/2,yf(b,c,d,e,p,t)&&(B=p,r=z));p=q}isNaN(B)&&(B=f[g]);return m(h)?(h.push(B,t),h):[B,t]};function Af(b,c,d,e,f,g){for(var h=[b[c],b[c+1]],k=[],n;c+e<d;c+=e){k[0]=b[c+e];k[1]=b[c+e+1];if(n=f.call(g,h,k))return n;h[0]=k[0];h[1]=k[1]}return!1};function Bf(b,c,d,e,f){var g=Zd(Ld(),b,c,d,e);return ie(f,g)?Ud(f,g)||g[0]>=f[0]&&g[2]<=f[2]||g[1]>=f[1]&&g[3]<=f[3]?!0:Af(b,c,d,e,function(b,c){var d=!1,e=Vd(f,b),g=Vd(f,c);if(1===e||1===g)d=!0;else{var r=f[0],t=f[1],v=f[2],B=f[3],z=c[0],E=c[1],A=(E-b[1])/(z-b[0]);g&2&&!(e&2)&&(d=z-(E-B)/A,d=d>=r&&d<=v);d||!(g&4)||e&4||(d=E-(z-v)*A,d=d>=t&&d<=B);d||!(g&8)||e&8||(d=z-(E-t)/A,d=d>=r&&d<=v);d||!(g&16)||e&16||(d=E-(z-r)*A,d=d>=t&&d<=B)}return d}):!1} +function Cf(b,c,d,e,f){var g=d[0];if(!(Bf(b,c,g,e,f)||xf(b,c,g,e,f[0],f[1])||xf(b,c,g,e,f[0],f[3])||xf(b,c,g,e,f[2],f[1])||xf(b,c,g,e,f[2],f[3])))return!1;if(1===d.length)return!0;c=1;for(g=d.length;c<g;++c)if(wf(b,d[c-1],d[c],e,f))return!1;return!0};function Df(b,c,d,e){for(var f=0,g=b[d-e],h=b[d-e+1];c<d;c+=e)var k=b[c],n=b[c+1],f=f+(k-g)*(n+h),g=k,h=n;return 0<f}function Ef(b,c,d,e){var f=0;e=m(e)?e:!1;var g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],f=Df(b,f,k,d);if(0===g){if(e&&f||!e&&!f)return!1}else if(e&&!f||!e&&f)return!1;f=k}return!0} +function Ff(b,c,d,e,f){f=m(f)?f:!1;var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],n=Df(b,c,k,e);if(0===g?f&&n||!f&&!n:f&&!n||!f&&n)for(var n=b,p=k,q=e;c<p-q;){var r;for(r=0;r<q;++r){var t=n[c+r];n[c+r]=n[p-q+r];n[p-q+r]=t}c+=q;p-=q}c=k}return c}function Gf(b,c,d,e){var f=0,g,h;g=0;for(h=c.length;g<h;++g)f=Ff(b,f,c[g],d,e);return f};function F(b,c){Ze.call(this);this.c=[];this.u=-1;this.G=null;this.U=this.C=this.K=-1;this.j=null;this.ja(b,c)}w(F,Ze);l=F.prototype;l.Pi=function(b){null===this.o?this.o=b.o.slice():cb(this.o,b.o);this.c.push(this.o.length);this.s()};l.clone=function(){var b=new F(null);Hf(b,this.b,this.o.slice(),this.c.slice());return b}; +l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;this.C!=this.a&&(this.K=Math.sqrt(jf(this.o,0,this.c,this.H,0)),this.C=this.a);return lf(this.o,0,this.c,this.H,this.K,!0,b,c,d,e)};l.ic=function(b,c){return yf(If(this),0,this.c,this.H,b,c)};l.Hl=function(){return df(If(this),0,this.c,this.H)};l.W=function(b){var c;m(b)?(c=If(this).slice(),Ff(c,0,this.c,this.H,b)):c=this.o;return qf(c,0,this.c,this.H)}; +function Jf(b){if(b.u!=b.a){var c=ee(b.R());b.G=zf(If(b),0,b.c,b.H,c,0);b.u=b.a}return b.G}l.vj=function(){return new D(Jf(this))};l.Aj=function(){return this.c.length};l.og=function(b){if(0>b||this.c.length<=b)return null;var c=new tf(null);uf(c,this.b,this.o.slice(0===b?0:this.c[b-1],this.c[b]));return c};l.Ud=function(){var b=this.b,c=this.o,d=this.c,e=[],f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],n=new tf(null);uf(n,b,c.slice(f,k));e.push(n);f=k}return e}; +function If(b){if(b.U!=b.a){var c=b.o;Ef(c,b.c,b.H)?b.j=c:(b.j=c.slice(),b.j.length=Ff(b.j,0,b.c,b.H));b.U=b.a}return b.j}l.Gc=function(b){var c=[],d=[];c.length=sf(this.o,0,this.c,this.H,Math.sqrt(b),c,0,d);b=new F(null);Hf(b,"XY",c,d);return b};l.V=function(){return"Polygon"};l.sa=function(b){return Cf(If(this),0,this.c,this.H,b)}; +l.ja=function(b,c){if(null===b)Hf(this,"XY",null,this.c);else{bf(this,c,b,2);null===this.o&&(this.o=[]);var d=of(this.o,0,b,this.H,this.c);this.o.length=0===d.length?0:d[d.length-1];this.s()}};function Hf(b,c,d,e){af(b,c,d);b.c=e;b.s()}function Kf(b,c,d,e){var f=m(e)?e:32;e=[];var g;for(g=0;g<f;++g)cb(e,b.offset(c,d,2*Math.PI*g/f));e.push(e[0],e[1]);b=new F(null);Hf(b,"XY",e,[e.length]);return b} +function Lf(b){var c=b[0],d=b[1],e=b[2];b=b[3];c=[c,d,c,b,e,b,e,d,c,d];d=new F(null);Hf(d,"XY",c,[c.length]);return d}function Mf(b,c,d){var e=m(c)?c:32,f=b.H;c=b.b;for(var g=new F(null,c),e=f*(e+1),f=[],h=0;h<e;h++)f[h]=0;Hf(g,c,f,[f.length]);Nf(g,b.rd(),b.xf(),d);return g}function Nf(b,c,d,e){var f=b.o,g=b.b,h=b.H,k=b.c,n=f.length/h-1;e=m(e)?e:0;for(var p,q,r=0;r<=n;++r)q=r*h,p=e+2*Xb(r,n)*Math.PI/n,f[q]=c[0]+d*Math.cos(p),f[q+1]=c[1]+d*Math.sin(p);Hf(b,g,f,k)};function Of(b){gd.call(this);b=m(b)?b:{};this.c=[0,0];var c={};c.center=m(b.center)?b.center:null;this.g=Pe(b.projection);var d,e,f,g=m(b.minZoom)?b.minZoom:0;d=m(b.maxZoom)?b.maxZoom:28;var h=m(b.zoomFactor)?b.zoomFactor:2;if(m(b.resolutions))d=b.resolutions,e=d[0],f=d[d.length-1],d=cc(d);else{e=Pe(b.projection);f=e.R();var k=(null===f?360*xe.degrees/xe[e.b]:Math.max(je(f),ge(f)))/256/Math.pow(2,0),n=k/Math.pow(2,28);e=b.maxResolution;m(e)?g=0:e=k/Math.pow(h,g);f=b.minResolution;m(f)||(f=m(b.maxZoom)? +m(b.maxResolution)?e/Math.pow(h,d):k/Math.pow(h,d):n);d=g+Math.floor(Math.log(e/f)/Math.log(h));f=e/Math.pow(h,d-g);d=dc(h,e,d-g)}this.b=e;this.j=f;this.f=g;g=m(b.extent)?$b(b.extent):ac;(m(b.enableRotation)?b.enableRotation:1)?(e=b.constrainRotation,e=m(e)&&!0!==e?!1===e?fc:ja(e)?gc(e):fc:hc()):e=ec;this.i=new ic(g,d,e);m(b.resolution)?c.resolution=b.resolution:m(b.zoom)&&(c.resolution=this.constrainResolution(this.b,b.zoom-this.f));c.rotation=m(b.rotation)?b.rotation:0;this.I(c)}w(Of,gd);l=Of.prototype; +l.Nd=function(b){return this.i.center(b)};l.constrainResolution=function(b,c,d){return this.i.resolution(b,c||0,d||0)};l.constrainRotation=function(b,c){return this.i.rotation(b,c||0)};l.Ka=function(){return this.get("center")};l.Zc=function(b){var c=this.Ka(),d=this.Da(),e=this.Ea();return fe(c,d,e,b)};l.ol=function(){return this.g};l.Da=function(){return this.get("resolution")};function Pf(b){var c=b.b,d=Math.log(c/b.j)/Math.log(2);return function(b){return c/Math.pow(2,b*d)}}l.Ea=function(){return this.get("rotation")}; +function Qf(b){var c=b.b,d=Math.log(c/b.j)/Math.log(2);return function(b){return Math.log(c/b)/Math.log(2)/d}}function Rf(b){var c=b.Ka(),d=b.g,e=b.Da();b=b.Ea();return{center:[Math.round(c[0]/e)*e,Math.round(c[1]/e)*e],projection:m(d)?d:null,resolution:e,rotation:b}}l.Wj=function(){var b,c=this.Da();if(m(c)){var d,e=0;do{d=this.constrainResolution(this.b,e);if(d==c){b=e;break}++e}while(d>this.j)}return m(b)?this.f+b:b}; +l.af=function(b,c,d){b instanceof Ze||(b=Lf(b));var e=m(d)?d:{};d=m(e.padding)?e.padding:[0,0,0,0];var f=m(e.constrainResolution)?e.constrainResolution:!0,g=m(e.nearest)?e.nearest:!1,h;m(e.minResolution)?h=e.minResolution:m(e.maxZoom)?h=this.constrainResolution(this.b,e.maxZoom-this.f,0):h=0;var k=b.o,n=this.Ea(),e=Math.cos(-n),n=Math.sin(-n),p=Infinity,q=Infinity,r=-Infinity,t=-Infinity;b=b.H;for(var v=0,B=k.length;v<B;v+=b)var z=k[v]*e-k[v+1]*n,E=k[v]*n+k[v+1]*e,p=Math.min(p,z),q=Math.min(q,E), +r=Math.max(r,z),t=Math.max(t,E);k=[p,q,r,t];c=[c[0]-d[1]-d[3],c[1]-d[0]-d[2]];c=Math.max(je(k)/c[0],ge(k)/c[1]);c=isNaN(c)?h:Math.max(c,h);f&&(h=this.constrainResolution(c,0,0),!g&&h<c&&(h=this.constrainResolution(h,-1,0)),c=h);this.Yb(c);n=-n;g=(p+r)/2+(d[1]-d[3])/2*c;d=(q+t)/2+(d[0]-d[2])/2*c;this.eb([g*e-d*n,d*e+g*n])}; +l.Vi=function(b,c,d){var e=this.Ea(),f=Math.cos(-e),e=Math.sin(-e),g=b[0]*f-b[1]*e;b=b[1]*f+b[0]*e;var h=this.Da(),g=g+(c[0]/2-d[0])*h;b+=(d[1]-c[1]/2)*h;e=-e;this.eb([g*f-b*e,b*f+g*e])};function Sf(b){return null!=b.Ka()&&m(b.Da())}l.rotate=function(b,c){if(m(c)){var d,e=this.Ka();m(e)&&(d=[e[0]-c[0],e[1]-c[1]],sd(d,b-this.Ea()),nd(d,c));this.eb(d)}this.oe(b)};l.eb=function(b){this.set("center",b)};function Uf(b,c){b.c[1]+=c}l.Yb=function(b){this.set("resolution",b)}; +l.oe=function(b){this.set("rotation",b)};l.Do=function(b){b=this.constrainResolution(this.b,b-this.f,0);this.Yb(b)};function Vf(b){return 1-Math.pow(1-b,3)};function Wf(b){return 3*b*b-2*b*b*b}function Xf(b){return b}function Yf(b){return.5>b?Wf(2*b):1-Wf(2*(b-.5))};function Zf(b){var c=b.source,d=m(b.start)?b.start:ua(),e=c[0],f=c[1],g=m(b.duration)?b.duration:1E3,h=m(b.easing)?b.easing:Wf;return function(b,c){if(c.time<d)return c.animate=!0,c.viewHints[0]+=1,!0;if(c.time<d+g){var p=1-h((c.time-d)/g),q=e-c.viewState.center[0],r=f-c.viewState.center[1];c.animate=!0;c.viewState.center[0]+=p*q;c.viewState.center[1]+=p*r;c.viewHints[0]+=1;return!0}return!1}} +function $f(b){var c=m(b.rotation)?b.rotation:0,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:Wf,g=m(b.anchor)?b.anchor:null;return function(b,k){if(k.time<d)return k.animate=!0,k.viewHints[0]+=1,!0;if(k.time<d+e){var n=1-f((k.time-d)/e),n=(c-k.viewState.rotation)*n;k.animate=!0;k.viewState.rotation+=n;if(null!==g){var p=k.viewState.center;p[0]-=g[0];p[1]-=g[1];sd(p,n);nd(p,g)}k.viewHints[0]+=1;return!0}return!1}} +function ag(b){var c=b.resolution,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:Wf;return function(b,h){if(h.time<d)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<d+e){var k=1-f((h.time-d)/e),n=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*n;h.viewHints[0]+=1;return!0}return!1}};function bg(b,c,d,e){return m(e)?(e[0]=b,e[1]=c,e[2]=d,e):[b,c,d]}function cg(b,c,d){return b+"/"+c+"/"+d}function dg(b){var c=b[0],d=Array(c),e=1<<c-1,f,g;for(f=0;f<c;++f)g=48,b[1]&e&&(g+=1),b[2]&e&&(g+=2),d[f]=String.fromCharCode(g),e>>=1;return d.join("")}function eg(b){return cg(b[0],b[1],b[2])}function fg(b,c,d){var e=b[0],f=gg(c,b);d=hg(d);if(Sd(d,f))return b;b=je(d);d=Math.ceil((d[0]-f[0])/b);f[0]+=b*d;return c.Zd(f,e)} +function ig(b,c){var d=b[0],e=b[1],f=b[2];if(c.minZoom>d||d>c.maxZoom)return!1;var g=c.R(),d=null===g?null===c.b?null:c.b[d]:jg(c,g,d);return null===d?!0:kg(d,e,f)};function lg(b,c,d,e){this.a=b;this.f=c;this.b=d;this.c=e}lg.prototype.contains=function(b){return kg(this,b[1],b[2])};function kg(b,c,d){return b.a<=c&&c<=b.f&&b.b<=d&&d<=b.c}function mg(b,c){return b.a==c.a&&b.b==c.b&&b.f==c.f&&b.c==c.c}function ng(b){return b.f-b.a+1}function og(b,c){return b.a<=c.f&&b.f>=c.a&&b.b<=c.c&&b.c>=c.b};function pg(b){this.b=b.html;this.a=m(b.tileRanges)?b.tileRanges:null}pg.prototype.c=function(){return this.b};function qg(b,c,d){sc.call(this,b,d);this.element=c}w(qg,sc);function rg(b){gd.call(this);this.b=m(b)?b:[];sg(this)}w(rg,gd);l=rg.prototype;l.clear=function(){for(;0<this.Rb();)this.pop()};l.sf=function(b){var c,d;c=0;for(d=b.length;c<d;++c)this.push(b[c]);return this};l.forEach=function(b,c){Sa(this.b,b,c)};l.Yk=function(){return this.b};l.item=function(b){return this.b[b]};l.Rb=function(){return this.get("length")};l.fe=function(b,c){db(this.b,b,0,c);sg(this);C(this,new qg("add",c,this))}; +l.pop=function(){return this.Kf(this.Rb()-1)};l.push=function(b){var c=this.b.length;this.fe(c,b);return c};l.remove=function(b){var c=this.b,d,e;d=0;for(e=c.length;d<e;++d)if(c[d]===b)return this.Kf(d)};l.Kf=function(b){var c=this.b[b];Qa.splice.call(this.b,b,1);sg(this);C(this,new qg("remove",c,this));return c};l.lo=function(b,c){var d=this.Rb();if(b<d)d=this.b[b],this.b[b]=c,C(this,new qg("remove",d,this)),C(this,new qg("add",c,this));else{for(;d<b;++d)this.fe(d,void 0);this.fe(b,c)}}; +function sg(b){b.set("length",b.b.length)};var tg=/^#(?:[0-9a-f]{3}){1,2}$/i,ug=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i,vg=/^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;function wg(b){return ga(b)?b:xg(b)}function yg(b){if(!ia(b)){var c=b[0];c!=(c|0)&&(c=c+.5|0);var d=b[1];d!=(d|0)&&(d=d+.5|0);var e=b[2];e!=(e|0)&&(e=e+.5|0);b="rgba("+c+","+d+","+e+","+b[3]+")"}return b} +var xg=function(){var b={},c=0;return function(d){var e;if(b.hasOwnProperty(d))e=b[d];else{if(1024<=c){e=0;for(var f in b)0===(e++&3)&&(delete b[f],--c)}var g,h;tg.exec(d)?(h=3==d.length-1?1:2,e=parseInt(d.substr(1+0*h,h),16),f=parseInt(d.substr(1+1*h,h),16),g=parseInt(d.substr(1+2*h,h),16),1==h&&(e=(e<<4)+e,f=(f<<4)+f,g=(g<<4)+g),e=[e,f,g,1]):(h=vg.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]),h=Number(h[4]),e=[e,f,g,h],e=zg(e,e)):(h=ug.exec(d))?(e=Number(h[1]),f=Number(h[2]),g=Number(h[3]), +e=[e,f,g,1],e=zg(e,e)):e=void 0;b[d]=e;++c}return e}}();function zg(b,c){var d=m(c)?c:[];d[0]=Wb(b[0]+.5|0,0,255);d[1]=Wb(b[1]+.5|0,0,255);d[2]=Wb(b[2]+.5|0,0,255);d[3]=Wb(b[3],0,1);return d};function Ag(){this.j=Ad();this.b=void 0;this.a=Ad();this.f=void 0;this.c=Ad();this.i=void 0;this.g=Ad();this.B=void 0;this.l=Ad()} +function Bg(b,c,d,e,f){var g=!1;m(c)&&c!==b.b&&(g=b.a,Ed(g),g[12]=c,g[13]=c,g[14]=c,g[15]=1,b.b=c,g=!0);if(m(d)&&d!==b.f){g=b.c;Ed(g);g[0]=d;g[5]=d;g[10]=d;g[15]=1;var h=-.5*d+.5;g[12]=h;g[13]=h;g[14]=h;g[15]=1;b.f=d;g=!0}m(e)&&e!==b.i&&(g=Math.cos(e),h=Math.sin(e),Bd(b.g,.213+.787*g-.213*h,.213-.213*g+.143*h,.213-.213*g-.787*h,0,.715-.715*g-.715*h,.715+.285*g+.14*h,.715-.715*g+.715*h,0,.072-.072*g+.928*h,.072-.072*g-.283*h,.072+.928*g+.072*h,0,0,0,0,1),b.i=e,g=!0);m(f)&&f!==b.B&&(Bd(b.l,.213+.787* +f,.213-.213*f,.213-.213*f,0,.715-.715*f,.715+.285*f,.715-.715*f,0,.072-.072*f,.072-.072*f,.072+.928*f,0,0,0,0,1),b.B=f,g=!0);g&&(g=b.j,Ed(g),m(d)&&Fd(g,b.c,g),m(c)&&Fd(g,b.a,g),m(f)&&Fd(g,b.l,g),m(e)&&Fd(g,b.g,g));return b.j};var Cg=!Jb||Jb&&9<=Ub;!Kb&&!Jb||Jb&&Jb&&9<=Ub||Kb&&Sb("1.9.1");Jb&&Sb("9");Hb("area base br col command embed hr img input keygen link meta param source track wbr".split(" "));function Dg(b,c){this.x=m(b)?b:0;this.y=m(c)?c:0}l=Dg.prototype;l.clone=function(){return new Dg(this.x,this.y)};l.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};l.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};l.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};l.scale=function(b,c){var d=ja(c)?c:b;this.x*=b;this.y*=d;return this};function Eg(b,c){this.width=b;this.height=c}l=Eg.prototype;l.clone=function(){return new Eg(this.width,this.height)};l.wa=function(){return!(this.width*this.height)};l.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};l.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this}; +l.scale=function(b,c){var d=ja(c)?c:b;this.width*=b;this.height*=d;return this};function Fg(b){return b?new Gg(Hg(b)):ya||(ya=new Gg)}function Ig(b){var c=document;return ia(b)?c.getElementById(b):b}function Jg(b,c){pb(c,function(c,e){"style"==e?b.style.cssText=c:"class"==e?b.className=c:"for"==e?b.htmlFor=c:e in Kg?b.setAttribute(Kg[e],c):0==e.lastIndexOf("aria-",0)||0==e.lastIndexOf("data-",0)?b.setAttribute(e,c):b[e]=c})} +var Kg={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};function Lg(b){b=b.document.documentElement;return new Eg(b.clientWidth,b.clientHeight)} +function Mg(b,c,d){var e=arguments,f=document,g=e[0],h=e[1];if(!Cg&&h&&(h.name||h.type)){g=["<",g];h.name&&g.push(' name="',Da(h.name),'"');if(h.type){g.push(' type="',Da(h.type),'"');var k={};Gb(k,h);delete k.type;h=k}g.push(">");g=g.join("")}g=f.createElement(g);h&&(ia(h)?g.className=h:ga(h)?g.className=h.join(" "):Jg(g,h));2<e.length&&Ng(f,g,e,2);return g} +function Ng(b,c,d,e){function f(d){d&&c.appendChild(ia(d)?b.createTextNode(d):d)}for(;e<d.length;e++){var g=d[e];!ha(g)||la(g)&&0<g.nodeType?f(g):Sa(Og(g)?bb(g):g,f)}}function Pg(b){return document.createElement(b)}function Qg(b,c){Ng(Hg(b),b,arguments,1)}function Rg(b){for(var c;c=b.firstChild;)b.removeChild(c)}function Sg(b,c,d){b.insertBefore(c,b.childNodes[d]||null)}function Tg(b){b&&b.parentNode&&b.parentNode.removeChild(b)}function Ug(b,c){var d=c.parentNode;d&&d.replaceChild(b,c)} +function Vg(b){if(void 0!=b.firstElementChild)b=b.firstElementChild;else for(b=b.firstChild;b&&1!=b.nodeType;)b=b.nextSibling;return b}function Wg(b,c){if(b.contains&&1==c.nodeType)return b==c||b.contains(c);if("undefined"!=typeof b.compareDocumentPosition)return b==c||Boolean(b.compareDocumentPosition(c)&16);for(;c&&b!=c;)c=c.parentNode;return c==b}function Hg(b){return 9==b.nodeType?b:b.ownerDocument||b.document} +function Og(b){if(b&&"number"==typeof b.length){if(la(b))return"function"==typeof b.item||"string"==typeof b.item;if(ka(b))return"function"==typeof b.item}return!1}function Gg(b){this.a=b||ba.document||document}function Xg(){return!0}function Yg(b){var c=b.a;b=Lb?c.body||c.documentElement:c.documentElement;c=c.parentWindow||c.defaultView;return Jb&&Sb("10")&&c.pageYOffset!=b.scrollTop?new Dg(b.scrollLeft,b.scrollTop):new Dg(c.pageXOffset||b.scrollLeft,c.pageYOffset||b.scrollTop)} +Gg.prototype.appendChild=function(b,c){b.appendChild(c)};Gg.prototype.contains=Wg;function Zg(b){if(b.classList)return b.classList;b=b.className;return ia(b)&&b.match(/\S+/g)||[]}function $g(b,c){return b.classList?b.classList.contains(c):Ya(Zg(b),c)}function ah(b,c){b.classList?b.classList.add(c):$g(b,c)||(b.className+=0<b.className.length?" "+c:c)}function bh(b,c){b.classList?b.classList.remove(c):$g(b,c)&&(b.className=Ta(Zg(b),function(b){return b!=c}).join(" "))}function ch(b,c){$g(b,c)?bh(b,c):ah(b,c)};function dh(b,c,d,e){this.top=b;this.right=c;this.bottom=d;this.left=e}l=dh.prototype;l.clone=function(){return new dh(this.top,this.right,this.bottom,this.left)};l.contains=function(b){return this&&b?b instanceof dh?b.left>=this.left&&b.right<=this.right&&b.top>=this.top&&b.bottom<=this.bottom:b.x>=this.left&&b.x<=this.right&&b.y>=this.top&&b.y<=this.bottom:!1}; l.ceil=function(){this.top=Math.ceil(this.top);this.right=Math.ceil(this.right);this.bottom=Math.ceil(this.bottom);this.left=Math.ceil(this.left);return this};l.floor=function(){this.top=Math.floor(this.top);this.right=Math.floor(this.right);this.bottom=Math.floor(this.bottom);this.left=Math.floor(this.left);return this};l.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this}; -l.scale=function(b,c){var d=ja(c)?c:b;this.left*=b;this.right*=b;this.top*=d;this.bottom*=d;return this};function Gg(b,c,d,e){this.left=b;this.top=c;this.width=d;this.height=e}l=Gg.prototype;l.clone=function(){return new Gg(this.left,this.top,this.width,this.height)};l.contains=function(b){return b instanceof Gg?this.left<=b.left&&this.left+this.width>=b.left+b.width&&this.top<=b.top&&this.top+this.height>=b.top+b.height:b.x>=this.left&&b.x<=this.left+this.width&&b.y>=this.top&&b.y<=this.top+this.height}; -function Hg(b,c){var d=c.x<b.left?b.left-c.x:Math.max(c.x-(b.left+b.width),0),e=c.y<b.top?b.top-c.y:Math.max(c.y-(b.top+b.height),0);return d*d+e*e}l.distance=function(b){return Math.sqrt(Hg(this,b))};l.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this}; -l.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};l.scale=function(b,c){var d=ja(c)?c:b;this.left*=b;this.width*=b;this.top*=d;this.height*=d;return this};function Ig(b,c){var d=zf(b);return d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(b,null))?d[c]||d.getPropertyValue(c)||"":""}function Jg(b,c){return Ig(b,c)||(b.currentStyle?b.currentStyle[c]:null)||b.style&&b.style[c]}function Kg(b,c,d){var e,f=Ib&&(Kb||Nb)&&Tb("1.9");c instanceof vf?(e=c.x,c=c.y):(e=c,c=d);b.style.left=Lg(e,f);b.style.top=Lg(c,f)} -function Mg(b){var c;try{c=b.getBoundingClientRect()}catch(d){return{left:0,top:0,right:0,bottom:0}}Hb&&b.ownerDocument.body&&(b=b.ownerDocument,c.left-=b.documentElement.clientLeft+b.body.clientLeft,c.top-=b.documentElement.clientTop+b.body.clientTop);return c} -function Ng(b){if(Hb&&!(Hb&&8<=Vb))return b.offsetParent;var c=zf(b),d=Jg(b,"position"),e="fixed"==d||"absolute"==d;for(b=b.parentNode;b&&b!=c;b=b.parentNode)if(d=Jg(b,"position"),e=e&&"static"==d&&b!=c.documentElement&&b!=c.body,!e&&(b.scrollWidth>b.clientWidth||b.scrollHeight>b.clientHeight||"fixed"==d||"absolute"==d||"relative"==d))return b;return null} -function Og(b){if(1==b.nodeType){var c;if(b.getBoundingClientRect)c=Mg(b),c=new vf(c.left,c.top);else{c=Sf(xf(b));var d,e=zf(b),f=Jg(b,"position"),g=Ib&&e.getBoxObjectFor&&!b.getBoundingClientRect&&"absolute"==f&&(d=e.getBoxObjectFor(b))&&(0>d.screenX||0>d.screenY),h=new vf(0,0),k;d=e?zf(e):document;k=!Hb||Hb&&9<=Vb||Rf(xf(d))?d.documentElement:d.body;if(b!=k)if(b.getBoundingClientRect)d=Mg(b),e=Sf(xf(e)),h.x=d.left+e.x,h.y=d.top+e.y;else if(e.getBoxObjectFor&&!g)d=e.getBoxObjectFor(b),e=e.getBoxObjectFor(k), -h.x=d.screenX-e.screenX,h.y=d.screenY-e.screenY;else{d=b;do{h.x+=d.offsetLeft;h.y+=d.offsetTop;d!=b&&(h.x+=d.clientLeft||0,h.y+=d.clientTop||0);if(Jb&&"fixed"==Jg(d,"position")){h.x+=e.body.scrollLeft;h.y+=e.body.scrollTop;break}d=d.offsetParent}while(d&&d!=b);if(Gb||Jb&&"absolute"==f)h.y-=e.body.offsetTop;for(d=b;(d=Ng(d))&&d!=e.body&&d!=k;)h.x-=d.scrollLeft,Gb&&"TR"==d.tagName||(h.y-=d.scrollTop)}c=new vf(h.x-c.x,h.y-c.y)}if(Ib&&!Tb(12)){b:{h=Qa();if(void 0===b.style[h]&&(h=(Jb?"Webkit":Ib?"Moz": -Hb?"ms":Gb?"O":null)+Ra(h),void 0!==b.style[h])){h=(Jb?"-webkit":Ib?"-moz":Hb?"-ms":Gb?"-o":null)+"-transform";break b}h="transform"}b=(b=Jg(b,h)||Jg(b,"transform"))?(b=b.match(Pg))?new vf(parseFloat(b[1]),parseFloat(b[2])):new vf(0,0):new vf(0,0);b=new vf(c.x+b.x,c.y+b.y)}else b=c;return b}c=ka(b.fh);h=b;b.targetTouches&&b.targetTouches.length?h=b.targetTouches[0]:c&&b.a.targetTouches&&b.a.targetTouches.length&&(h=b.a.targetTouches[0]);return new vf(h.clientX,h.clientY)} -function Lg(b,c){"number"==typeof b&&(b=(c?Math.round(b):b)+"px");return b}function Qg(b){var c=Rg;if("none"!=Jg(b,"display"))return c(b);var d=b.style,e=d.display,f=d.visibility,g=d.position;d.visibility="hidden";d.position="absolute";d.display="inline";b=c(b);d.display=e;d.position=g;d.visibility=f;return b}function Rg(b){var c=b.offsetWidth,d=b.offsetHeight,e=Jb&&!c&&!d;return m(c)&&!e||!b.getBoundingClientRect?new wf(c,d):(b=Mg(b),new wf(b.right-b.left,b.bottom-b.top))} -function Sg(b,c){b.style.display=c?"":"none"}function Tg(b,c,d,e){if(/^\d+px?$/.test(c))return parseInt(c,10);var f=b.style[d],g=b.runtimeStyle[d];b.runtimeStyle[d]=b.currentStyle[d];b.style[d]=c;c=b.style[e];b.style[d]=f;b.runtimeStyle[d]=g;return c}function Ug(b,c){var d=b.currentStyle?b.currentStyle[c]:null;return d?Tg(b,d,"left","pixelLeft"):0} -function Vg(b,c){if(Hb){var d=Ug(b,c+"Left"),e=Ug(b,c+"Right"),f=Ug(b,c+"Top"),g=Ug(b,c+"Bottom");return new Fg(f,e,g,d)}d=Ig(b,c+"Left");e=Ig(b,c+"Right");f=Ig(b,c+"Top");g=Ig(b,c+"Bottom");return new Fg(parseFloat(f),parseFloat(e),parseFloat(g),parseFloat(d))}var Wg={thin:2,medium:4,thick:6};function Xg(b,c){if("none"==(b.currentStyle?b.currentStyle[c+"Style"]:null))return 0;var d=b.currentStyle?b.currentStyle[c+"Width"]:null;return d in Wg?Wg[d]:Tg(b,d,"left","pixelLeft")} -function Yg(b){if(Hb&&!(Hb&&9<=Vb)){var c=Xg(b,"borderLeft"),d=Xg(b,"borderRight"),e=Xg(b,"borderTop");b=Xg(b,"borderBottom");return new Fg(e,d,b,c)}c=Ig(b,"borderLeftWidth");d=Ig(b,"borderRightWidth");e=Ig(b,"borderTopWidth");b=Ig(b,"borderBottomWidth");return new Fg(parseFloat(e),parseFloat(d),parseFloat(b),parseFloat(c))}var Pg=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function Zg(b,c,d){uc.call(this,b);this.map=c;this.frameState=m(d)?d:null}u(Zg,uc);function $g(b){td.call(this);this.element=m(b.element)?b.element:null;this.i=m(b.target)?Af(b.target):null;this.a=null;this.n=[];this.render=m(b.render)?b.render:ca}u($g,td);$g.prototype.M=function(){Nf(this.element);$g.S.M.call(this)};$g.prototype.d=function(){return this.a}; -$g.prototype.setMap=function(b){null===this.a||Nf(this.element);0!=this.n.length&&(Ta(this.n,Yc),this.n.length=0);this.a=b;null!==this.a&&((null===this.i?b.t:this.i).appendChild(this.element),this.render!==ca&&this.n.push(z(b,"postrender",this.render,!1,this)),b.render())};function ah(b){b=m(b)?b:{};this.q=If("UL");this.j=If("LI");this.q.appendChild(this.j);Sg(this.j,!1);this.b=m(b.collapsed)?b.collapsed:!0;this.f=m(b.collapsible)?b.collapsible:!0;this.f||(this.b=!1);var c=m(b.className)?b.className:"ol-attribution",d=m(b.tipLabel)?b.tipLabel:"Attributions";this.r=m(b.collapseLabel)?b.collapseLabel:"\u00bb";this.F=m(b.label)?b.label:"i";this.t=Ff("SPAN",{},this.f&&!this.b?this.r:this.F);d=Ff("BUTTON",{type:"button",title:d},this.t);z(d,"click",this.cj,!1,this);z(d, -["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control"+(this.b&&this.f?" ol-collapsed":"")+(this.f?"":" ol-uncollapsible"),this.q,d);$g.call(this,{element:c,render:m(b.render)?b.render:bh,target:b.target});this.p=!0;this.g={};this.e={};this.J={}}u(ah,$g); -function bh(b){b=b.frameState;if(null===b)this.p&&(Sg(this.element,!1),this.p=!1);else{var c,d,e,f,g,h,k,n,p,q=b.layerStatesArray,r=Cb(b.attributions),s={};d=0;for(c=q.length;d<c;d++)if(e=q[d].layer.a(),null!==e&&(p=ma(e).toString(),n=e.e,null!==n))for(e=0,f=n.length;e<f;e++)if(h=n[e],k=ma(h).toString(),!(k in r)){g=b.usedTiles[p];var v;if(v=m(g))a:if(null===h.a)v=!0;else{var y=v=void 0,C=void 0,F=void 0;for(F in g)if(F in h.a)for(C=g[F],v=0,y=h.a[F].length;v<y;++v){var G=h.a[F][v];if(G.a<=C.d&&G.d>= -C.a&&G.b<=C.c&&G.c>=C.b){v=!0;break a}}v=!1}v?(k in s&&delete s[k],r[k]=h):s[k]=h}c=[r,s];d=c[0];c=c[1];for(var w in this.g)w in d?(this.e[w]||(Sg(this.g[w],!0),this.e[w]=!0),delete d[w]):w in c?(this.e[w]&&(Sg(this.g[w],!1),delete this.e[w]),delete c[w]):(Nf(this.g[w]),delete this.g[w],delete this.e[w]);for(w in d)p=If("LI"),p.innerHTML=d[w].c,this.q.appendChild(p),this.g[w]=p,this.e[w]=!0;for(w in c)p=If("LI"),p.innerHTML=c[w].c,Sg(p,!1),this.q.appendChild(p),this.g[w]=p;w=!xb(this.e)||!xb(b.logos); -this.p!=w&&(Sg(this.element,w),this.p=w);w&&xb(this.e)?Cg(this.element,"ol-logo-only"):Dg(this.element,"ol-logo-only");var U;b=b.logos;w=this.J;for(U in w)U in b||(Nf(w[U]),delete w[U]);for(var N in b)N in w||(U=new Image,U.src=N,d=b[N],""===d?d=U:(d=Ff("A",{href:d}),d.appendChild(U)),this.j.appendChild(d),w[N]=d);Sg(this.j,!xb(b))}}l=ah.prototype;l.cj=function(b){b.preventDefault();ch(this)};function ch(b){Eg(b.element,"ol-collapsed");Qf(b.t,b.b?b.r:b.F);b.b=!b.b}l.bj=function(){return this.f}; -l.ej=function(b){this.f!==b&&(this.f=b,Eg(this.element,"ol-uncollapsible"),!b&&this.b&&ch(this))};l.dj=function(b){this.f&&this.b!==b&&ch(this)};l.aj=function(){return this.b};function dh(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-rotate";this.b=Ff("SPAN","ol-compass",m(b.label)?b.label:"\u21e7");var d=Ff("BUTTON",{"class":c+"-reset",type:"button",title:m(b.tipLabel)?b.tipLabel:"Reset rotation"},this.b);z(d,"click",dh.prototype.j,!1,this);z(d,["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control",d);$g.call(this,{element:c,render:m(b.render)?b.render:eh,target:b.target});this.f=m(b.duration)?b.duration:250;this.e=m(b.autoHide)?b.autoHide: -!0;this.g=void 0;this.e&&Cg(this.element,"ol-hidden")}u(dh,$g);dh.prototype.j=function(b){b.preventDefault();b=this.a;var c=b.a();if(null!==c){for(var d=c.e();d<-Math.PI;)d+=2*Math.PI;for(;d>Math.PI;)d-=2*Math.PI;m(d)&&(0<this.f&&b.Ua(hf({rotation:d,duration:this.f,easing:cf})),c.r(0))}}; -function eh(b){b=b.frameState;if(null!==b){b=b.viewState.rotation;if(b!=this.g){var c="rotate("+180*b/Math.PI+"deg)";if(this.e){var d=this.element;0===b?Cg(d,"ol-hidden"):Dg(d,"ol-hidden")}this.b.style.msTransform=c;this.b.style.webkitTransform=c;this.b.style.transform=c}this.g=b}};function fh(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-zoom",d=m(b.delta)?b.delta:1,e=m(b.zoomOutLabel)?b.zoomOutLabel:"\u2212",f=m(b.zoomOutTipLabel)?b.zoomOutTipLabel:"Zoom out",g=Ff("BUTTON",{"class":c+"-in",type:"button",title:m(b.zoomInTipLabel)?b.zoomInTipLabel:"Zoom in"},m(b.zoomInLabel)?b.zoomInLabel:"+");z(g,"click",ta(fh.prototype.e,d),!1,this);z(g,["mouseout",xc],function(){this.blur()},!1);e=Ff("BUTTON",{"class":c+"-out",type:"button",title:f},e);z(e,"click",ta(fh.prototype.e, --d),!1,this);z(e,["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control",g,e);$g.call(this,{element:c,target:b.target});this.b=m(b.duration)?b.duration:250}u(fh,$g);fh.prototype.e=function(b,c){c.preventDefault();var d=this.a,e=d.a();if(null!==e){var f=e.b();m(f)&&(0<this.b&&d.Ua(jf({resolution:f,duration:this.b,easing:cf})),d=e.constrainResolution(f,b),e.d(d))}};function gh(b){b=m(b)?b:{};var c=new B;(m(b.zoom)?b.zoom:1)&&c.push(new fh(b.zoomOptions));(m(b.rotate)?b.rotate:1)&&c.push(new dh(b.rotateOptions));(m(b.attribution)?b.attribution:1)&&c.push(new ah(b.attributionOptions));return c};var hh=Jb?"webkitfullscreenchange":Ib?"mozfullscreenchange":Hb?"MSFullscreenChange":"fullscreenchange";function ih(){var b=xf().a,c=b.body;return!!(c.webkitRequestFullscreen||c.mozRequestFullScreen&&b.mozFullScreenEnabled||c.msRequestFullscreen&&b.msFullscreenEnabled||c.requestFullscreen&&b.fullscreenEnabled)} -function jh(b){b.webkitRequestFullscreen?b.webkitRequestFullscreen():b.mozRequestFullScreen?b.mozRequestFullScreen():b.msRequestFullscreen?b.msRequestFullscreen():b.requestFullscreen&&b.requestFullscreen()}function kh(){var b=xf().a;return!!(b.webkitIsFullScreen||b.mozFullScreen||b.msFullscreenElement||b.fullscreenElement)};function lh(b){b=m(b)?b:{};this.b=m(b.className)?b.className:"ol-full-screen";var c=m(b.tipLabel)?b.tipLabel:"Toggle full-screen",c=Ff("BUTTON",{"class":this.b+"-"+kh(),type:"button",title:c});z(c,"click",this.g,!1,this);z(c,["mouseout",xc],function(){this.blur()},!1);z(ba.document,hh,this.e,!1,this);var d=this.b+" ol-unselectable ol-control "+(ih()?"":"ol-unsupported"),c=Ff("DIV",d,c);$g.call(this,{element:c,target:b.target});this.f=m(b.keys)?b.keys:!1}u(lh,$g); -lh.prototype.g=function(b){b.preventDefault();ih()&&(b=this.a,null!==b&&(kh()?(b=xf().a,b.webkitCancelFullScreen?b.webkitCancelFullScreen():b.mozCancelFullScreen?b.mozCancelFullScreen():b.msExitFullscreen?b.msExitFullscreen():b.exitFullscreen&&b.exitFullscreen()):(b=b.qc(),b=Af(b),this.f?b.mozRequestFullScreenWithKeys?b.mozRequestFullScreenWithKeys():b.webkitRequestFullscreen?b.webkitRequestFullscreen():jh(b):jh(b))))}; -lh.prototype.e=function(){var b=this.b+"-true",c=this.b+"-false",d=Of(this.element),e=this.a;kh()?Bg(d,c)&&(Dg(d,c),Cg(d,b)):Bg(d,b)&&(Dg(d,b),Cg(d,c));null===e||e.j()};function mh(b){b=m(b)?b:{};var c=Ff("DIV",m(b.className)?b.className:"ol-mouse-position");$g.call(this,{element:c,render:m(b.render)?b.render:nh,target:b.target});z(this,xd("projection"),this.J,!1,this);m(b.coordinateFormat)&&this.r(b.coordinateFormat);m(b.projection)&&this.q(Ee(b.projection));this.Q=m(b.undefinedHTML)?b.undefinedHTML:"";this.j=c.innerHTML;this.f=this.e=this.b=null}u(mh,$g); -function nh(b){b=b.frameState;null===b?this.b=null:this.b!=b.viewState.projection&&(this.b=b.viewState.projection,this.e=null);oh(this,this.f)}mh.prototype.J=function(){this.e=null};mh.prototype.g=function(){return this.get("coordinateFormat")};mh.prototype.getCoordinateFormat=mh.prototype.g;mh.prototype.p=function(){return this.get("projection")};mh.prototype.getProjection=mh.prototype.p;mh.prototype.t=function(b){this.f=this.a.ad(b.a);oh(this,this.f)}; -mh.prototype.F=function(){oh(this,null);this.f=null};mh.prototype.setMap=function(b){mh.S.setMap.call(this,b);null!==b&&(b=b.b,this.n.push(z(b,"mousemove",this.t,!1,this),z(b,"mouseout",this.F,!1,this)))};mh.prototype.r=function(b){this.set("coordinateFormat",b)};mh.prototype.setCoordinateFormat=mh.prototype.r;mh.prototype.q=function(b){this.set("projection",b)};mh.prototype.setProjection=mh.prototype.q; -function oh(b,c){var d=b.Q;if(null!==c&&null!==b.b){if(null===b.e){var e=b.p();b.e=m(e)?De(b.b,e):We}e=b.a.Ga(c);null!==e&&(b.e(e,e),d=b.g(),d=m(d)?d(e):e.toString())}m(b.j)&&d==b.j||(b.element.innerHTML=d,b.j=d)};function ph(b){if("function"==typeof b.kb)return b.kb();if(ia(b))return b.split("");if(ha(b)){for(var c=[],d=b.length,e=0;e<d;e++)c.push(b[e]);return c}return rb(b)} -function qh(b,c){if("function"==typeof b.forEach)b.forEach(c,void 0);else if(ha(b)||ia(b))Ta(b,c,void 0);else{var d;if("function"==typeof b.I)d=b.I();else if("function"!=typeof b.kb)if(ha(b)||ia(b)){d=[];for(var e=b.length,f=0;f<e;f++)d.push(f)}else d=sb(b);else d=void 0;for(var e=ph(b),f=e.length,g=0;g<f;g++)c.call(void 0,e[g],d&&d[g],b)}};function rh(b,c){this.c={};this.a=[];this.b=0;var d=arguments.length;if(1<d){if(d%2)throw Error("Uneven number of arguments");for(var e=0;e<d;e+=2)this.set(arguments[e],arguments[e+1])}else if(b){b instanceof rh?(d=b.I(),e=b.kb()):(d=sb(b),e=rb(b));for(var f=0;f<d.length;f++)this.set(d[f],e[f])}}l=rh.prototype;l.Ub=function(){return this.b};l.kb=function(){sh(this);for(var b=[],c=0;c<this.a.length;c++)b.push(this.c[this.a[c]]);return b};l.I=function(){sh(this);return this.a.concat()}; -l.ia=function(){return 0==this.b};l.clear=function(){this.c={};this.b=this.a.length=0};l.remove=function(b){return th(this.c,b)?(delete this.c[b],this.b--,this.a.length>2*this.b&&sh(this),!0):!1};function sh(b){if(b.b!=b.a.length){for(var c=0,d=0;c<b.a.length;){var e=b.a[c];th(b.c,e)&&(b.a[d++]=e);c++}b.a.length=d}if(b.b!=b.a.length){for(var f={},d=c=0;c<b.a.length;)e=b.a[c],th(f,e)||(b.a[d++]=e,f[e]=1),c++;b.a.length=d}}l.get=function(b,c){return th(this.c,b)?this.c[b]:c}; -l.set=function(b,c){th(this.c,b)||(this.b++,this.a.push(b));this.c[b]=c};l.forEach=function(b,c){for(var d=this.I(),e=0;e<d.length;e++){var f=d[e],g=this.get(f);b.call(c,g,f,this)}};l.clone=function(){return new rh(this)};function th(b,c){return Object.prototype.hasOwnProperty.call(b,c)};var uh=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function vh(b){if(wh){wh=!1;var c=ba.location;if(c){var d=c.href;if(d&&(d=(d=vh(d)[3]||null)?decodeURI(d):d)&&d!=c.hostname)throw wh=!0,Error();}}return b.match(uh)}var wh=Jb;function xh(b){if(b[1]){var c=b[0],d=c.indexOf("#");0<=d&&(b.push(c.substr(d)),b[0]=c=c.substr(0,d));d=c.indexOf("?");0>d?b[1]="?":d==c.length-1&&(b[1]=void 0)}return b.join("")} -function yh(b,c,d){if(ga(c))for(var e=0;e<c.length;e++)yh(b,String(c[e]),d);else null!=c&&d.push("&",b,""===c?"":"=",encodeURIComponent(String(c)))}function zh(b,c){for(var d in c)yh(d,c[d],b);return b};function Ah(b,c){var d;b instanceof Ah?(this.Wb=m(c)?c:b.Wb,Bh(this,b.Pb),this.cc=b.cc,this.pb=b.pb,Ch(this,b.sc),this.nb=b.nb,Dh(this,b.a.clone()),this.Tb=b.Tb):b&&(d=vh(String(b)))?(this.Wb=!!c,Bh(this,d[1]||"",!0),this.cc=Fh(d[2]||""),this.pb=Fh(d[3]||"",!0),Ch(this,d[4]),this.nb=Fh(d[5]||"",!0),Dh(this,d[6]||"",!0),this.Tb=Fh(d[7]||"")):(this.Wb=!!c,this.a=new Gh(null,0,this.Wb))}l=Ah.prototype;l.Pb="";l.cc="";l.pb="";l.sc=null;l.nb="";l.Tb="";l.Wb=!1; -l.toString=function(){var b=[],c=this.Pb;c&&b.push(Hh(c,Ih,!0),":");if(c=this.pb){b.push("//");var d=this.cc;d&&b.push(Hh(d,Ih,!0),"@");b.push(encodeURIComponent(String(c)).replace(/%25([0-9a-fA-F]{2})/g,"%$1"));c=this.sc;null!=c&&b.push(":",String(c))}if(c=this.nb)this.pb&&"/"!=c.charAt(0)&&b.push("/"),b.push(Hh(c,"/"==c.charAt(0)?Jh:Kh,!0));(c=this.a.toString())&&b.push("?",c);(c=this.Tb)&&b.push("#",Hh(c,Lh));return b.join("")};l.clone=function(){return new Ah(this)}; -function Bh(b,c,d){b.Pb=d?Fh(c,!0):c;b.Pb&&(b.Pb=b.Pb.replace(/:$/,""))}function Ch(b,c){if(c){c=Number(c);if(isNaN(c)||0>c)throw Error("Bad port number "+c);b.sc=c}else b.sc=null}function Dh(b,c,d){c instanceof Gh?(b.a=c,Mh(b.a,b.Wb)):(d||(c=Hh(c,Nh)),b.a=new Gh(c,0,b.Wb))}function Oh(b){return b instanceof Ah?b.clone():new Ah(b,void 0)} -function Ph(b,c){b instanceof Ah||(b=Oh(b));c instanceof Ah||(c=Oh(c));var d=b,e=c,f=d.clone(),g=!!e.Pb;g?Bh(f,e.Pb):g=!!e.cc;g?f.cc=e.cc:g=!!e.pb;g?f.pb=e.pb:g=null!=e.sc;var h=e.nb;if(g)Ch(f,e.sc);else if(g=!!e.nb)if("/"!=h.charAt(0)&&(d.pb&&!d.nb?h="/"+h:(d=f.nb.lastIndexOf("/"),-1!=d&&(h=f.nb.substr(0,d+1)+h))),d=h,".."==d||"."==d)h="";else if(-1!=d.indexOf("./")||-1!=d.indexOf("/.")){for(var h=0==d.lastIndexOf("/",0),d=d.split("/"),k=[],n=0;n<d.length;){var p=d[n++];"."==p?h&&n==d.length&&k.push(""): -".."==p?((1<k.length||1==k.length&&""!=k[0])&&k.pop(),h&&n==d.length&&k.push("")):(k.push(p),h=!0)}h=k.join("/")}else h=d;g?f.nb=h:g=""!==e.a.toString();g?Dh(f,Fh(e.a.toString())):g=!!e.Tb;g&&(f.Tb=e.Tb);return f}function Fh(b,c){return b?c?decodeURI(b):decodeURIComponent(b):""}function Hh(b,c,d){return ia(b)?(b=encodeURI(b).replace(c,Qh),d&&(b=b.replace(/%25([0-9a-fA-F]{2})/g,"%$1")),b):null}function Qh(b){b=b.charCodeAt(0);return"%"+(b>>4&15).toString(16)+(b&15).toString(16)} -var Ih=/[#\/\?@]/g,Kh=/[\#\?:]/g,Jh=/[\#\?]/g,Nh=/[\#\?@]/g,Lh=/#/g;function Gh(b,c,d){this.a=b||null;this.c=!!d}function Rh(b){if(!b.da&&(b.da=new rh,b.ta=0,b.a))for(var c=b.a.split("&"),d=0;d<c.length;d++){var e=c[d].indexOf("="),f=null,g=null;0<=e?(f=c[d].substring(0,e),g=c[d].substring(e+1)):f=c[d];f=decodeURIComponent(f.replace(/\+/g," "));f=Sh(b,f);b.add(f,g?decodeURIComponent(g.replace(/\+/g," ")):"")}}l=Gh.prototype;l.da=null;l.ta=null;l.Ub=function(){Rh(this);return this.ta}; -l.add=function(b,c){Rh(this);this.a=null;b=Sh(this,b);var d=this.da.get(b);d||this.da.set(b,d=[]);d.push(c);this.ta++;return this};l.remove=function(b){Rh(this);b=Sh(this,b);return th(this.da.c,b)?(this.a=null,this.ta-=this.da.get(b).length,this.da.remove(b)):!1};l.clear=function(){this.da=this.a=null;this.ta=0};l.ia=function(){Rh(this);return 0==this.ta};function Th(b,c){Rh(b);c=Sh(b,c);return th(b.da.c,c)} -l.I=function(){Rh(this);for(var b=this.da.kb(),c=this.da.I(),d=[],e=0;e<c.length;e++)for(var f=b[e],g=0;g<f.length;g++)d.push(c[e]);return d};l.kb=function(b){Rh(this);var c=[];if(ia(b))Th(this,b)&&(c=bb(c,this.da.get(Sh(this,b))));else{b=this.da.kb();for(var d=0;d<b.length;d++)c=bb(c,b[d])}return c};l.set=function(b,c){Rh(this);this.a=null;b=Sh(this,b);Th(this,b)&&(this.ta-=this.da.get(b).length);this.da.set(b,[c]);this.ta++;return this}; -l.get=function(b,c){var d=b?this.kb(b):[];return 0<d.length?String(d[0]):c};function Uh(b,c,d){b.remove(c);0<d.length&&(b.a=null,b.da.set(Sh(b,c),cb(d)),b.ta+=d.length)}l.toString=function(){if(this.a)return this.a;if(!this.da)return"";for(var b=[],c=this.da.I(),d=0;d<c.length;d++)for(var e=c[d],f=encodeURIComponent(String(e)),e=this.kb(e),g=0;g<e.length;g++){var h=f;""!==e[g]&&(h+="="+encodeURIComponent(String(e[g])));b.push(h)}return this.a=b.join("&")}; -l.clone=function(){var b=new Gh;b.a=this.a;this.da&&(b.da=this.da.clone(),b.ta=this.ta);return b};function Sh(b,c){var d=String(c);b.c&&(d=d.toLowerCase());return d}function Mh(b,c){c&&!b.c&&(Rh(b),b.a=null,b.da.forEach(function(b,c){var f=c.toLowerCase();c!=f&&(this.remove(c),Uh(this,f,b))},b));b.c=c};function Vh(b,c,d){pc.call(this);this.d=b;this.b=d;this.a=c||window;this.c=sa(this.Ye,this)}u(Vh,pc);l=Vh.prototype;l.X=null;l.Ie=!1;l.start=function(){Wh(this);this.Ie=!1;var b=Xh(this),c=Yh(this);b&&!c&&this.a.mozRequestAnimationFrame?(this.X=z(this.a,"MozBeforePaint",this.c),this.a.mozRequestAnimationFrame(null),this.Ie=!0):this.X=b&&c?b.call(this.a,this.c):this.a.setTimeout(gd(this.c),20)}; -function Wh(b){if(null!=b.X){var c=Xh(b),d=Yh(b);c&&!d&&b.a.mozRequestAnimationFrame?Yc(b.X):c&&d?d.call(b.a,b.X):b.a.clearTimeout(b.X)}b.X=null}l.Ye=function(){this.Ie&&this.X&&Yc(this.X);this.X=null;this.d.call(this.b,ua())};l.M=function(){Wh(this);Vh.S.M.call(this)};function Xh(b){b=b.a;return b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||b.oRequestAnimationFrame||b.msRequestAnimationFrame||null} -function Yh(b){b=b.a;return b.cancelRequestAnimationFrame||b.webkitCancelRequestAnimationFrame||b.mozCancelRequestAnimationFrame||b.oCancelRequestAnimationFrame||b.msCancelRequestAnimationFrame||null};function Zh(b){ba.setTimeout(function(){throw b;},0)}function $h(b,c){var d=b;c&&(d=sa(b,c));d=ai(d);!ka(ba.setImmediate)||ba.Window&&ba.Window.prototype.setImmediate==ba.setImmediate?(bi||(bi=ci()),bi(d)):ba.setImmediate(d)}var bi; -function ci(){var b=ba.MessageChannel;"undefined"===typeof b&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(b=function(){var b=document.createElement("iframe");b.style.display="none";b.src="";document.documentElement.appendChild(b);var c=b.contentWindow,b=c.document;b.open();b.write("");b.close();var d="callImmediate"+Math.random(),e="file:"==c.location.protocol?"*":c.location.protocol+"//"+c.location.host,b=sa(function(b){if(("*"==e||b.origin==e)&&b.data==d)this.port1.onmessage()}, -this);c.addEventListener("message",b,!1);this.port1={};this.port2={postMessage:function(){c.postMessage(d,e)}}});if("undefined"!==typeof b&&!nb("Trident")&&!nb("MSIE")){var c=new b,d={},e=d;c.port1.onmessage=function(){if(m(d.next)){d=d.next;var b=d.Ue;d.Ue=null;b()}};return function(b){e.next={Ue:b};e=e.next;c.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(b){var c=document.createElement("script");c.onreadystatechange=function(){c.onreadystatechange= -null;c.parentNode.removeChild(c);c=null;b();b=null};document.documentElement.appendChild(c)}:function(b){ba.setTimeout(b,0)}}var ai=ed;function di(){this.a=ua()}new di;di.prototype.set=function(b){this.a=b};di.prototype.get=function(){return this.a};function ei(b){jd.call(this);this.Pc=b||window;this.ld=z(this.Pc,"resize",this.oi,!1,this);this.md=Ef(this.Pc||window)}u(ei,jd);l=ei.prototype;l.ld=null;l.Pc=null;l.md=null;l.M=function(){ei.S.M.call(this);this.ld&&(Yc(this.ld),this.ld=null);this.md=this.Pc=null};l.oi=function(){var b=Ef(this.Pc||window),c=this.md;b==c||b&&c&&b.width==c.width&&b.height==c.height||(this.md=b,this.dispatchEvent("resize"))};function fi(b,c,d,e,f){if(!(Hb||Jb&&Tb("525")))return!0;if(Kb&&f)return gi(b);if(f&&!e)return!1;ja(c)&&(c=hi(c));if(!d&&(17==c||18==c||Kb&&91==c))return!1;if(Jb&&e&&d)switch(b){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(Hb&&e&&c==b)return!1;switch(b){case 13:return!0;case 27:return!Jb}return gi(b)} -function gi(b){if(48<=b&&57>=b||96<=b&&106>=b||65<=b&&90>=b||Jb&&0==b)return!0;switch(b){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function hi(b){if(Ib)b=ii(b);else if(Kb&&Jb)a:switch(b){case 93:b=91;break a}return b} -function ii(b){switch(b){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return b}};function ji(b,c){jd.call(this);b&&ki(this,b,c)}u(ji,jd);l=ji.prototype;l.aa=null;l.sd=null;l.le=null;l.td=null;l.Ka=-1;l.Gb=-1;l.ae=!1; -var li={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},mi={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},ni=Hb||Jb&&Tb("525"),oi=Kb&&Ib; -ji.prototype.a=function(b){Jb&&(17==this.Ka&&!b.o||18==this.Ka&&!b.c||Kb&&91==this.Ka&&!b.n)&&(this.Gb=this.Ka=-1);-1==this.Ka&&(b.o&&17!=b.e?this.Ka=17:b.c&&18!=b.e?this.Ka=18:b.n&&91!=b.e&&(this.Ka=91));ni&&!fi(b.e,this.Ka,b.d,b.o,b.c)?this.handleEvent(b):(this.Gb=hi(b.e),oi&&(this.ae=b.c))};ji.prototype.c=function(b){this.Gb=this.Ka=-1;this.ae=b.c}; -ji.prototype.handleEvent=function(b){var c=b.a,d,e,f=c.altKey;Hb&&"keypress"==b.type?(d=this.Gb,e=13!=d&&27!=d?c.keyCode:0):Jb&&"keypress"==b.type?(d=this.Gb,e=0<=c.charCode&&63232>c.charCode&&gi(d)?c.charCode:0):Gb?(d=this.Gb,e=gi(d)?c.keyCode:0):(d=c.keyCode||this.Gb,e=c.charCode||0,oi&&(f=this.ae),Kb&&63==e&&224==d&&(d=191));var g=d=hi(d),h=c.keyIdentifier;d?63232<=d&&d in li?g=li[d]:25==d&&b.d&&(g=9):h&&h in mi&&(g=mi[h]);this.Ka=g;b=new pi(g,e,0,c);b.c=f;this.dispatchEvent(b)}; -function ki(b,c,d){b.td&&qi(b);b.aa=c;b.sd=z(b.aa,"keypress",b,d);b.le=z(b.aa,"keydown",b.a,d,b);b.td=z(b.aa,"keyup",b.c,d,b)}function qi(b){b.sd&&(Yc(b.sd),Yc(b.le),Yc(b.td),b.sd=null,b.le=null,b.td=null);b.aa=null;b.Ka=-1;b.Gb=-1}ji.prototype.M=function(){ji.S.M.call(this);qi(this)};function pi(b,c,d,e){zc.call(this,e);this.type="key";this.e=b;this.i=c}u(pi,zc);function ri(b,c){jd.call(this);var d=this.aa=b;(d=la(d)&&1==d.nodeType?this.aa:this.aa?this.aa.body:null)&&Jg(d,"direction");this.a=z(this.aa,Ib?"DOMMouseScroll":"mousewheel",this,c)}u(ri,jd); -ri.prototype.handleEvent=function(b){var c=0,d=0,e=0;b=b.a;if("mousewheel"==b.type){d=1;if(Hb||Jb&&(Lb||Tb("532.0")))d=40;e=si(-b.wheelDelta,d);m(b.wheelDeltaX)?(c=si(-b.wheelDeltaX,d),d=si(-b.wheelDeltaY,d)):d=e}else e=b.detail,100<e?e=3:-100>e&&(e=-3),m(b.axis)&&b.axis===b.HORIZONTAL_AXIS?c=e:d=e;ja(this.c)&&Yb(c,-this.c,this.c);ja(this.b)&&(d=Yb(d,-this.b,this.b));c=new ti(e,b,0,d);this.dispatchEvent(c)};function si(b,c){return Jb&&(Kb||Mb)&&0!=b%c?b:b/c} -ri.prototype.M=function(){ri.S.M.call(this);Yc(this.a);this.a=null};function ti(b,c,d,e){zc.call(this,c);this.type="mousewheel";this.detail=b;this.j=e}u(ti,zc);function ui(b,c,d){uc.call(this,b);this.a=c;b=m(d)?d:{};this.buttons=vi(b);this.pressure=wi(b,this.buttons);this.bubbles=x(b,"bubbles",!1);this.cancelable=x(b,"cancelable",!1);this.view=x(b,"view",null);this.detail=x(b,"detail",null);this.screenX=x(b,"screenX",0);this.screenY=x(b,"screenY",0);this.clientX=x(b,"clientX",0);this.clientY=x(b,"clientY",0);this.button=x(b,"button",0);this.relatedTarget=x(b,"relatedTarget",null);this.pointerId=x(b,"pointerId",0);this.width=x(b,"width",0);this.height=x(b, -"height",0);this.pointerType=x(b,"pointerType","");this.isPrimary=x(b,"isPrimary",!1);c.preventDefault&&(this.preventDefault=function(){c.preventDefault()})}u(ui,uc);function vi(b){if(b.buttons||xi)b=b.buttons;else switch(b.which){case 1:b=1;break;case 2:b=4;break;case 3:b=2;break;default:b=0}return b}function wi(b,c){var d=0;b.pressure?d=b.pressure:d=c?.5:0;return d}var xi=!1;try{xi=1===(new MouseEvent("click",{buttons:1})).buttons}catch(yi){};function zi(b,c){this.a=b;this.e=c};function Ai(b){zi.call(this,b,{mousedown:this.yi,mousemove:this.zi,mouseup:this.Ci,mouseover:this.Bi,mouseout:this.Ai});this.c=b.c;this.b=[]}u(Ai,zi);function Bi(b,c){for(var d=b.b,e=c.clientX,f=c.clientY,g=0,h=d.length,k;g<h&&(k=d[g]);g++){var n=Math.abs(f-k[1]);if(25>=Math.abs(e-k[0])&&25>=n)return!0}return!1}function Ci(b){var c=Di(b,b.a),d=c.preventDefault;c.preventDefault=function(){b.preventDefault();d()};c.pointerId=1;c.isPrimary=!0;c.pointerType="mouse";return c}l=Ai.prototype; -l.yi=function(b){if(!Bi(this,b)){(1).toString()in this.c&&this.cancel(b);var c=Ci(b);this.c[(1).toString()]=b;Ei(this.a,Fi,c,b)}};l.zi=function(b){if(!Bi(this,b)){var c=Ci(b);Ei(this.a,Gi,c,b)}};l.Ci=function(b){if(!Bi(this,b)){var c=x(this.c,(1).toString());c&&c.button===b.button&&(c=Ci(b),Ei(this.a,Hi,c,b),zb(this.c,(1).toString()))}};l.Bi=function(b){if(!Bi(this,b)){var c=Ci(b);Ii(this.a,c,b)}};l.Ai=function(b){if(!Bi(this,b)){var c=Ci(b);Ji(this.a,c,b)}}; -l.cancel=function(b){var c=Ci(b);this.a.cancel(c,b);zb(this.c,(1).toString())};function Ki(b){zi.call(this,b,{MSPointerDown:this.Hi,MSPointerMove:this.Ii,MSPointerUp:this.Li,MSPointerOut:this.Ji,MSPointerOver:this.Ki,MSPointerCancel:this.Gi,MSGotPointerCapture:this.Ei,MSLostPointerCapture:this.Fi});this.c=b.c;this.b=["","unavailable","touch","pen","mouse"]}u(Ki,zi);function Li(b,c){var d=c;ja(c.a.pointerType)&&(d=Di(c,c.a),d.pointerType=b.b[c.a.pointerType]);return d}l=Ki.prototype;l.Hi=function(b){this.c[b.a.pointerId]=b;var c=Li(this,b);Ei(this.a,Fi,c,b)}; -l.Ii=function(b){var c=Li(this,b);Ei(this.a,Gi,c,b)};l.Li=function(b){var c=Li(this,b);Ei(this.a,Hi,c,b);zb(this.c,b.a.pointerId)};l.Ji=function(b){var c=Li(this,b);Ji(this.a,c,b)};l.Ki=function(b){var c=Li(this,b);Ii(this.a,c,b)};l.Gi=function(b){var c=Li(this,b);this.a.cancel(c,b);zb(this.c,b.a.pointerId)};l.Fi=function(b){this.a.dispatchEvent(new ui("lostpointercapture",b,b.a))};l.Ei=function(b){this.a.dispatchEvent(new ui("gotpointercapture",b,b.a))};function Mi(b){zi.call(this,b,{pointerdown:this.zk,pointermove:this.Ak,pointerup:this.Dk,pointerout:this.Bk,pointerover:this.Ck,pointercancel:this.yk,gotpointercapture:this.Ph,lostpointercapture:this.xi})}u(Mi,zi);l=Mi.prototype;l.zk=function(b){Ni(this.a,b)};l.Ak=function(b){Ni(this.a,b)};l.Dk=function(b){Ni(this.a,b)};l.Bk=function(b){Ni(this.a,b)};l.Ck=function(b){Ni(this.a,b)};l.yk=function(b){Ni(this.a,b)};l.xi=function(b){Ni(this.a,b)};l.Ph=function(b){Ni(this.a,b)};function Oi(b,c){zi.call(this,b,{touchstart:this.vl,touchmove:this.ul,touchend:this.tl,touchcancel:this.sl});this.c=b.c;this.g=c;this.b=void 0;this.f=0;this.d=void 0}u(Oi,zi);l=Oi.prototype;l.Yf=function(){this.f=0;this.d=void 0}; -function Pi(b,c,d){c=Di(c,d);c.pointerId=d.identifier+2;c.bubbles=!0;c.cancelable=!0;c.detail=b.f;c.button=0;c.buttons=1;c.width=d.webkitRadiusX||d.radiusX||0;c.height=d.webkitRadiusY||d.radiusY||0;c.pressure=d.webkitForce||d.force||.5;c.isPrimary=b.b===d.identifier;c.pointerType="touch";c.clientX=d.clientX;c.clientY=d.clientY;c.screenX=d.screenX;c.screenY=d.screenY;return c} -function Qi(b,c,d){function e(){c.preventDefault()}var f=Array.prototype.slice.call(c.a.changedTouches),g=f.length,h,k;for(h=0;h<g;++h)k=Pi(b,c,f[h]),k.preventDefault=e,d.call(b,c,k)} -l.vl=function(b){var c=b.a.touches,d=sb(this.c),e=d.length;if(e>=c.length){var f=[],g,h,k;for(g=0;g<e;++g){h=d[g];k=this.c[h];var n;if(!(n=1==h))a:{n=c.length;for(var p=void 0,q=0;q<n;q++)if(p=c[q],p.identifier===h-2){n=!0;break a}n=!1}n||f.push(k.$b)}for(g=0;g<f.length;++g)this.be(b,f[g])}c=qb(this.c);if(0===c||1===c&&(1).toString()in this.c)this.b=b.a.changedTouches[0].identifier,m(this.d)&&ba.clearTimeout(this.d);Ri(this,b);this.f++;Qi(this,b,this.uk)}; -l.uk=function(b,c){this.c[c.pointerId]={target:c.target,$b:c,Jf:c.target};var d=this.a;c.bubbles=!0;Ei(d,Si,c,b);d=this.a;c.bubbles=!1;Ei(d,Ti,c,b);Ei(this.a,Fi,c,b)};l.ul=function(b){b.preventDefault();Qi(this,b,this.Di)};l.Di=function(b,c){var d=x(this.c,c.pointerId);if(d){var e=d.$b,f=d.Jf;Ei(this.a,Gi,c,b);e&&f!==c.target&&(e.relatedTarget=c.target,c.relatedTarget=f,e.target=f,c.target?(Ji(this.a,e,b),Ii(this.a,c,b)):(c.target=f,c.relatedTarget=null,this.be(b,c)));d.$b=c;d.Jf=c.target}}; -l.tl=function(b){Ri(this,b);Qi(this,b,this.wl)};l.wl=function(b,c){Ei(this.a,Hi,c,b);this.a.$b(c,b);var d=this.a;c.bubbles=!1;Ei(d,Ui,c,b);zb(this.c,c.pointerId);c.isPrimary&&(this.b=void 0,this.d=ba.setTimeout(sa(this.Yf,this),200))};l.sl=function(b){Qi(this,b,this.be)};l.be=function(b,c){this.a.cancel(c,b);this.a.$b(c,b);var d=this.a;c.bubbles=!1;Ei(d,Ui,c,b);zb(this.c,c.pointerId);c.isPrimary&&(this.b=void 0,this.d=ba.setTimeout(sa(this.Yf,this),200))}; -function Ri(b,c){var d=b.g.b,e=c.a.changedTouches[0];if(b.b===e.identifier){var f=[e.clientX,e.clientY];d.push(f);ba.setTimeout(function(){ab(d,f)},2500)}};function Vi(b){jd.call(this);this.aa=b;this.c={};this.b={};this.a=[];hg?Xi(this,new Mi(this)):ig?Xi(this,new Ki(this)):(b=new Ai(this),Xi(this,b),gg&&Xi(this,new Oi(this,b)));b=this.a.length;for(var c,d=0;d<b;d++)c=this.a[d],Yi(this,sb(c.e))}u(Vi,jd);function Xi(b,c){var d=sb(c.e);d&&(Ta(d,function(b){var d=c.e[b];d&&(this.b[b]=sa(d,c))},b),b.a.push(c))}Vi.prototype.d=function(b){var c=this.b[b.type];c&&c(b)};function Yi(b,c){Ta(c,function(b){z(this.aa,b,this.d,!1,this)},b)} -function Zi(b,c){Ta(c,function(b){Xc(this.aa,b,this.d,!1,this)},b)}function Di(b,c){for(var d={},e,f=0,g=$i.length;f<g;f++)e=$i[f][0],d[e]=b[e]||c[e]||$i[f][1];return d}Vi.prototype.$b=function(b,c){b.bubbles=!0;Ei(this,aj,b,c)};Vi.prototype.cancel=function(b,c){Ei(this,bj,b,c)};function Ji(b,c,d){b.$b(c,d);c.target.contains(c.relatedTarget)||(c.bubbles=!1,Ei(b,Ui,c,d))}function Ii(b,c,d){c.bubbles=!0;Ei(b,Si,c,d);c.target.contains(c.relatedTarget)||(c.bubbles=!1,Ei(b,Ti,c,d))} -function Ei(b,c,d,e){b.dispatchEvent(new ui(c,e,d))}function Ni(b,c){b.dispatchEvent(new ui(c.type,c,c.a))}Vi.prototype.M=function(){for(var b=this.a.length,c,d=0;d<b;d++)c=this.a[d],Zi(this,sb(c.e));Vi.S.M.call(this)}; -var Gi="pointermove",Fi="pointerdown",Hi="pointerup",Si="pointerover",aj="pointerout",Ti="pointerenter",Ui="pointerleave",bj="pointercancel",$i=[["bubbles",!1],["cancelable",!1],["view",null],["detail",null],["screenX",0],["screenY",0],["clientX",0],["clientY",0],["ctrlKey",!1],["altKey",!1],["shiftKey",!1],["metaKey",!1],["button",0],["relatedTarget",null],["buttons",0],["pointerId",0],["width",0],["height",0],["pressure",0],["tiltX",0],["tiltY",0],["pointerType",""],["hwTimestamp",0],["isPrimary", -!1],["type",""],["target",null],["currentTarget",null],["which",0]];function cj(b,c,d,e){Zg.call(this,b,c,e);this.a=d;this.originalEvent=d.a;this.pixel=c.ad(this.originalEvent);this.coordinate=c.Ga(this.pixel)}u(cj,Zg);cj.prototype.preventDefault=function(){cj.S.preventDefault.call(this);this.a.preventDefault()};cj.prototype.lb=function(){cj.S.lb.call(this);this.a.lb()};function dj(b,c,d,e){cj.call(this,b,c,d.a,e);this.c=d}u(dj,cj); -function ej(b){jd.call(this);this.c=b;this.e=0;this.o=!1;this.f=this.g=this.b=null;b=this.c.b;this.j=0;this.n={};this.d=new Vi(b);this.a=null;this.g=z(this.d,Fi,this.ki,!1,this);this.i=z(this.d,Gi,this.Tk,!1,this)}u(ej,jd);function fj(b,c){var d;d=new dj(gj,b.c,c);b.dispatchEvent(d);0!==b.e?(ba.clearTimeout(b.e),b.e=0,d=new dj(hj,b.c,c),b.dispatchEvent(d)):b.e=ba.setTimeout(sa(function(){this.e=0;var b=new dj(ij,this.c,c);this.dispatchEvent(b)},b),250)} -function jj(b,c){c.type==kj||c.type==lj?delete b.n[c.pointerId]:c.type==mj&&(b.n[c.pointerId]=!0);b.j=qb(b.n)}l=ej.prototype;l.hf=function(b){jj(this,b);var c=new dj(kj,this.c,b);this.dispatchEvent(c);0===this.j&&(Ta(this.b,Yc),this.b=null,tc(this.a),this.a=null);!this.o&&0===b.button&&fj(this,this.f)}; -l.ki=function(b){jj(this,b);var c=new dj(mj,this.c,b);this.dispatchEvent(c);this.f=b;this.o=!1;null===this.b&&(this.a=new Vi(document),this.b=[z(this.a,nj,this.Zi,!1,this),z(this.a,kj,this.hf,!1,this),z(this.d,lj,this.hf,!1,this)])};l.Zi=function(b){if(b.clientX!=this.f.clientX||b.clientY!=this.f.clientY){this.o=!0;var c=new dj(oj,this.c,b);this.dispatchEvent(c)}b.preventDefault()};l.Tk=function(b){this.dispatchEvent(new dj(b.type,this.c,b))}; -l.M=function(){null!==this.i&&(Yc(this.i),this.i=null);null!==this.g&&(Yc(this.g),this.g=null);null!==this.b&&(Ta(this.b,Yc),this.b=null);null!==this.a&&(tc(this.a),this.a=null);null!==this.d&&(tc(this.d),this.d=null);ej.S.M.call(this)};var ij="singleclick",gj="click",hj="dblclick",oj="pointerdrag",nj="pointermove",mj="pointerdown",kj="pointerup",lj="pointercancel",pj={Tl:ij,Il:gj,Jl:hj,Ml:oj,Pl:nj,Ll:mj,Sl:kj,Rl:"pointerover",Ql:"pointerout",Nl:"pointerenter",Ol:"pointerleave",Kl:lj};function qj(b){md.call(this);this.g=Ee(b.projection);this.e=m(b.attributions)?b.attributions:null;this.r=b.logo;this.n=m(b.state)?b.state:"ready"}u(qj,md);l=qj.prototype;l.yd=ca;l.Y=function(){return this.e};l.W=function(){return this.r};l.Z=function(){return this.g};l.$=function(){return this.n};function rj(b,c){b.n=c;b.l()};function D(b){td.call(this);var c=Cb(b);c.brightness=m(b.brightness)?b.brightness:0;c.contrast=m(b.contrast)?b.contrast:1;c.hue=m(b.hue)?b.hue:0;c.opacity=m(b.opacity)?b.opacity:1;c.saturation=m(b.saturation)?b.saturation:1;c.visible=m(b.visible)?b.visible:!0;c.maxResolution=m(b.maxResolution)?b.maxResolution:Infinity;c.minResolution=m(b.minResolution)?b.minResolution:0;this.G(c)}u(D,td);D.prototype.d=function(){return this.get("brightness")};D.prototype.getBrightness=D.prototype.d; -D.prototype.e=function(){return this.get("contrast")};D.prototype.getContrast=D.prototype.e;D.prototype.f=function(){return this.get("hue")};D.prototype.getHue=D.prototype.f; -function sj(b){var c=b.d(),d=b.e(),e=b.f(),f=b.j(),g=b.n(),h=b.Ta(),k=b.b(),n=b.D(),p=b.g(),q=b.i();return{layer:b,brightness:m(c)?Yb(c,-1,1):0,contrast:m(d)?Math.max(d,0):1,hue:m(e)?e:0,opacity:m(f)?Yb(f,0,1):1,saturation:m(g)?Math.max(g,0):1,gc:h,visible:m(k)?!!k:!0,extent:n,maxResolution:m(p)?p:Infinity,minResolution:m(q)?Math.max(q,0):0}}D.prototype.D=function(){return this.get("extent")};D.prototype.getExtent=D.prototype.D;D.prototype.g=function(){return this.get("maxResolution")}; -D.prototype.getMaxResolution=D.prototype.g;D.prototype.i=function(){return this.get("minResolution")};D.prototype.getMinResolution=D.prototype.i;D.prototype.j=function(){return this.get("opacity")};D.prototype.getOpacity=D.prototype.j;D.prototype.n=function(){return this.get("saturation")};D.prototype.getSaturation=D.prototype.n;D.prototype.b=function(){return this.get("visible")};D.prototype.getVisible=D.prototype.b;D.prototype.t=function(b){this.set("brightness",b)};D.prototype.setBrightness=D.prototype.t; -D.prototype.F=function(b){this.set("contrast",b)};D.prototype.setContrast=D.prototype.F;D.prototype.J=function(b){this.set("hue",b)};D.prototype.setHue=D.prototype.J;D.prototype.p=function(b){this.set("extent",b)};D.prototype.setExtent=D.prototype.p;D.prototype.Q=function(b){this.set("maxResolution",b)};D.prototype.setMaxResolution=D.prototype.Q;D.prototype.V=function(b){this.set("minResolution",b)};D.prototype.setMinResolution=D.prototype.V;D.prototype.q=function(b){this.set("opacity",b)}; -D.prototype.setOpacity=D.prototype.q;D.prototype.ba=function(b){this.set("saturation",b)};D.prototype.setSaturation=D.prototype.ba;D.prototype.ca=function(b){this.set("visible",b)};D.prototype.setVisible=D.prototype.ca;function E(b){var c=Cb(b);delete c.source;D.call(this,c);this.Ea=null;z(this,xd("source"),this.Yd,!1,this);this.ga(m(b.source)?b.source:null)}u(E,D);E.prototype.Da=function(b){b=m(b)?b:[];b.push(sj(this));return b};E.prototype.a=function(){var b=this.get("source");return m(b)?b:null};E.prototype.getSource=E.prototype.a;E.prototype.Ta=function(){var b=this.a();return null===b?"undefined":b.n};E.prototype.Zd=function(){this.l()}; -E.prototype.Yd=function(){null!==this.Ea&&(Yc(this.Ea),this.Ea=null);var b=this.a();null!==b&&(this.Ea=z(b,"change",this.Zd,!1,this));this.l()};E.prototype.ga=function(b){this.set("source",b)};E.prototype.setSource=E.prototype.ga;function tj(b,c,d,e,f){jd.call(this);this.f=f;this.extent=b;this.e=d;this.resolution=c;this.state=e}u(tj,jd);tj.prototype.D=function(){return this.extent};function uj(b,c){jd.call(this);this.a=b;this.state=c}u(uj,jd);function vj(b){b.dispatchEvent("change")}uj.prototype.mb=function(){return ma(this).toString()};uj.prototype.f=function(){return this.a};function wj(b){this.minZoom=m(b.minZoom)?b.minZoom:0;this.a=b.resolutions;this.maxZoom=this.a.length-1;this.d=m(b.origin)?b.origin:null;this.f=null;m(b.origins)&&(this.f=b.origins);this.c=null;m(b.tileSizes)&&(this.c=b.tileSizes);this.e=m(b.tileSize)?b.tileSize:null===this.c?256:void 0}var xj=[0,0,0];l=wj.prototype;l.Bb=function(){return ed};l.$c=function(b,c,d,e,f){f=yj(this,b,f);for(b=b[0]-1;b>=this.minZoom;){if(c.call(d,b,zj(this,f,b,e)))return!0;--b}return!1};l.ed=function(){return this.maxZoom}; -l.fd=function(){return this.minZoom};l.Lb=function(b){return null===this.d?this.f[b]:this.d};l.ka=function(b){return this.a[b]};l.Fd=function(){return this.a};l.kd=function(b,c,d){return b[0]<this.maxZoom?(d=yj(this,b,d),zj(this,d,b[0]+1,c)):null};function Aj(b,c,d,e){Bj(b,c[0],c[1],d,!1,xj);var f=xj[1],g=xj[2];Bj(b,c[2],c[3],d,!0,xj);return pf(f,xj[1],g,xj[2],e)}function zj(b,c,d,e){return Aj(b,c,b.ka(d),e)} -function Cj(b,c){var d=b.Lb(c[0]),e=b.ka(c[0]),f=b.sa(c[0]);return[d[0]+(c[1]+.5)*f*e,d[1]+(c[2]+.5)*f*e]}function yj(b,c,d){var e=b.Lb(c[0]),f=b.ka(c[0]);b=b.sa(c[0]);var g=e[0]+c[1]*b*f;c=e[1]+c[2]*b*f;return Xd(g,c,g+b*f,c+b*f,d)}l.Vb=function(b,c,d){return Bj(this,b[0],b[1],c,!1,d)};function Bj(b,c,d,e,f,g){var h=ec(b.a,e,0),k=e/b.ka(h),n=b.Lb(h);b=b.sa(h);c=k*(c-n[0])/(e*b);d=k*(d-n[1])/(e*b);f?(c=Math.ceil(c)-1,d=Math.ceil(d)-1):(c=Math.floor(c),d=Math.floor(d));return kf(h,c,d,g)} -l.Fc=function(b,c,d){return Bj(this,b[0],b[1],this.ka(c),!1,d)};l.sa=function(b){return m(this.e)?this.e:this.c[b]};function Dj(b,c,d){c=m(c)?c:42;d=m(d)?d:256;b=Math.max(re(b)/d,oe(b)/d);c+=1;d=Array(c);for(var e=0;e<c;++e)d[e]=b/Math.pow(2,e);return d}function Ej(b){b=Ee(b);var c=b.D();null===c&&(b=180*Ae.degrees/b.ie(),c=Xd(-b,-b,b,b));return c};function Fj(b){qj.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state});this.t=m(b.opaque)?b.opaque:!1;this.F=m(b.tilePixelRatio)?b.tilePixelRatio:1;this.tileGrid=m(b.tileGrid)?b.tileGrid:null}u(Fj,qj);l=Fj.prototype;l.zd=cd;l.fe=function(b,c,d,e){var f=!0,g,h,k,n;for(k=e.a;k<=e.d;++k)for(n=e.b;n<=e.c;++n)h=this.hb(d,k,n),b[d]&&b[d][h]||(g=c(d,k,n),null===g?f=!1:(b[d]||(b[d]={}),b[d][h]=g));return f};l.bd=function(){return 0};l.hb=lf;l.za=function(){return this.tileGrid}; -function Gj(b,c){var d;if(null===b.tileGrid){if(d=c.e,null===d){d=Ej(c);var e=m(void 0)?void 0:256,f=m(void 0)?void 0:"bottom-left",g=Dj(d,void 0,e);d=new wj({origin:le(d,f),resolutions:g,tileSize:e});c.e=d}}else d=b.tileGrid;return d}l.Gc=function(b,c,d){return Gj(this,d).sa(b)*this.F};l.He=ca;function Hj(b,c){pc.call(this);this.b=b;this.a=c}u(Hj,pc);Hj.prototype.Zb=ca;Hj.prototype.o=function(b){2===b.target.state&&Ij(this)};function Ij(b){var c=b.a;c.b()&&"ready"==c.Ta()&&b.b.e.render()}function Jj(b,c){c.zd()&&b.postRenderFunctions.push(ta(function(b,c,f){c=ma(b).toString();b.se(f.usedTiles[c])},c))}function Kj(b,c){if(null!=c){var d,e,f;e=0;for(f=c.length;e<f;++e)d=c[e],b[ma(d).toString()]=d}}function Lj(b,c){var d=c.r;m(d)&&(ia(d)?b.logos[d]="":la(d)&&(b.logos[d.src]=d.href))} -function Mj(b,c,d,e){c=ma(c).toString();d=d.toString();c in b?d in b[c]?(b=b[c][d],e.a<b.a&&(b.a=e.a),e.d>b.d&&(b.d=e.d),e.b<b.b&&(b.b=e.b),e.c>b.c&&(b.c=e.c)):b[c][d]=e:(b[c]={},b[c][d]=e)}function Nj(b,c,d,e){return function(f,g,h){f=c.Fb(f,g,h,d,e);return b(f)?f:null}}function Oj(b,c,d){return[c*(Math.round(b[0]/c)+d[0]%2/2),c*(Math.round(b[1]/c)+d[1]%2/2)]} -function Pj(b,c,d,e,f,g,h,k,n,p){var q=ma(c).toString();q in b.wantedTiles||(b.wantedTiles[q]={});var r=b.wantedTiles[q];b=b.tileQueue;var s=d.minZoom,v,y,C,F,G,w;m(k)||(k=0);for(w=h;w>=s;--w)for(y=zj(d,g,w,y),C=d.ka(w),F=y.a;F<=y.d;++F)for(G=y.b;G<=y.c;++G)h-w<=k?(v=c.Fb(w,F,G,e,f),0==v.state&&(r[nf(v.a)]=!0,v.mb()in b.b||Qj(b,[v,q,Cj(d,v.a),C])),m(n)&&n.call(p,v)):c.He(w,F,G)};function Rj(b){this.p=b.opacity;this.q=b.rotateWithView;this.i=b.rotation;this.n=b.scale;this.r=b.snapToPixel}l=Rj.prototype;l.Ad=function(){return this.p};l.hd=function(){return this.q};l.Bd=function(){return this.i};l.Cd=function(){return this.n};l.jd=function(){return this.r};l.Dd=function(b){this.i=b};l.Ed=function(b){this.n=b};function Sj(b){b=m(b)?b:{};this.e=m(b.anchor)?b.anchor:[.5,.5];this.d=null;this.c=m(b.anchorOrigin)?b.anchorOrigin:"top-left";this.g=m(b.anchorXUnits)?b.anchorXUnits:"fraction";this.o=m(b.anchorYUnits)?b.anchorYUnits:"fraction";var c=m(b.crossOrigin)?b.crossOrigin:null,d=m(b.img)?b.img:null,e=b.src;m(e)&&0!==e.length||null===d||(e=d.src);var f=m(b.src)?0:2,g=Tj.Ja(),h=g.get(e,c);null===h&&(h=new Uj(d,e,c,f),g.set(e,c,h));this.a=h;this.t=m(b.offset)?b.offset:[0,0];this.b=m(b.offsetOrigin)?b.offsetOrigin: -"top-left";this.f=null;this.j=m(b.size)?b.size:null;Rj.call(this,{opacity:m(b.opacity)?b.opacity:1,rotation:m(b.rotation)?b.rotation:0,scale:m(b.scale)?b.scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0,rotateWithView:m(b.rotateWithView)?b.rotateWithView:!1})}u(Sj,Rj);l=Sj.prototype; -l.tb=function(){if(null!==this.d)return this.d;var b=this.e,c=this.ab();if("fraction"==this.g||"fraction"==this.o){if(null===c)return null;b=this.e.slice();"fraction"==this.g&&(b[0]*=c[0]);"fraction"==this.o&&(b[1]*=c[1])}if("top-left"!=this.c){if(null===c)return null;b===this.e&&(b=this.e.slice());if("top-right"==this.c||"bottom-right"==this.c)b[0]=-b[0]+c[0];if("bottom-left"==this.c||"bottom-right"==this.c)b[1]=-b[1]+c[1]}return this.d=b};l.yb=function(){return this.a.a};l.cd=function(){return this.a.c}; -l.ue=function(){return this.a.b};l.te=function(){var b=this.a;if(null===b.e)if(b.o){var c=b.c[0],d=b.c[1],e=Tf(c,d);e.fillRect(0,0,c,d);b.e=e.canvas}else b.e=b.a;return b.e};l.zb=function(){if(null!==this.f)return this.f;var b=this.t;if("top-left"!=this.b){var c=this.ab(),d=this.a.c;if(null===c||null===d)return null;b=b.slice();if("top-right"==this.b||"bottom-right"==this.b)b[0]=d[0]-c[0]-b[0];if("bottom-left"==this.b||"bottom-right"==this.b)b[1]=d[1]-c[1]-b[1]}return this.f=b};l.Pj=function(){return this.a.f}; -l.ab=function(){return null===this.j?this.a.c:this.j};l.ne=function(b,c){return z(this.a,"change",b,!1,c)};l.load=function(){this.a.load()};l.Ge=function(b,c){Xc(this.a,"change",b,!1,c)};function Uj(b,c,d,e){jd.call(this);this.e=null;this.a=null===b?new Image:b;null!==d&&(this.a.crossOrigin=d);this.d=null;this.b=e;this.c=null;this.f=c;this.o=!1}u(Uj,jd);Uj.prototype.g=function(){this.b=3;Ta(this.d,Yc);this.d=null;this.dispatchEvent("change")}; -Uj.prototype.i=function(){this.b=2;this.c=[this.a.width,this.a.height];Ta(this.d,Yc);this.d=null;var b=Tf(1,1);b.drawImage(this.a,0,0);try{b.getImageData(0,0,1,1)}catch(c){this.o=!0}this.dispatchEvent("change")};Uj.prototype.load=function(){if(0==this.b){this.b=1;this.d=[Wc(this.a,"error",this.g,!1,this),Wc(this.a,"load",this.i,!1,this)];try{this.a.src=this.f}catch(b){this.g()}}};function Tj(){this.a={};this.c=0}da(Tj);Tj.prototype.clear=function(){this.a={};this.c=0}; -Tj.prototype.get=function(b,c){var d=c+":"+b;return d in this.a?this.a[d]:null};Tj.prototype.set=function(b,c,d){this.a[c+":"+b]=d;++this.c};function Vj(b,c,d,e,f,g,h,k){Od(b);0===c&&0===d||Qd(b,c,d);1==e&&1==f||Rd(b,e,f);0!==g&&Sd(b,g);0===h&&0===k||Qd(b,h,k);return b}function Wj(b,c){return b[0]==c[0]&&b[1]==c[1]&&b[4]==c[4]&&b[5]==c[5]&&b[12]==c[12]&&b[13]==c[13]}function Xj(b,c,d){var e=b[1],f=b[5],g=b[13],h=c[0];c=c[1];d[0]=b[0]*h+b[4]*c+b[12];d[1]=e*h+f*c+g;return d};function Yj(b,c){pc.call(this);this.e=c;this.g=null;this.b={}}u(Yj,pc); -function Zj(b){var c=b.viewState,d=b.coordinateToPixelMatrix;Vj(d,b.size[0]/2,b.size[1]/2,1/c.resolution,-1/c.resolution,-c.rotation,-c.center[0],-c.center[1]);b=b.pixelToCoordinateMatrix;var c=d[0],e=d[1],f=d[2],g=d[3],h=d[4],k=d[5],n=d[6],p=d[7],q=d[8],r=d[9],s=d[10],v=d[11],y=d[12],C=d[13],F=d[14],d=d[15],G=c*k-e*h,w=c*n-f*h,U=c*p-g*h,N=e*n-f*k,Y=e*p-g*k,T=f*p-g*n,qa=q*C-r*y,vb=q*F-s*y,Ka=q*d-v*y,ac=r*F-s*C,Sb=r*d-v*C,La=s*d-v*F,Pa=G*La-w*Sb+U*ac+N*Ka-Y*vb+T*qa;0!=Pa&&(Pa=1/Pa,b[0]=(k*La-n*Sb+ -p*ac)*Pa,b[1]=(-e*La+f*Sb-g*ac)*Pa,b[2]=(C*T-F*Y+d*N)*Pa,b[3]=(-r*T+s*Y-v*N)*Pa,b[4]=(-h*La+n*Ka-p*vb)*Pa,b[5]=(c*La-f*Ka+g*vb)*Pa,b[6]=(-y*T+F*U-d*w)*Pa,b[7]=(q*T-s*U+v*w)*Pa,b[8]=(h*Sb-k*Ka+p*qa)*Pa,b[9]=(-c*Sb+e*Ka-g*qa)*Pa,b[10]=(y*Y-C*U+d*G)*Pa,b[11]=(-q*Y+r*U-v*G)*Pa,b[12]=(-h*ac+k*vb-n*qa)*Pa,b[13]=(c*ac-e*vb+f*qa)*Pa,b[14]=(-y*N+C*w-F*G)*Pa,b[15]=(q*N-r*w+s*G)*Pa)}Yj.prototype.Yc=function(b){return new Hj(this,b)};Yj.prototype.M=function(){ob(this.b,tc);Yj.S.M.call(this)}; -function ak(){var b=Tj.Ja();if(32<b.c){var c=0,d,e;for(d in b.a){e=b.a[d];var f;if(f=0===(c++&3))Ec(e)?e=ld(e,void 0,void 0):(e=Sc(e),e=!!e&&Mc(e,void 0,void 0)),f=!e;f&&(delete b.a[d],--b.c)}}} -function bk(b,c,d,e,f,g,h){var k,n=d.viewState,p=n.resolution,n=n.rotation;if(null!==b.g){var q={};if(k=ck(b.g,p,n,c,{},function(b){var c=ma(b).toString();if(!(c in q))return q[c]=!0,e.call(f,b,null)}))return k}var n=b.e.Eb().Da(),r;for(r=n.length-1;0<=r;--r){k=n[r];var s=k.layer;if(k.visible&&p>=k.minResolution&&p<k.maxResolution&&g.call(h,s)&&(k=dk(b,s).Zb(c,d,e,f)))return k}}function dk(b,c){var d=ma(c).toString();if(d in b.b)return b.b[d];var e=b.Yc(c);return b.b[d]=e}Yj.prototype.Ld=ca; -Yj.prototype.t=function(b,c){for(var d in this.b)if(!(null!==c&&d in c.layerStates)){var e=this.b[d];delete this.b[d];tc(e)}};function ek(b,c){for(var d in b.b)if(!(d in c.layerStates)){c.postRenderFunctions.push(sa(b.t,b));break}};function fk(b,c){this.f=b;this.e=c;this.a=[];this.c=[];this.b={}}fk.prototype.clear=function(){this.a.length=0;this.c.length=0;yb(this.b)};function gk(b){var c=b.a,d=b.c,e=c[0];1==c.length?(c.length=0,d.length=0):(c[0]=c.pop(),d[0]=d.pop(),hk(b,0));c=b.e(e);delete b.b[c];return e}function Qj(b,c){var d=b.f(c);Infinity!=d&&(b.a.push(c),b.c.push(d),b.b[b.e(c)]=!0,ik(b,0,b.a.length-1))}fk.prototype.Ub=function(){return this.a.length};fk.prototype.ia=function(){return 0===this.a.length}; -function hk(b,c){for(var d=b.a,e=b.c,f=d.length,g=d[c],h=e[c],k=c;c<f>>1;){var n=2*c+1,p=2*c+2,n=p<f&&e[p]<e[n]?p:n;d[c]=d[n];e[c]=e[n];c=n}d[c]=g;e[c]=h;ik(b,k,c)}function ik(b,c,d){var e=b.a;b=b.c;for(var f=e[d],g=b[d];d>c;){var h=d-1>>1;if(b[h]>g)e[d]=e[h],b[d]=b[h],d=h;else break}e[d]=f;b[d]=g}function jk(b){var c=b.f,d=b.a,e=b.c,f=0,g=d.length,h,k,n;for(k=0;k<g;++k)h=d[k],n=c(h),Infinity==n?delete b.b[b.e(h)]:(e[f]=n,d[f++]=h);d.length=f;e.length=f;for(c=(b.a.length>>1)-1;0<=c;c--)hk(b,c)};function kk(b,c){fk.call(this,function(c){return b.apply(null,c)},function(b){return b[0].mb()});this.o=c;this.d=0}u(kk,fk);kk.prototype.g=function(){--this.d;this.o()};function lk(b,c,d){this.d=b;this.b=c;this.f=d;this.a=[];this.c=this.e=0}lk.prototype.update=function(b,c){this.a.push(b,c,ua())};function mk(b,c){var d=b.d,e=b.c,f=b.b-e,g=nk(b);return gf({source:c,duration:g,easing:function(b){return e*(Math.exp(d*b*g)-1)/f}})}function nk(b){return Math.log(b.b/b.c)/b.d};function ok(b){td.call(this);this.n=null;this.b(!0);this.handleEvent=b.handleEvent}u(ok,td);ok.prototype.a=function(){return this.get("active")};ok.prototype.getActive=ok.prototype.a;ok.prototype.b=function(b){this.set("active",b)};ok.prototype.setActive=ok.prototype.b;ok.prototype.setMap=function(b){this.n=b};function pk(b,c,d,e,f){if(null!=d){var g=c.e(),h=c.a();m(g)&&m(h)&&m(f)&&0<f&&(b.Ua(hf({rotation:g,duration:f,easing:cf})),m(e)&&b.Ua(gf({source:h,duration:f,easing:cf})));c.rotate(d,e)}} -function qk(b,c,d,e,f){var g=c.b();d=c.constrainResolution(g,d,0);rk(b,c,d,e,f)}function rk(b,c,d,e,f){if(null!=d){var g=c.b(),h=c.a();m(g)&&m(h)&&m(f)&&0<f&&(b.Ua(jf({resolution:g,duration:f,easing:cf})),m(e)&&b.Ua(gf({source:h,duration:f,easing:cf})));if(null!=e){var k;b=c.a();f=c.b();m(b)&&m(f)&&(k=[e[0]-d*(e[0]-b[0])/f,e[1]-d*(e[1]-b[1])/f]);c.Oa(k)}c.d(d)}};function sk(b){b=m(b)?b:{};this.d=m(b.delta)?b.delta:1;ok.call(this,{handleEvent:tk});this.e=m(b.duration)?b.duration:250}u(sk,ok);function tk(b){var c=!1,d=b.a;if(b.type==hj){var c=b.map,e=b.coordinate,d=d.d?-this.d:this.d,f=c.a();qk(c,f,d,e,this.e);b.preventDefault();c=!0}return!c};function uk(b){b=b.a;return b.c&&!b.g&&b.d}function vk(b){return"mousemove"==b.originalEvent.type}function wk(b){return b.type==ij}function xk(b){b=b.a;return!b.c&&!b.g&&!b.d}function yk(b){b=b.a;return!b.c&&!b.g&&b.d}function zk(b){b=b.a.target.tagName;return"INPUT"!==b&&"SELECT"!==b&&"TEXTAREA"!==b}function Ak(b){return 1==b.c.pointerId};function Bk(b){b=m(b)?b:{};ok.call(this,{handleEvent:m(b.handleEvent)?b.handleEvent:Ck});this.Da=m(b.handleDownEvent)?b.handleDownEvent:cd;this.oa=m(b.handleDragEvent)?b.handleDragEvent:ca;this.Ea=m(b.handleMoveEvent)?b.handleMoveEvent:ca;this.qa=m(b.handleUpEvent)?b.handleUpEvent:cd;this.p=!1;this.t={};this.e=[]}u(Bk,ok);function Dk(b){for(var c=b.length,d=0,e=0,f=0;f<c;f++)d+=b[f].clientX,e+=b[f].clientY;return[d/c,e/c]} -function Ck(b){if(!(b instanceof dj))return!0;var c=!1,d=b.type;if(d===mj||d===oj||d===kj)d=b.c,b.type==kj?delete this.t[d.pointerId]:b.type==mj?this.t[d.pointerId]=d:d.pointerId in this.t&&(this.t[d.pointerId]=d),this.e=rb(this.t);this.p&&(b.type==oj?this.oa(b):b.type==kj&&(this.p=this.qa(b)));b.type==mj?(this.p=b=this.Da(b),c=this.q(b)):b.type==nj&&this.Ea(b);return!c}Bk.prototype.q=ed;function Ek(b){Bk.call(this,{handleDownEvent:Fk,handleDragEvent:Gk,handleUpEvent:Hk});b=m(b)?b:{};this.d=b.kinetic;this.f=this.g=null;this.j=m(b.condition)?b.condition:xk;this.i=!1}u(Ek,Bk);function Gk(b){var c=Dk(this.e);this.d&&this.d.update(c[0],c[1]);if(null!==this.f){var d=this.f[0]-c[0],e=c[1]-this.f[1];b=b.map;var f=b.a(),g=$e(f),e=d=[d,e],h=g.resolution;e[0]*=h;e[1]*=h;Dd(d,g.rotation);yd(d,g.center);d=f.i(d);b.render();f.Oa(d)}this.f=c} -function Hk(b){b=b.map;var c=b.a();if(0===this.e.length){var d;if(d=!this.i&&this.d)if(d=this.d,6>d.a.length)d=!1;else{var e=ua()-d.f,f=d.a.length-3;if(d.a[f+2]<e)d=!1;else{for(var g=f-3;0<g&&d.a[g+2]>e;)g-=3;var e=d.a[f+2]-d.a[g+2],h=d.a[f]-d.a[g],f=d.a[f+1]-d.a[g+1];d.e=Math.atan2(f,h);d.c=Math.sqrt(h*h+f*f)/e;d=d.c>d.b}}d&&(d=this.d,d=(d.b-d.c)/d.d,f=this.d.e,g=c.a(),this.g=mk(this.d,g),b.Ua(this.g),g=b.f(g),d=b.Ga([g[0]-d*Math.cos(f),g[1]-d*Math.sin(f)]),d=c.i(d),c.Oa(d));bf(c,-1);b.render(); -return!1}this.f=null;return!0}function Fk(b){if(0<this.e.length&&this.j(b)){var c=b.map,d=c.a();this.f=null;this.p||bf(d,1);c.render();null!==this.g&&ab(c.F,this.g)&&(d.Oa(b.frameState.viewState.center),this.g=null);this.d&&(b=this.d,b.a.length=0,b.e=0,b.c=0);this.i=1<this.e.length;return!0}return!1}Ek.prototype.q=cd;function Ik(b){b=m(b)?b:{};Bk.call(this,{handleDownEvent:Jk,handleDragEvent:Kk,handleUpEvent:Lk});this.f=m(b.condition)?b.condition:uk;this.d=void 0}u(Ik,Bk);function Kk(b){if(Ak(b)){var c=b.map,d=c.e();b=b.pixel;d=Math.atan2(d[1]/2-b[1],b[0]-d[0]/2);if(m(this.d)){b=d-this.d;var e=c.a(),f=$e(e);c.render();pk(c,e,f.rotation-b)}this.d=d}}function Lk(b){if(!Ak(b))return!0;b=b.map;var c=b.a();bf(c,-1);var d=$e(c).rotation,d=c.constrainRotation(d,0);pk(b,c,d,void 0,250);return!1} -function Jk(b){return Ak(b)&&Cc(b.a)&&this.f(b)?(b=b.map,bf(b.a(),1),b.render(),this.d=void 0,!0):!1}Ik.prototype.q=cd;function Mk(){md.call(this);this.extent=void 0;this.g=-1;this.o={};this.j=this.i=0}u(Mk,md);Mk.prototype.f=function(b,c){var d=m(c)?c:[NaN,NaN];this.Va(b[0],b[1],d,Infinity);return d};Mk.prototype.Jb=cd;Mk.prototype.e=function(b,c){this.ma(Ve(b,c));return this};function Nk(b,c,d,e,f,g){var h=f[0],k=f[1],n=f[4],p=f[5],q=f[12];f=f[13];for(var r=m(g)?g:[],s=0;c<d;c+=e){var v=b[c],y=b[c+1];r[s++]=h*v+n*y+q;r[s++]=k*v+p*y+f}m(g)&&r.length!=s&&(r.length=s);return r};function Ok(){Mk.call(this);this.a="XY";this.s=2;this.k=null}u(Ok,Mk);function Pk(b){if("XY"==b)return 2;if("XYZ"==b||"XYM"==b)return 3;if("XYZM"==b)return 4}l=Ok.prototype;l.Jb=cd;l.D=function(b){if(this.g!=this.c){var c=this.k,d=this.k.length,e=this.s,f=Xd(Infinity,Infinity,-Infinity,-Infinity,this.extent);this.extent=fe(f,c,0,d,e);this.g=this.c}return te(this.extent,b)};l.vb=function(){return this.k.slice(0,this.s)};l.wb=function(){return this.k.slice(this.k.length-this.s)};l.xb=function(){return this.a}; -l.ke=function(b){this.j!=this.c&&(yb(this.o),this.i=0,this.j=this.c);if(0>b||0!==this.i&&b<=this.i)return this;var c=b.toString();if(this.o.hasOwnProperty(c))return this.o[c];var d=this.mc(b);if(d.k.length<this.k.length)return this.o[c]=d;this.i=b;return this};l.mc=function(){return this};function Qk(b,c,d){b.s=Pk(c);b.a=c;b.k=d} -function Rk(b,c,d,e){if(m(c))d=Pk(c);else{for(c=0;c<e;++c){if(0===d.length){b.a="XY";b.s=2;return}d=d[0]}d=d.length;c=2==d?"XY":3==d?"XYZ":4==d?"XYZM":void 0}b.a=c;b.s=d}l.ma=function(b){null!==this.k&&(b(this.k,this.k,this.s),this.l())};l.Aa=function(b,c){var d=this.k;if(null!==d){var e=d.length,f=this.s,g=m(d)?d:[],h=0,k,n;for(k=0;k<e;k+=f)for(g[h++]=d[k]+b,g[h++]=d[k+1]+c,n=k+2;n<k+f;++n)g[h++]=d[n];m(d)&&g.length!=h&&(g.length=h);this.l()}};function Sk(b,c,d,e){for(var f=0,g=b[d-e],h=b[d-e+1];c<d;c+=e)var k=b[c],n=b[c+1],f=f+(h*k-g*n),g=k,h=n;return f/2}function Tk(b,c,d,e){var f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],f=f+Sk(b,c,k,e);c=k}return f};function Uk(b,c,d,e,f,g){var h=f-d,k=g-e;if(0!==h||0!==k){var n=((b-d)*h+(c-e)*k)/(h*h+k*k);1<n?(d=f,e=g):0<n&&(d+=h*n,e+=k*n)}return Vk(b,c,d,e)}function Vk(b,c,d,e){b=d-b;c=e-c;return b*b+c*c};function Wk(b,c,d,e,f,g,h){var k=b[c],n=b[c+1],p=b[d]-k,q=b[d+1]-n;if(0!==p||0!==q)if(g=((f-k)*p+(g-n)*q)/(p*p+q*q),1<g)c=d;else if(0<g){for(f=0;f<e;++f)h[f]=$b(b[c+f],b[d+f],g);h.length=e;return}for(f=0;f<e;++f)h[f]=b[c+f];h.length=e}function Xk(b,c,d,e,f){var g=b[c],h=b[c+1];for(c+=e;c<d;c+=e){var k=b[c],n=b[c+1],g=Vk(g,h,k,n);g>f&&(f=g);g=k;h=n}return f}function Yk(b,c,d,e,f){var g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];f=Xk(b,c,k,e,f);c=k}return f} -function Zk(b,c,d,e,f,g,h,k,n,p,q){if(c==d)return p;var r;if(0===f){r=Vk(h,k,b[c],b[c+1]);if(r<p){for(q=0;q<e;++q)n[q]=b[c+q];n.length=e;return r}return p}for(var s=m(q)?q:[NaN,NaN],v=c+e;v<d;)if(Wk(b,v-e,v,e,h,k,s),r=Vk(h,k,s[0],s[1]),r<p){p=r;for(q=0;q<e;++q)n[q]=s[q];n.length=e;v+=e}else v+=e*Math.max((Math.sqrt(r)-Math.sqrt(p))/f|0,1);if(g&&(Wk(b,d-e,c,e,h,k,s),r=Vk(h,k,s[0],s[1]),r<p)){p=r;for(q=0;q<e;++q)n[q]=s[q];n.length=e}return p} -function $k(b,c,d,e,f,g,h,k,n,p,q){q=m(q)?q:[NaN,NaN];var r,s;r=0;for(s=d.length;r<s;++r){var v=d[r];p=Zk(b,c,v,e,f,g,h,k,n,p,q);c=v}return p};function al(b,c){var d=0,e,f;e=0;for(f=c.length;e<f;++e)b[d++]=c[e];return d}function bl(b,c,d,e){var f,g;f=0;for(g=d.length;f<g;++f){var h=d[f],k;for(k=0;k<e;++k)b[c++]=h[k]}return c}function cl(b,c,d,e,f){f=m(f)?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h)c=bl(b,c,d[h],e),f[g++]=c;f.length=g;return f};function dl(b,c,d,e,f){f=m(f)?f:[];for(var g=0;c<d;c+=e)f[g++]=b.slice(c,c+e);f.length=g;return f}function el(b,c,d,e,f){f=m(f)?f:[];var g=0,h,k;h=0;for(k=d.length;h<k;++h){var n=d[h];f[g++]=dl(b,c,n,e,f[g]);c=n}f.length=g;return f};function fl(b,c,d,e,f,g,h){var k=(d-c)/e;if(3>k){for(;c<d;c+=e)g[h++]=b[c],g[h++]=b[c+1];return h}var n=Array(k);n[0]=1;n[k-1]=1;d=[c,d-e];for(var p=0,q;0<d.length;){var r=d.pop(),s=d.pop(),v=0,y=b[s],C=b[s+1],F=b[r],G=b[r+1];for(q=s+e;q<r;q+=e){var w=Uk(b[q],b[q+1],y,C,F,G);w>v&&(p=q,v=w)}v>f&&(n[(p-c)/e]=1,s+e<p&&d.push(s,p),p+e<r&&d.push(p,r))}for(q=0;q<k;++q)n[q]&&(g[h++]=b[c+q*e],g[h++]=b[c+q*e+1]);return h} -function gl(b,c,d,e,f,g,h,k){var n,p;n=0;for(p=d.length;n<p;++n){var q=d[n];a:{var r=b,s=q,v=e,y=f,C=g;if(c!=s){var F=y*Math.round(r[c]/y),G=y*Math.round(r[c+1]/y);c+=v;C[h++]=F;C[h++]=G;var w=void 0,U=void 0;do if(w=y*Math.round(r[c]/y),U=y*Math.round(r[c+1]/y),c+=v,c==s){C[h++]=w;C[h++]=U;break a}while(w==F&&U==G);for(;c<s;){var N,Y;N=y*Math.round(r[c]/y);Y=y*Math.round(r[c+1]/y);c+=v;if(N!=w||Y!=U){var T=w-F,qa=U-G,vb=N-F,Ka=Y-G;T*Ka==qa*vb&&(0>T&&vb<T||T==vb||0<T&&vb>T)&&(0>qa&&Ka<qa||qa==Ka|| -0<qa&&Ka>qa)||(C[h++]=w,C[h++]=U,F=w,G=U);w=N;U=Y}}C[h++]=w;C[h++]=U}}k.push(h);c=q}return h};function hl(b,c){Ok.call(this);this.b=this.n=-1;this.U(b,c)}u(hl,Ok);l=hl.prototype;l.clone=function(){var b=new hl(null);il(b,this.a,this.k.slice());return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.b!=this.c&&(this.n=Math.sqrt(Xk(this.k,0,this.k.length,this.s,0)),this.b=this.c);return Zk(this.k,0,this.k.length,this.s,this.n,!0,b,c,d,e)};l.nj=function(){return Sk(this.k,0,this.k.length,this.s)};l.K=function(){return dl(this.k,0,this.k.length,this.s)}; -l.mc=function(b){var c=[];c.length=fl(this.k,0,this.k.length,this.s,b,c,0);b=new hl(null);il(b,"XY",c);return b};l.H=function(){return"LinearRing"};l.U=function(b,c){null===b?il(this,"XY",null):(Rk(this,c,b,1),null===this.k&&(this.k=[]),this.k.length=bl(this.k,0,b,this.s),this.l())};function il(b,c,d){Qk(b,c,d);b.l()};function jl(b,c){Ok.call(this);this.U(b,c)}u(jl,Ok);l=jl.prototype;l.clone=function(){var b=new jl(null);kl(b,this.a,this.k.slice());return b};l.Va=function(b,c,d,e){var f=this.k;b=Vk(b,c,f[0],f[1]);if(b<e){e=this.s;for(c=0;c<e;++c)d[c]=f[c];d.length=e;return b}return e};l.K=function(){return null===this.k?[]:this.k.slice()};l.D=function(b){this.g!=this.c&&(this.extent=ce(this.k,this.extent),this.g=this.c);return te(this.extent,b)};l.H=function(){return"Point"}; -l.ha=function(b){return ae(b,this.k[0],this.k[1])};l.U=function(b,c){null===b?kl(this,"XY",null):(Rk(this,c,b,0),null===this.k&&(this.k=[]),this.k.length=al(this.k,b),this.l())};function kl(b,c,d){Qk(b,c,d);b.l()};function ll(b,c,d,e,f){return!ge(f,function(f){return!ml(b,c,d,e,f[0],f[1])})}function ml(b,c,d,e,f,g){for(var h=!1,k=b[d-e],n=b[d-e+1];c<d;c+=e){var p=b[c],q=b[c+1];n>g!=q>g&&f<(p-k)*(g-n)/(q-n)+k&&(h=!h);k=p;n=q}return h}function nl(b,c,d,e,f,g){if(0===d.length||!ml(b,c,d[0],e,f,g))return!1;var h;c=1;for(h=d.length;c<h;++c)if(ml(b,d[c-1],d[c],e,f,g))return!1;return!0};function pl(b,c,d,e,f,g,h){var k,n,p,q,r,s=f[g+1],v=[],y=d[0];p=b[y-e];r=b[y-e+1];for(k=c;k<y;k+=e){q=b[k];n=b[k+1];if(s<=r&&n<=s||r<=s&&s<=n)p=(s-r)/(n-r)*(q-p)+p,v.push(p);p=q;r=n}y=NaN;r=-Infinity;v.sort();p=v[0];k=1;for(n=v.length;k<n;++k){q=v[k];var C=Math.abs(q-p);C>r&&(p=(p+q)/2,nl(b,c,d,e,p,s)&&(y=p,r=C));p=q}isNaN(y)&&(y=f[g]);return m(h)?(h.push(y,s),h):[y,s]};function ql(b,c,d,e,f){for(var g=[b[c],b[c+1]],h=[],k;c+e<d;c+=e){h[0]=b[c+e];h[1]=b[c+e+1];if(k=f(g,h))return k;g[0]=h[0];g[1]=h[1]}return!1};function rl(b,c,d,e,f){var g=fe(Vd(),b,c,d,e);return qe(f,g)?$d(f,g)||g[0]>=f[0]&&g[2]<=f[2]||g[1]>=f[1]&&g[3]<=f[3]?!0:ql(b,c,d,e,function(b,c){var d=!1,e=be(f,b),g=be(f,c);if(1===e||1===g)d=!0;else{var r=f[0],s=f[1],v=f[2],y=f[3],C=c[0],F=c[1],G=(F-b[1])/(C-b[0]);g&2&&!(e&2)?(s=C-(F-y)/G,d=s>=r&&s<=v):g&4&&!(e&4)?(r=F-(C-v)*G,d=r>=s&&r<=y):g&8&&!(e&8)?(s=C-(F-s)/G,d=s>=r&&s<=v):g&16&&!(e&16)&&(r=F-(C-r)*G,d=r>=s&&r<=y)}return d}):!1} -function sl(b,c,d,e,f){var g=d[0];if(!(rl(b,c,g,e,f)||ml(b,c,g,e,f[0],f[1])||ml(b,c,g,e,f[0],f[3])||ml(b,c,g,e,f[2],f[1])||ml(b,c,g,e,f[2],f[3])))return!1;if(1===d.length)return!0;c=1;for(g=d.length;c<g;++c)if(ll(b,d[c-1],d[c],e,f))return!1;return!0};function tl(b,c,d,e){for(var f=0,g=b[d-e],h=b[d-e+1];c<d;c+=e)var k=b[c],n=b[c+1],f=f+(k-g)*(n+h),g=k,h=n;return 0<f}function ul(b,c,d){var e=0,f,g;f=0;for(g=c.length;f<g;++f){var h=c[f],e=tl(b,e,h,d);if(0===f?!e:e)return!1;e=h}return!0}function vl(b,c,d,e){var f,g;f=0;for(g=d.length;f<g;++f){var h=d[f],k=tl(b,c,h,e);if(0===f?!k:k)for(var k=b,n=h,p=e;c<n-p;){var q;for(q=0;q<p;++q){var r=k[c+q];k[c+q]=k[n-p+q];k[n-p+q]=r}c+=p;n-=p}c=h}return c};function H(b,c){Ok.call(this);this.b=[];this.p=-1;this.q=null;this.F=this.r=this.t=-1;this.n=null;this.U(b,c)}u(H,Ok);l=H.prototype;l.Ug=function(b){null===this.k?this.k=b.k.slice():db(this.k,b.k);this.b.push(this.k.length);this.l()};l.clone=function(){var b=new H(null);wl(b,this.a,this.k.slice(),this.b.slice());return b}; -l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.r!=this.c&&(this.t=Math.sqrt(Yk(this.k,0,this.b,this.s,0)),this.r=this.c);return $k(this.k,0,this.b,this.s,this.t,!0,b,c,d,e)};l.Jb=function(b,c){return nl(xl(this),0,this.b,this.s,b,c)};l.qj=function(){return Tk(xl(this),0,this.b,this.s)};l.K=function(){return el(this.k,0,this.b,this.s)};function yl(b){if(b.p!=b.c){var c=ke(b.D());b.q=pl(xl(b),0,b.b,b.s,c,0);b.p=b.c}return b.q}l.ph=function(){return new jl(yl(this))};l.vh=function(){return this.b.length}; -l.uh=function(b){if(0>b||this.b.length<=b)return null;var c=new hl(null);il(c,this.a,this.k.slice(0===b?0:this.b[b-1],this.b[b]));return c};l.dd=function(){var b=this.a,c=this.k,d=this.b,e=[],f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g],n=new hl(null);il(n,b,c.slice(f,k));e.push(n);f=k}return e};function xl(b){if(b.F!=b.c){var c=b.k;ul(c,b.b,b.s)?b.n=c:(b.n=c.slice(),b.n.length=vl(b.n,0,b.b,b.s));b.F=b.c}return b.n} -l.mc=function(b){var c=[],d=[];c.length=gl(this.k,0,this.b,this.s,Math.sqrt(b),c,0,d);b=new H(null);wl(b,"XY",c,d);return b};l.H=function(){return"Polygon"};l.ha=function(b){return sl(xl(this),0,this.b,this.s,b)};l.U=function(b,c){if(null===b)wl(this,"XY",null,this.b);else{Rk(this,c,b,2);null===this.k&&(this.k=[]);var d=cl(this.k,0,b,this.s,this.b);this.k.length=0===d.length?0:d[d.length-1];this.l()}};function wl(b,c,d,e){Qk(b,c,d);b.b=e;b.l()} -function zl(b,c,d,e){var f=m(e)?e:32;e=[];var g;for(g=0;g<f;++g)db(e,b.offset(c,d,2*Math.PI*g/f));e.push(e[0],e[1]);b=new H(null);wl(b,"XY",e,[e.length]);return b};function Al(b,c,d,e,f,g,h){uc.call(this,b,c);this.vectorContext=d;this.a=e;this.frameState=f;this.context=g;this.glContext=h}u(Al,uc);function Bl(b){this.b=this.c=this.e=this.d=this.a=null;this.f=b}u(Bl,pc);function Cl(b){var c=b.e,d=b.c;b=Va([c,[c[0],d[1]],d,[d[0],c[1]]],b.a.Ga,b.a);b[4]=b[0].slice();return new H([b])}Bl.prototype.M=function(){this.setMap(null)};Bl.prototype.g=function(b){var c=this.b,d=this.f;b.vectorContext.ic(Infinity,function(b){b.wa(d.e,d.b);b.xa(d.c);b.Sb(c,null)})};Bl.prototype.N=function(){return this.b};function Dl(b){null===b.a||null===b.e||null===b.c||b.a.render()} -Bl.prototype.setMap=function(b){null!==this.d&&(Yc(this.d),this.d=null,this.a.render(),this.a=null);this.a=b;null!==this.a&&(this.d=z(b,"postcompose",this.g,!1,this),Dl(this))};function El(b,c){uc.call(this,b);this.coordinate=c}u(El,uc);function Fl(b){Bk.call(this,{handleDownEvent:Hl,handleDragEvent:Il,handleUpEvent:Jl});b=m(b)?b:{};this.f=new Bl(m(b.style)?b.style:null);this.d=null;this.i=m(b.condition)?b.condition:dd}u(Fl,Bk);function Il(b){if(Ak(b)){var c=this.f;b=b.pixel;c.e=this.d;c.c=b;c.b=Cl(c);Dl(c)}}Fl.prototype.N=function(){return this.f.N()};Fl.prototype.g=ca; -function Jl(b){if(!Ak(b))return!0;this.f.setMap(null);var c=b.pixel[0]-this.d[0],d=b.pixel[1]-this.d[1];64<=c*c+d*d&&(this.g(b),this.dispatchEvent(new El("boxend",b.coordinate)));return!1}function Hl(b){if(Ak(b)&&Cc(b.a)&&this.i(b)){this.d=b.pixel;this.f.setMap(b.map);var c=this.f,d=this.d;c.e=this.d;c.c=d;c.b=Cl(c);Dl(c);this.dispatchEvent(new El("boxstart",b.coordinate));return!0}return!1};function Kl(){this.c=-1};function Ll(){this.c=-1;this.c=64;this.a=Array(4);this.e=Array(this.c);this.d=this.b=0;this.a[0]=1732584193;this.a[1]=4023233417;this.a[2]=2562383102;this.a[3]=271733878;this.d=this.b=0}u(Ll,Kl); -function Ml(b,c,d){d||(d=0);var e=Array(16);if(ia(c))for(var f=0;16>f;++f)e[f]=c.charCodeAt(d++)|c.charCodeAt(d++)<<8|c.charCodeAt(d++)<<16|c.charCodeAt(d++)<<24;else for(f=0;16>f;++f)e[f]=c[d++]|c[d++]<<8|c[d++]<<16|c[d++]<<24;c=b.a[0];d=b.a[1];var f=b.a[2],g=b.a[3],h=0,h=c+(g^d&(f^g))+e[0]+3614090360&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[1]+3905402710&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[2]+606105819&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^ +l.scale=function(b,c){var d=ja(c)?c:b;this.left*=b;this.right*=b;this.top*=d;this.bottom*=d;return this};function eh(b,c,d,e){this.left=b;this.top=c;this.width=d;this.height=e}l=eh.prototype;l.clone=function(){return new eh(this.left,this.top,this.width,this.height)};l.contains=function(b){return b instanceof eh?this.left<=b.left&&this.left+this.width>=b.left+b.width&&this.top<=b.top&&this.top+this.height>=b.top+b.height:b.x>=this.left&&b.x<=this.left+this.width&&b.y>=this.top&&b.y<=this.top+this.height}; +function fh(b,c){var d=c.x<b.left?b.left-c.x:Math.max(c.x-(b.left+b.width),0),e=c.y<b.top?b.top-c.y:Math.max(c.y-(b.top+b.height),0);return d*d+e*e}l.distance=function(b){return Math.sqrt(fh(this,b))};l.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this}; +l.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};l.scale=function(b,c){var d=ja(c)?c:b;this.left*=b;this.width*=b;this.top*=d;this.height*=d;return this};function gh(b,c){var d=Hg(b);return d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(b,null))?d[c]||d.getPropertyValue(c)||"":""}function hh(b,c){return gh(b,c)||(b.currentStyle?b.currentStyle[c]:null)||b.style&&b.style[c]}function ih(b,c,d){var e;c instanceof Dg?(e=c.x,c=c.y):(e=c,c=d);b.style.left=jh(e);b.style.top=jh(c)} +function kh(b){var c;try{c=b.getBoundingClientRect()}catch(d){return{left:0,top:0,right:0,bottom:0}}Jb&&b.ownerDocument.body&&(b=b.ownerDocument,c.left-=b.documentElement.clientLeft+b.body.clientLeft,c.top-=b.documentElement.clientTop+b.body.clientTop);return c} +function lh(b){if(1==b.nodeType)return b=kh(b),new Dg(b.left,b.top);var c=ka(b.ij),d=b;b.targetTouches&&b.targetTouches.length?d=b.targetTouches[0]:c&&b.a.targetTouches&&b.a.targetTouches.length&&(d=b.a.targetTouches[0]);return new Dg(d.clientX,d.clientY)}function jh(b){"number"==typeof b&&(b=b+"px");return b} +function mh(b){var c=nh;if("none"!=hh(b,"display"))return c(b);var d=b.style,e=d.display,f=d.visibility,g=d.position;d.visibility="hidden";d.position="absolute";d.display="inline";b=c(b);d.display=e;d.position=g;d.visibility=f;return b}function nh(b){var c=b.offsetWidth,d=b.offsetHeight,e=Lb&&!c&&!d;return m(c)&&!e||!b.getBoundingClientRect?new Eg(c,d):(b=kh(b),new Eg(b.right-b.left,b.bottom-b.top))}function oh(b,c){b.style.display=c?"":"none"} +function ph(b,c,d,e){if(/^\d+px?$/.test(c))return parseInt(c,10);var f=b.style[d],g=b.runtimeStyle[d];b.runtimeStyle[d]=b.currentStyle[d];b.style[d]=c;c=b.style[e];b.style[d]=f;b.runtimeStyle[d]=g;return c}function qh(b,c){var d=b.currentStyle?b.currentStyle[c]:null;return d?ph(b,d,"left","pixelLeft"):0} +function rh(b,c){if(Jb){var d=qh(b,c+"Left"),e=qh(b,c+"Right"),f=qh(b,c+"Top"),g=qh(b,c+"Bottom");return new dh(f,e,g,d)}d=gh(b,c+"Left");e=gh(b,c+"Right");f=gh(b,c+"Top");g=gh(b,c+"Bottom");return new dh(parseFloat(f),parseFloat(e),parseFloat(g),parseFloat(d))}var sh={thin:2,medium:4,thick:6};function th(b,c){if("none"==(b.currentStyle?b.currentStyle[c+"Style"]:null))return 0;var d=b.currentStyle?b.currentStyle[c+"Width"]:null;return d in sh?sh[d]:ph(b,d,"left","pixelLeft")} +function uh(b){if(Jb&&!(Jb&&9<=Ub)){var c=th(b,"borderLeft"),d=th(b,"borderRight"),e=th(b,"borderTop");b=th(b,"borderBottom");return new dh(e,d,b,c)}c=gh(b,"borderLeftWidth");d=gh(b,"borderRightWidth");e=gh(b,"borderTopWidth");b=gh(b,"borderBottomWidth");return new dh(parseFloat(e),parseFloat(d),parseFloat(b),parseFloat(c))};function vh(b,c,d){sc.call(this,b);this.map=c;this.frameState=m(d)?d:null}w(vh,sc);function wh(b){gd.call(this);this.element=m(b.element)?b.element:null;this.b=this.U=null;this.v=[];this.render=m(b.render)?b.render:ca;m(b.target)&&this.c(b.target)}w(wh,gd);wh.prototype.X=function(){Tg(this.element);wh.aa.X.call(this)};wh.prototype.g=function(){return this.b}; +wh.prototype.setMap=function(b){null===this.b||Tg(this.element);0!=this.v.length&&(Sa(this.v,Xc),this.v.length=0);this.b=b;null!==this.b&&((null===this.U?b.G:this.U).appendChild(this.element),this.render!==ca&&this.v.push(x(b,"postrender",this.render,!1,this)),b.render())};wh.prototype.c=function(b){this.U=Ig(b)};function xh(){this.c=0;this.f={};this.b=this.a=null}l=xh.prototype;l.clear=function(){this.c=0;this.f={};this.b=this.a=null};function yh(b,c){return b.f.hasOwnProperty(c)}l.forEach=function(b,c){for(var d=this.a;null!==d;)b.call(c,d.zc,d.ie,this),d=d.cb};l.get=function(b){b=this.f[b];if(b===this.b)return b.zc;b===this.a?(this.a=this.a.cb,this.a.Ub=null):(b.cb.Ub=b.Ub,b.Ub.cb=b.cb);b.cb=null;b.Ub=this.b;this.b=this.b.cb=b;return b.zc};l.ac=function(){return this.c}; +l.O=function(){var b=Array(this.c),c=0,d;for(d=this.b;null!==d;d=d.Ub)b[c++]=d.ie;return b};l.mb=function(){var b=Array(this.c),c=0,d;for(d=this.b;null!==d;d=d.Ub)b[c++]=d.zc;return b};l.pop=function(){var b=this.a;delete this.f[b.ie];null!==b.cb&&(b.cb.Ub=null);this.a=b.cb;null===this.a&&(this.b=null);--this.c;return b.zc};l.set=function(b,c){var d={ie:b,cb:null,Ub:this.b,zc:c};null===this.b?this.a=d:this.b.cb=d;this.b=d;this.f[b]=d;++this.c};function zh(b){xh.call(this);this.g=m(b)?b:2048}w(zh,xh);function Ah(b){return b.ac()>b.g};function Bh(b,c){ad.call(this);this.a=b;this.state=c}w(Bh,ad);function Ch(b){C(b,"change")}Bh.prototype.pb=function(){return ma(this).toString()};Bh.prototype.i=function(){return this.a};function Dh(b){gd.call(this);this.j=Ae(b.projection);this.g=m(b.attributions)?b.attributions:null;this.U=b.logo;this.A=m(b.state)?b.state:"ready";this.G=m(b.wrapX)?b.wrapX:!1}w(Dh,gd);l=Dh.prototype;l.te=ca;l.la=function(){return this.g};l.ka=function(){return this.U};l.ma=function(){return this.j};l.na=function(){return this.A};function Eh(b){return b.G}function Fh(b,c){b.A=c;b.s()};function Gh(b){this.minZoom=m(b.minZoom)?b.minZoom:0;this.a=b.resolutions;this.maxZoom=this.a.length-1;this.c=m(b.origin)?b.origin:null;this.g=null;m(b.origins)&&(this.g=b.origins);var c=b.extent;m(c)&&null===this.c&&null===this.g&&(this.c=de(c));this.i=null;m(b.tileSizes)&&(this.i=b.tileSizes);this.l=m(b.tileSize)?b.tileSize:null===this.i?256:null;this.v=m(c)?c:null;this.b=null;m(b.sizes)?this.b=Ua(b.sizes,function(b){return new lg(Math.min(0,b[0]),Math.max(b[0]-1,-1),Math.min(0,b[1]),Math.max(b[1]- +1,-1))},this):null!=c&&Hh(this,c);this.f=[0,0]}var Ih=[0,0,0];function Jh(b,c,d,e,f){f=Kh(b,c,f);for(c=c[0]-1;c>=b.minZoom;){if(d.call(null,c,jg(b,f,c,e)))return!0;--c}return!1}l=Gh.prototype;l.R=function(){return this.v};l.pg=function(){return this.maxZoom};l.qg=function(){return this.minZoom};l.Nc=function(b){return null===this.c?this.g[b]:this.c};l.ua=function(b){return this.a[b]};l.ih=function(){return this.a};function Lh(b,c,d,e){return c[0]<b.maxZoom?(e=Kh(b,c,e),jg(b,e,c[0]+1,d)):null} +function Mh(b,c,d,e){Nh(b,c[0],c[1],d,!1,Ih);var f=Ih[1],g=Ih[2];Nh(b,c[2],c[3],d,!0,Ih);b=Ih[1];c=Ih[2];m(e)?(e.a=f,e.f=b,e.b=g,e.c=c):e=new lg(f,b,g,c);return e}function jg(b,c,d,e){return Mh(b,c,b.ua(d),e)}function gg(b,c){var d=b.Nc(c[0]),e=b.ua(c[0]),f=md(b.Ja(c[0]),b.f);return[d[0]+(c[1]+.5)*f[0]*e,d[1]+(c[2]+.5)*f[1]*e]}function Kh(b,c,d){var e=b.Nc(c[0]),f=b.ua(c[0]);b=md(b.Ja(c[0]),b.f);var g=e[0]+c[1]*b[0]*f;c=e[1]+c[2]*b[1]*f;return Od(g,c,g+b[0]*f,c+b[1]*f,d)} +l.pd=function(b,c,d){return Nh(this,b[0],b[1],c,!1,d)};function Nh(b,c,d,e,f,g){var h=Oh(b,e),k=e/b.ua(h),n=b.Nc(h);b=md(b.Ja(h),b.f);c=k*Math.floor((c-n[0])/e+(f?.5:0))/b[0];d=k*Math.floor((d-n[1])/e+(f?0:.5))/b[1];f?(c=Math.ceil(c)-1,d=Math.ceil(d)-1):(c=Math.floor(c),d=Math.floor(d));return bg(h,c,d,g)}l.Zd=function(b,c,d){return Nh(this,b[0],b[1],this.ua(c),!1,d)};l.Ja=function(b){return null===this.l?this.i[b]:this.l};function Oh(b,c){var d=bc(b.a,c,0);return Wb(d,b.minZoom,b.maxZoom)} +function Hh(b,c){for(var d=b.a.length,e=Array(d),f=b.minZoom;f<d;++f)e[f]=jg(b,c,f);b.b=e}function Ph(b){var c={};Gb(c,m(b)?b:{});m(c.extent)||(c.extent=Ae("EPSG:3857").R());c.resolutions=Qh(c.extent,c.maxZoom,c.tileSize);delete c.maxZoom;return new Gh(c)}function Qh(b,c,d){c=m(c)?c:42;var e=ge(b);b=je(b);d=md(m(d)?d:256);d=Math.max(b/d[0],e/d[1]);c+=1;e=Array(c);for(b=0;b<c;++b)e[b]=d/Math.pow(2,b);return e} +function hg(b){b=Ae(b);var c=b.R();null===c&&(b=180*xe.degrees/b.Vd(),c=Od(-b,-b,b,b));return c};function Rh(b){Dh.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state,wrapX:b.wrapX});this.ba=m(b.opaque)?b.opaque:!1;this.ea=m(b.tilePixelRatio)?b.tilePixelRatio:1;this.tileGrid=m(b.tileGrid)?b.tileGrid:null;this.b=new zh;this.c=[0,0]}w(Rh,Dh);function Sh(b,c,d,e){for(var f=!0,g,h,k=d.a;k<=d.f;++k)for(var n=d.b;n<=d.c;++n)g=b.lb(c,k,n),h=!1,yh(b.b,g)&&(g=b.b.get(g),(h=2===g.state)&&(h=!1!==e(g))),h||(f=!1);return f}l=Rh.prototype;l.Sd=function(){return 0}; +l.lb=cg;l.za=function(){return this.tileGrid};function Th(b,c){var d;if(null===b.tileGrid){var e=c.l;if(null===e){var e=hg(c),f=m(void 0)?void 0:"top-left",g=Qh(e,void 0,void 0);"bottom-left"===f?d=ae(e):"bottom-right"===f?d=be(e):"top-left"===f?d=de(e):"top-right"===f&&(d=ce(e));e=new Gh({extent:e,origin:d,resolutions:g,tileSize:void 0});c.l=e}d=e}else d=b.tileGrid;return d}l.dc=function(b,c,d){c=Th(this,d);return ld(md(c.Ja(b),this.c),this.ea,this.c)}; +function Uh(b,c,d){d=m(d)?d:b.j;var e=Th(b,d);b.G&&d.c&&(c=fg(c,e,d));return ig(c,e)?c:null}l.Pf=ca;function Vh(b,c){sc.call(this,b);this.tile=c}w(Vh,sc);function Wh(b){b=m(b)?b:{};this.G=Pg("UL");this.A=Pg("LI");this.G.appendChild(this.A);oh(this.A,!1);this.f=m(b.collapsed)?b.collapsed:!0;this.j=m(b.collapsible)?b.collapsible:!0;this.j||(this.f=!1);var c=m(b.className)?b.className:"ol-attribution",d=m(b.tipLabel)?b.tipLabel:"Attributions",e=m(b.collapseLabel)?b.collapseLabel:"\u00bb";this.C=ia(e)?Mg("SPAN",{},e):e;e=m(b.label)?b.label:"i";this.K=ia(e)?Mg("SPAN",{},e):e;d=Mg("BUTTON",{type:"button",title:d},this.j&&!this.f?this.C:this.K);x(d,"click", +this.rl,!1,this);x(d,["mouseout",vc],function(){this.blur()},!1);c=Mg("DIV",c+" ol-unselectable ol-control"+(this.f&&this.j?" ol-collapsed":"")+(this.j?"":" ol-uncollapsible"),this.G,d);wh.call(this,{element:c,render:m(b.render)?b.render:Xh,target:b.target});this.u=!0;this.l={};this.i={};this.T={}}w(Wh,wh); +function Xh(b){b=b.frameState;if(null===b)this.u&&(oh(this.element,!1),this.u=!1);else{var c,d,e,f,g,h,k,n,p,q,r,t=b.layerStatesArray,v=Cb(b.attributions),B={},z=b.viewState.projection;d=0;for(c=t.length;d<c;d++)if(h=t[d].layer.ca(),null!==h&&(q=ma(h).toString(),p=h.g,null!==p))for(e=0,f=p.length;e<f;e++)if(k=p[e],n=ma(k).toString(),!(n in v)){g=b.usedTiles[q];if(m(g)){var E=Th(h,z);a:{r=k;var A=z;if(null===r.a)r=!0;else{var y=void 0,J=void 0,L=void 0,H=void 0;for(H in g)if(H in r.a)for(var L=g[H], +S,y=0,J=r.a[H].length;y<J;++y){S=r.a[H][y];if(og(S,L)){r=!0;break a}var ta=jg(E,A.R(),parseInt(H,10)),Pa=ng(ta);if(L.a<ta.a||L.f>ta.f)if(og(S,new lg(Xb(L.a,Pa),Xb(L.f,Pa),L.b,L.c))||ng(L)>Pa&&og(S,ta)){r=!0;break a}}r=!1}}}else r=!1;r?(n in B&&delete B[n],v[n]=k):B[n]=k}c=[v,B];d=c[0];c=c[1];for(var R in this.l)R in d?(this.i[R]||(oh(this.l[R],!0),this.i[R]=!0),delete d[R]):R in c?(this.i[R]&&(oh(this.l[R],!1),delete this.i[R]),delete c[R]):(Tg(this.l[R]),delete this.l[R],delete this.i[R]);for(R in d)e= +Pg("LI"),e.innerHTML=d[R].b,this.G.appendChild(e),this.l[R]=e,this.i[R]=!0;for(R in c)e=Pg("LI"),e.innerHTML=c[R].b,oh(e,!1),this.G.appendChild(e),this.l[R]=e;R=!xb(this.i)||!xb(b.logos);this.u!=R&&(oh(this.element,R),this.u=R);R&&xb(this.i)?ah(this.element,"ol-logo-only"):bh(this.element,"ol-logo-only");var Aa;b=b.logos;R=this.T;for(Aa in R)Aa in b||(Tg(R[Aa]),delete R[Aa]);for(var fb in b)fb in R||(Aa=new Image,Aa.src=fb,d=b[fb],""===d?d=Aa:(d=Mg("A",{href:d}),d.appendChild(Aa)),this.A.appendChild(d), +R[fb]=d);oh(this.A,!xb(b))}}l=Wh.prototype;l.rl=function(b){b.preventDefault();Yh(this)};function Yh(b){ch(b.element,"ol-collapsed");b.f?Ug(b.C,b.K):Ug(b.K,b.C);b.f=!b.f}l.ql=function(){return this.j};l.tl=function(b){this.j!==b&&(this.j=b,ch(this.element,"ol-uncollapsible"),!b&&this.f&&Yh(this))};l.sl=function(b){this.j&&this.f!==b&&Yh(this)};l.pl=function(){return this.f};function Zh(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-rotate",d=m(b.label)?b.label:"\u21e7";this.f=null;ia(d)?this.f=Mg("SPAN","ol-compass",d):(this.f=d,ah(this.f,"ol-compass"));d=Mg("BUTTON",{"class":c+"-reset",type:"button",title:m(b.tipLabel)?b.tipLabel:"Reset rotation"},this.f);x(d,"click",Zh.prototype.A,!1,this);c=Mg("DIV",c+" ol-unselectable ol-control",d);wh.call(this,{element:c,render:m(b.render)?b.render:$h,target:b.target});this.j=m(b.duration)?b.duration:250;this.i=m(b.autoHide)? +b.autoHide:!0;this.l=void 0;this.i&&ah(this.element,"ol-hidden")}w(Zh,wh);Zh.prototype.A=function(b){b.preventDefault();b=this.b;var c=b.Y();if(null!==c){for(var d=c.Ea();d<-Math.PI;)d+=2*Math.PI;for(;d>Math.PI;)d-=2*Math.PI;m(d)&&(0<this.j&&b.Oa($f({rotation:d,duration:this.j,easing:Vf})),c.oe(0))}}; +function $h(b){b=b.frameState;if(null!==b){b=b.viewState.rotation;if(b!=this.l){var c="rotate("+180*b/Math.PI+"deg)";if(this.i){var d=this.element;0===b?ah(d,"ol-hidden"):bh(d,"ol-hidden")}this.f.style.msTransform=c;this.f.style.webkitTransform=c;this.f.style.transform=c}this.l=b}};function bi(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-zoom",d=m(b.delta)?b.delta:1,e=m(b.zoomOutLabel)?b.zoomOutLabel:"\u2212",f=m(b.zoomOutTipLabel)?b.zoomOutTipLabel:"Zoom out",g=Mg("BUTTON",{"class":c+"-in",type:"button",title:m(b.zoomInTipLabel)?b.zoomInTipLabel:"Zoom in"},m(b.zoomInLabel)?b.zoomInLabel:"+");x(g,"click",sa(bi.prototype.i,d),!1,this);e=Mg("BUTTON",{"class":c+"-out",type:"button",title:f},e);x(e,"click",sa(bi.prototype.i,-d),!1,this);x(e,["mouseout",vc],function(){this.blur()}, +!1);c=Mg("DIV",c+" ol-unselectable ol-control",g,e);wh.call(this,{element:c,target:b.target});this.f=m(b.duration)?b.duration:250}w(bi,wh);bi.prototype.i=function(b,c){c.preventDefault();var d=this.b,e=d.Y();if(null!==e){var f=e.Da();m(f)&&(0<this.f&&d.Oa(ag({resolution:f,duration:this.f,easing:Vf})),d=e.constrainResolution(f,b),e.Yb(d))}};function ci(b){b=m(b)?b:{};var c=new rg;(m(b.zoom)?b.zoom:1)&&c.push(new bi(b.zoomOptions));(m(b.rotate)?b.rotate:1)&&c.push(new Zh(b.rotateOptions));(m(b.attribution)?b.attribution:1)&&c.push(new Wh(b.attributionOptions));return c};var di=Lb?"webkitfullscreenchange":Kb?"mozfullscreenchange":Jb?"MSFullscreenChange":"fullscreenchange";function ei(){var b=Fg().a,c=b.body;return!!(c.webkitRequestFullscreen||c.mozRequestFullScreen&&b.mozFullScreenEnabled||c.msRequestFullscreen&&b.msFullscreenEnabled||c.requestFullscreen&&b.fullscreenEnabled)} +function fi(b){b.webkitRequestFullscreen?b.webkitRequestFullscreen():b.mozRequestFullScreen?b.mozRequestFullScreen():b.msRequestFullscreen?b.msRequestFullscreen():b.requestFullscreen&&b.requestFullscreen()}function gi(){var b=Fg().a;return!!(b.webkitIsFullScreen||b.mozFullScreen||b.msFullscreenElement||b.fullscreenElement)};function hi(b){b=m(b)?b:{};this.f=m(b.className)?b.className:"ol-full-screen";var c=m(b.label)?b.label:"\u2194";this.i=ia(c)?document.createTextNode(String(c)):c;c=m(b.labelActive)?b.labelActive:"\u00d7";this.j=ia(c)?document.createTextNode(String(c)):c;c=m(b.tipLabel)?b.tipLabel:"Toggle full-screen";c=Mg("BUTTON",{"class":this.f+"-"+gi(),type:"button",title:c},this.i);x(c,"click",this.u,!1,this);x(ba.document,di,this.l,!1,this);var d=this.f+" ol-unselectable ol-control "+(ei()?"":"ol-unsupported"), +c=Mg("DIV",d,c);wh.call(this,{element:c,target:b.target});this.A=m(b.keys)?b.keys:!1}w(hi,wh); +hi.prototype.u=function(b){b.preventDefault();ei()&&(b=this.b,null!==b&&(gi()?(b=Fg().a,b.webkitCancelFullScreen?b.webkitCancelFullScreen():b.mozCancelFullScreen?b.mozCancelFullScreen():b.msExitFullscreen?b.msExitFullscreen():b.exitFullscreen&&b.exitFullscreen()):(b=b.vf(),b=Ig(b),this.A?b.mozRequestFullScreenWithKeys?b.mozRequestFullScreenWithKeys():b.webkitRequestFullscreen?b.webkitRequestFullscreen():fi(b):fi(b))))}; +hi.prototype.l=function(){var b=this.f+"-true",c=this.f+"-false",d=Vg(this.element),e=this.b;gi()?($g(d,c)&&(bh(d,c),ah(d,b)),Ug(this.j,this.i)):($g(d,b)&&(bh(d,b),ah(d,c)),Ug(this.i,this.j));null===e||e.Uc()};function ii(b){b=m(b)?b:{};var c=Mg("DIV",m(b.className)?b.className:"ol-mouse-position");wh.call(this,{element:c,render:m(b.render)?b.render:ji,target:b.target});x(this,id("projection"),this.ul,!1,this);m(b.coordinateFormat)&&this.Gh(b.coordinateFormat);m(b.projection)&&this.Pg(Ae(b.projection));this.A=m(b.undefinedHTML)?b.undefinedHTML:"";this.l=c.innerHTML;this.j=this.i=this.f=null}w(ii,wh); +function ji(b){b=b.frameState;null===b?this.f=null:this.f!=b.viewState.projection&&(this.f=b.viewState.projection,this.i=null);ki(this,this.j)}l=ii.prototype;l.ul=function(){this.i=null};l.lg=function(){return this.get("coordinateFormat")};l.Og=function(){return this.get("projection")};l.ok=function(b){this.j=this.b.Rd(b.a);ki(this,this.j)};l.pk=function(){ki(this,null);this.j=null}; +l.setMap=function(b){ii.aa.setMap.call(this,b);null!==b&&(b=b.b,this.v.push(x(b,"mousemove",this.ok,!1,this),x(b,"mouseout",this.pk,!1,this)))};l.Gh=function(b){this.set("coordinateFormat",b)};l.Pg=function(b){this.set("projection",b)};function ki(b,c){var d=b.A;if(null!==c&&null!==b.f){if(null===b.i){var e=b.Og();b.i=m(e)?Ee(b.f,e):Ue}e=b.b.ta(c);null!==e&&(b.i(e,e),d=b.lg(),d=m(d)?d(e):e.toString())}m(b.l)&&d==b.l||(b.element.innerHTML=d,b.l=d)};function li(b,c,d){nc.call(this);this.f=b;this.c=d;this.a=c||window;this.b=ra(this.hg,this)}w(li,nc);l=li.prototype;l.ha=null;l.Qf=!1;l.start=function(){mi(this);this.Qf=!1;var b=ni(this),c=oi(this);b&&!c&&this.a.mozRequestAnimationFrame?(this.ha=x(this.a,"MozBeforePaint",this.b),this.a.mozRequestAnimationFrame(null),this.Qf=!0):this.ha=b&&c?b.call(this.a,this.b):this.a.setTimeout(se(this.b),20)}; +function mi(b){if(null!=b.ha){var c=ni(b),d=oi(b);c&&!d&&b.a.mozRequestAnimationFrame?Xc(b.ha):c&&d?d.call(b.a,b.ha):b.a.clearTimeout(b.ha)}b.ha=null}l.hg=function(){this.Qf&&this.ha&&Xc(this.ha);this.ha=null;this.f.call(this.c,ua())};l.X=function(){mi(this);li.aa.X.call(this)};function ni(b){b=b.a;return b.requestAnimationFrame||b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||b.oRequestAnimationFrame||b.msRequestAnimationFrame||null} +function oi(b){b=b.a;return b.cancelAnimationFrame||b.cancelRequestAnimationFrame||b.webkitCancelRequestAnimationFrame||b.mozCancelRequestAnimationFrame||b.oCancelRequestAnimationFrame||b.msCancelRequestAnimationFrame||null};function pi(b){ba.setTimeout(function(){throw b;},0)}function qi(b,c){var d=b;c&&(d=ra(b,c));d=ri(d);!ka(ba.setImmediate)||ba.Window&&ba.Window.prototype.setImmediate==ba.setImmediate?(si||(si=ti()),si(d)):ba.setImmediate(d)}var si; +function ti(){var b=ba.MessageChannel;"undefined"===typeof b&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(b=function(){var b=document.createElement("iframe");b.style.display="none";b.src="";document.documentElement.appendChild(b);var c=b.contentWindow,b=c.document;b.open();b.write("");b.close();var d="callImmediate"+Math.random(),e="file:"==c.location.protocol?"*":c.location.protocol+"//"+c.location.host,b=ra(function(b){if(("*"==e||b.origin==e)&&b.data==d)this.port1.onmessage()}, +this);c.addEventListener("message",b,!1);this.port1={};this.port2={postMessage:function(){c.postMessage(d,e)}}});if("undefined"!==typeof b&&!ob("Trident")&&!ob("MSIE")){var c=new b,d={},e=d;c.port1.onmessage=function(){if(m(d.next)){d=d.next;var b=d.dg;d.dg=null;b()}};return function(b){e.next={dg:b};e=e.next;c.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(b){var c=document.createElement("script");c.onreadystatechange=function(){c.onreadystatechange= +null;c.parentNode.removeChild(c);c=null;b();b=null};document.documentElement.appendChild(c)}:function(b){ba.setTimeout(b,0)}}var ri=re;function ui(b){if("function"==typeof b.mb)return b.mb();if(ia(b))return b.split("");if(ha(b)){for(var c=[],d=b.length,e=0;e<d;e++)c.push(b[e]);return c}return sb(b)} +function vi(b,c){if("function"==typeof b.forEach)b.forEach(c,void 0);else if(ha(b)||ia(b))Sa(b,c,void 0);else{var d;if("function"==typeof b.O)d=b.O();else if("function"!=typeof b.mb)if(ha(b)||ia(b)){d=[];for(var e=b.length,f=0;f<e;f++)d.push(f)}else d=tb(b);else d=void 0;for(var e=ui(b),f=e.length,g=0;g<f;g++)c.call(void 0,e[g],d&&d[g],b)}};function wi(b,c){this.b={};this.a=[];this.c=0;var d=arguments.length;if(1<d){if(d%2)throw Error("Uneven number of arguments");for(var e=0;e<d;e+=2)this.set(arguments[e],arguments[e+1])}else if(b){b instanceof wi?(d=b.O(),e=b.mb()):(d=tb(b),e=sb(b));for(var f=0;f<d.length;f++)this.set(d[f],e[f])}}l=wi.prototype;l.ac=function(){return this.c};l.mb=function(){xi(this);for(var b=[],c=0;c<this.a.length;c++)b.push(this.b[this.a[c]]);return b};l.O=function(){xi(this);return this.a.concat()}; +l.wa=function(){return 0==this.c};l.clear=function(){this.b={};this.c=this.a.length=0};l.remove=function(b){return yi(this.b,b)?(delete this.b[b],this.c--,this.a.length>2*this.c&&xi(this),!0):!1};function xi(b){if(b.c!=b.a.length){for(var c=0,d=0;c<b.a.length;){var e=b.a[c];yi(b.b,e)&&(b.a[d++]=e);c++}b.a.length=d}if(b.c!=b.a.length){for(var f={},d=c=0;c<b.a.length;)e=b.a[c],yi(f,e)||(b.a[d++]=e,f[e]=1),c++;b.a.length=d}}l.get=function(b,c){return yi(this.b,b)?this.b[b]:c}; +l.set=function(b,c){yi(this.b,b)||(this.c++,this.a.push(b));this.b[b]=c};l.forEach=function(b,c){for(var d=this.O(),e=0;e<d.length;e++){var f=d[e],g=this.get(f);b.call(c,g,f,this)}};l.clone=function(){return new wi(this)};function yi(b,c){return Object.prototype.hasOwnProperty.call(b,c)};function zi(){this.a=ua()}new zi;zi.prototype.set=function(b){this.a=b};zi.prototype.get=function(){return this.a};function Ai(b){ad.call(this);this.Cd=b||window;this.$d=x(this.Cd,"resize",this.xk,!1,this);this.ae=Lg(this.Cd||window)}w(Ai,ad);l=Ai.prototype;l.$d=null;l.Cd=null;l.ae=null;l.X=function(){Ai.aa.X.call(this);this.$d&&(Xc(this.$d),this.$d=null);this.ae=this.Cd=null};l.xk=function(){var b=Lg(this.Cd||window),c=this.ae;b==c||b&&c&&b.width==c.width&&b.height==c.height||(this.ae=b,C(this,"resize"))};function Bi(b,c,d,e,f){if(!(Jb||Lb&&Sb("525")))return!0;if(Mb&&f)return Ci(b);if(f&&!e)return!1;ja(c)&&(c=Di(c));if(!d&&(17==c||18==c||Mb&&91==c))return!1;if(Lb&&e&&d)switch(b){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(Jb&&e&&c==b)return!1;switch(b){case 13:return!0;case 27:return!Lb}return Ci(b)} +function Ci(b){if(48<=b&&57>=b||96<=b&&106>=b||65<=b&&90>=b||Lb&&0==b)return!0;switch(b){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function Di(b){if(Kb)b=Ei(b);else if(Mb&&Lb)a:switch(b){case 93:b=91;break a}return b} +function Ei(b){switch(b){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return b}};function Fi(b,c){ad.call(this);b&&Gi(this,b,c)}w(Fi,ad);l=Fi.prototype;l.qd=null;l.ge=null;l.pf=null;l.he=null;l.Qa=-1;l.Pb=-1;l.Ue=!1; +var Hi={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},Ii={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},Ji=Jb||Lb&&Sb("525"),Ki=Mb&&Kb; +Fi.prototype.a=function(b){Lb&&(17==this.Qa&&!b.l||18==this.Qa&&!b.b||Mb&&91==this.Qa&&!b.v)&&(this.Pb=this.Qa=-1);-1==this.Qa&&(b.l&&17!=b.g?this.Qa=17:b.b&&18!=b.g?this.Qa=18:b.v&&91!=b.g&&(this.Qa=91));Ji&&!Bi(b.g,this.Qa,b.f,b.l,b.b)?this.handleEvent(b):(this.Pb=Di(b.g),Ki&&(this.Ue=b.b))};Fi.prototype.b=function(b){this.Pb=this.Qa=-1;this.Ue=b.b}; +Fi.prototype.handleEvent=function(b){var c=b.a,d,e,f=c.altKey;Jb&&"keypress"==b.type?(d=this.Pb,e=13!=d&&27!=d?c.keyCode:0):Lb&&"keypress"==b.type?(d=this.Pb,e=0<=c.charCode&&63232>c.charCode&&Ci(d)?c.charCode:0):Ib?(d=this.Pb,e=Ci(d)?c.keyCode:0):(d=c.keyCode||this.Pb,e=c.charCode||0,Ki&&(f=this.Ue),Mb&&63==e&&224==d&&(d=191));var g=d=Di(d),h=c.keyIdentifier;d?63232<=d&&d in Hi?g=Hi[d]:25==d&&b.f&&(g=9):h&&h in Ii&&(g=Ii[h]);this.Qa=g;b=new Li(g,e,0,c);b.b=f;C(this,b)}; +function Gi(b,c,d){b.he&&Mi(b);b.qd=c;b.ge=x(b.qd,"keypress",b,d);b.pf=x(b.qd,"keydown",b.a,d,b);b.he=x(b.qd,"keyup",b.b,d,b)}function Mi(b){b.ge&&(Xc(b.ge),Xc(b.pf),Xc(b.he),b.ge=null,b.pf=null,b.he=null);b.qd=null;b.Qa=-1;b.Pb=-1}Fi.prototype.X=function(){Fi.aa.X.call(this);Mi(this)};function Li(b,c,d,e){xc.call(this,e);this.type="key";this.g=b;this.B=c}w(Li,xc);function Ni(b,c){ad.call(this);var d=this.a=b;(d=la(d)&&1==d.nodeType?this.a:this.a?this.a.body:null)&&hh(d,"direction");this.b=x(this.a,Kb?"DOMMouseScroll":"mousewheel",this,c)}w(Ni,ad); +Ni.prototype.handleEvent=function(b){var c=0,d=0,e=0;b=b.a;if("mousewheel"==b.type){d=1;if(Jb||Lb&&(Nb||Sb("532.0")))d=40;e=Oi(-b.wheelDelta,d);m(b.wheelDeltaX)?(c=Oi(-b.wheelDeltaX,d),d=Oi(-b.wheelDeltaY,d)):d=e}else e=b.detail,100<e?e=3:-100>e&&(e=-3),m(b.axis)&&b.axis===b.HORIZONTAL_AXIS?c=e:d=e;ja(this.c)&&Wb(c,-this.c,this.c);ja(this.f)&&(d=Wb(d,-this.f,this.f));c=new Pi(e,b,0,d);C(this,c)};function Oi(b,c){return Lb&&(Mb||Ob)&&0!=b%c?b:b/c} +Ni.prototype.X=function(){Ni.aa.X.call(this);Xc(this.b);this.b=null};function Pi(b,c,d,e){xc.call(this,c);this.type="mousewheel";this.detail=b;this.A=e}w(Pi,xc);function Qi(b,c,d){sc.call(this,b);this.a=c;b=m(d)?d:{};this.buttons=Ri(b);this.pressure=Si(b,this.buttons);this.bubbles=Ab(b,"bubbles",!1);this.cancelable=Ab(b,"cancelable",!1);this.view=Ab(b,"view",null);this.detail=Ab(b,"detail",null);this.screenX=Ab(b,"screenX",0);this.screenY=Ab(b,"screenY",0);this.clientX=Ab(b,"clientX",0);this.clientY=Ab(b,"clientY",0);this.button=Ab(b,"button",0);this.relatedTarget=Ab(b,"relatedTarget",null);this.pointerId=Ab(b,"pointerId",0);this.width=Ab(b,"width",0);this.height= +Ab(b,"height",0);this.pointerType=Ab(b,"pointerType","");this.isPrimary=Ab(b,"isPrimary",!1);c.preventDefault&&(this.preventDefault=function(){c.preventDefault()})}w(Qi,sc);function Ri(b){if(b.buttons||Ti)b=b.buttons;else switch(b.which){case 1:b=1;break;case 2:b=4;break;case 3:b=2;break;default:b=0}return b}function Si(b,c){var d=0;b.pressure?d=b.pressure:d=c?.5:0;return d}var Ti=!1;try{Ti=1===(new MouseEvent("click",{buttons:1})).buttons}catch(Ui){};function Vi(b,c){var d=Pg("CANVAS");m(b)&&(d.width=b);m(c)&&(d.height=c);return d.getContext("2d")} +var Wi=function(){var b;return function(){if(!m(b))if(ba.getComputedStyle){var c=Pg("P"),d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate(1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Tg(c);b=d&&"none"!==d}else b=!1;return b}}(),Xi=function(){var b;return function(){if(!m(b))if(ba.getComputedStyle){var c=Pg("P"), +d,e={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.appendChild(c);for(var f in e)f in c.style&&(c.style[f]="translate3d(1px,1px,1px)",d=ba.getComputedStyle(c).getPropertyValue(e[f]));Tg(c);b=d&&"none"!==d}else b=!1;return b}}();function Yi(b,c){var d=b.style;d.WebkitTransform=c;d.MozTransform=c;d.a=c;d.msTransform=c;d.transform=c;Jb&&!Vb&&(b.style.transformOrigin="0 0")} +function Zi(b,c){var d;if(Xi()){if(m(6)){var e=Array(16);for(d=0;16>d;++d)e[d]=c[d].toFixed(6);d=e.join(",")}else d=c.join(",");Yi(b,"matrix3d("+d+")")}else if(Wi()){e=[c[0],c[1],c[4],c[5],c[12],c[13]];if(m(6)){var f=Array(6);for(d=0;6>d;++d)f[d]=e[d].toFixed(6);d=f.join(",")}else d=e.join(",");Yi(b,"matrix("+d+")")}else b.style.left=Math.round(c[12])+"px",b.style.top=Math.round(c[13])+"px"};var $i=["experimental-webgl","webgl","webkit-3d","moz-webgl"];function aj(b,c){var d,e,f=$i.length;for(e=0;e<f;++e)try{if(d=b.getContext($i[e],c),null!==d)return d}catch(g){}return null};var bj,cj=ba.devicePixelRatio||1,dj=!1,ej=function(){if(!("HTMLCanvasElement"in ba))return!1;try{var b=Vi();if(null===b)return!1;m(b.setLineDash)&&(dj=!0);return!0}catch(c){return!1}}(),fj="DeviceOrientationEvent"in ba,gj="geolocation"in ba.navigator,hj="ontouchstart"in ba,ij="PointerEvent"in ba,jj=!!ba.navigator.msPointerEnabled,kj=!1,lj,mj=[]; +if("WebGLRenderingContext"in ba)try{var nj=aj(Pg("CANVAS"),{failIfMajorPerformanceCaveat:!0});null!==nj&&(kj=!0,lj=nj.getParameter(nj.MAX_TEXTURE_SIZE),mj=nj.getSupportedExtensions())}catch(oj){}bj=kj;wa=mj;va=lj;function pj(b,c){this.a=b;this.g=c};function qj(b){pj.call(this,b,{mousedown:this.Kk,mousemove:this.Lk,mouseup:this.Ok,mouseover:this.Nk,mouseout:this.Mk});this.b=b.b;this.c=[]}w(qj,pj);function rj(b,c){for(var d=b.c,e=c.clientX,f=c.clientY,g=0,h=d.length,k;g<h&&(k=d[g]);g++){var n=Math.abs(f-k[1]);if(25>=Math.abs(e-k[0])&&25>=n)return!0}return!1}function sj(b){var c=tj(b,b.a),d=c.preventDefault;c.preventDefault=function(){b.preventDefault();d()};c.pointerId=1;c.isPrimary=!0;c.pointerType="mouse";return c}l=qj.prototype; +l.Kk=function(b){if(!rj(this,b)){(1).toString()in this.b&&this.cancel(b);var c=sj(b);this.b[(1).toString()]=b;uj(this.a,vj,c,b)}};l.Lk=function(b){if(!rj(this,b)){var c=sj(b);uj(this.a,xj,c,b)}};l.Ok=function(b){if(!rj(this,b)){var c=this.b[(1).toString()];c&&c.button===b.button&&(c=sj(b),uj(this.a,yj,c,b),zb(this.b,(1).toString()))}};l.Nk=function(b){if(!rj(this,b)){var c=sj(b);zj(this.a,c,b)}};l.Mk=function(b){if(!rj(this,b)){var c=sj(b);Aj(this.a,c,b)}}; +l.cancel=function(b){var c=sj(b);this.a.cancel(c,b);zb(this.b,(1).toString())};function Bj(b){pj.call(this,b,{MSPointerDown:this.Tk,MSPointerMove:this.Uk,MSPointerUp:this.Xk,MSPointerOut:this.Vk,MSPointerOver:this.Wk,MSPointerCancel:this.Sk,MSGotPointerCapture:this.Qk,MSLostPointerCapture:this.Rk});this.b=b.b;this.c=["","unavailable","touch","pen","mouse"]}w(Bj,pj);function Cj(b,c){var d=c;ja(c.a.pointerType)&&(d=tj(c,c.a),d.pointerType=b.c[c.a.pointerType]);return d}l=Bj.prototype;l.Tk=function(b){this.b[b.a.pointerId]=b;var c=Cj(this,b);uj(this.a,vj,c,b)}; +l.Uk=function(b){var c=Cj(this,b);uj(this.a,xj,c,b)};l.Xk=function(b){var c=Cj(this,b);uj(this.a,yj,c,b);zb(this.b,b.a.pointerId)};l.Vk=function(b){var c=Cj(this,b);Aj(this.a,c,b)};l.Wk=function(b){var c=Cj(this,b);zj(this.a,c,b)};l.Sk=function(b){var c=Cj(this,b);this.a.cancel(c,b);zb(this.b,b.a.pointerId)};l.Rk=function(b){C(this.a,new Qi("lostpointercapture",b,b.a))};l.Qk=function(b){C(this.a,new Qi("gotpointercapture",b,b.a))};function Dj(b){pj.call(this,b,{pointerdown:this.Cn,pointermove:this.Dn,pointerup:this.Gn,pointerout:this.En,pointerover:this.Fn,pointercancel:this.Bn,gotpointercapture:this.Xj,lostpointercapture:this.Jk})}w(Dj,pj);l=Dj.prototype;l.Cn=function(b){Ej(this.a,b)};l.Dn=function(b){Ej(this.a,b)};l.Gn=function(b){Ej(this.a,b)};l.En=function(b){Ej(this.a,b)};l.Fn=function(b){Ej(this.a,b)};l.Bn=function(b){Ej(this.a,b)};l.Jk=function(b){Ej(this.a,b)};l.Xj=function(b){Ej(this.a,b)};function Fj(b,c){pj.call(this,b,{touchstart:this.Io,touchmove:this.Ho,touchend:this.Go,touchcancel:this.Fo});this.b=b.b;this.j=c;this.c=void 0;this.i=0;this.f=void 0}w(Fj,pj);l=Fj.prototype;l.Ch=function(){this.i=0;this.f=void 0}; +function Gj(b,c,d){c=tj(c,d);c.pointerId=d.identifier+2;c.bubbles=!0;c.cancelable=!0;c.detail=b.i;c.button=0;c.buttons=1;c.width=d.webkitRadiusX||d.radiusX||0;c.height=d.webkitRadiusY||d.radiusY||0;c.pressure=d.webkitForce||d.force||.5;c.isPrimary=b.c===d.identifier;c.pointerType="touch";c.clientX=d.clientX;c.clientY=d.clientY;c.screenX=d.screenX;c.screenY=d.screenY;return c} +function Hj(b,c,d){function e(){c.preventDefault()}var f=Array.prototype.slice.call(c.a.changedTouches),g=f.length,h,k;for(h=0;h<g;++h)k=Gj(b,c,f[h]),k.preventDefault=e,d.call(b,c,k)} +l.Io=function(b){var c=b.a.touches,d=tb(this.b),e=d.length;if(e>=c.length){var f=[],g,h,k;for(g=0;g<e;++g){h=d[g];k=this.b[h];var n;if(!(n=1==h))a:{n=c.length;for(var p=void 0,q=0;q<n;q++)if(p=c[q],p.identifier===h-2){n=!0;break a}n=!1}n||f.push(k.nc)}for(g=0;g<f.length;++g)this.Ve(b,f[g])}c=rb(this.b);if(0===c||1===c&&(1).toString()in this.b)this.c=b.a.changedTouches[0].identifier,m(this.f)&&ba.clearTimeout(this.f);Ij(this,b);this.i++;Hj(this,b,this.xn)}; +l.xn=function(b,c){this.b[c.pointerId]={target:c.target,nc:c,mh:c.target};var d=this.a;c.bubbles=!0;uj(d,Jj,c,b);d=this.a;c.bubbles=!1;uj(d,Kj,c,b);uj(this.a,vj,c,b)};l.Ho=function(b){b.preventDefault();Hj(this,b,this.Pk)};l.Pk=function(b,c){var d=this.b[c.pointerId];if(d){var e=d.nc,f=d.mh;uj(this.a,xj,c,b);e&&f!==c.target&&(e.relatedTarget=c.target,c.relatedTarget=f,e.target=f,c.target?(Aj(this.a,e,b),zj(this.a,c,b)):(c.target=f,c.relatedTarget=null,this.Ve(b,c)));d.nc=c;d.mh=c.target}}; +l.Go=function(b){Ij(this,b);Hj(this,b,this.Jo)};l.Jo=function(b,c){uj(this.a,yj,c,b);this.a.nc(c,b);var d=this.a;c.bubbles=!1;uj(d,Lj,c,b);zb(this.b,c.pointerId);c.isPrimary&&(this.c=void 0,this.f=ba.setTimeout(ra(this.Ch,this),200))};l.Fo=function(b){Hj(this,b,this.Ve)};l.Ve=function(b,c){this.a.cancel(c,b);this.a.nc(c,b);var d=this.a;c.bubbles=!1;uj(d,Lj,c,b);zb(this.b,c.pointerId);c.isPrimary&&(this.c=void 0,this.f=ba.setTimeout(ra(this.Ch,this),200))}; +function Ij(b,c){var d=b.j.c,e=c.a.changedTouches[0];if(b.c===e.identifier){var f=[e.clientX,e.clientY];d.push(f);ba.setTimeout(function(){Za(d,f)},2500)}};function Mj(b){ad.call(this);this.f=b;this.b={};this.c={};this.a=[];ij?Nj(this,new Dj(this)):jj?Nj(this,new Bj(this)):(b=new qj(this),Nj(this,b),hj&&Nj(this,new Fj(this,b)));b=this.a.length;for(var c,d=0;d<b;d++)c=this.a[d],Oj(this,tb(c.g))}w(Mj,ad);function Nj(b,c){var d=tb(c.g);d&&(Sa(d,function(b){var d=c.g[b];d&&(this.c[b]=ra(d,c))},b),b.a.push(c))}Mj.prototype.g=function(b){var c=this.c[b.type];c&&c(b)};function Oj(b,c){Sa(c,function(b){x(this.f,b,this.g,!1,this)},b)} +function Pj(b,c){Sa(c,function(b){Wc(this.f,b,this.g,!1,this)},b)}function tj(b,c){for(var d={},e,f=0,g=Qj.length;f<g;f++)e=Qj[f][0],d[e]=b[e]||c[e]||Qj[f][1];return d}Mj.prototype.nc=function(b,c){b.bubbles=!0;uj(this,Rj,b,c)};Mj.prototype.cancel=function(b,c){uj(this,Sj,b,c)};function Aj(b,c,d){b.nc(c,d);var e=c.relatedTarget;null!==e&&Wg(c.target,e)||(c.bubbles=!1,uj(b,Lj,c,d))} +function zj(b,c,d){c.bubbles=!0;uj(b,Jj,c,d);var e=c.relatedTarget;null!==e&&Wg(c.target,e)||(c.bubbles=!1,uj(b,Kj,c,d))}function uj(b,c,d,e){C(b,new Qi(c,e,d))}function Ej(b,c){C(b,new Qi(c.type,c,c.a))}Mj.prototype.X=function(){for(var b=this.a.length,c,d=0;d<b;d++)c=this.a[d],Pj(this,tb(c.g));Mj.aa.X.call(this)}; +var xj="pointermove",vj="pointerdown",yj="pointerup",Jj="pointerover",Rj="pointerout",Kj="pointerenter",Lj="pointerleave",Sj="pointercancel",Qj=[["bubbles",!1],["cancelable",!1],["view",null],["detail",null],["screenX",0],["screenY",0],["clientX",0],["clientY",0],["ctrlKey",!1],["altKey",!1],["shiftKey",!1],["metaKey",!1],["button",0],["relatedTarget",null],["buttons",0],["pointerId",0],["width",0],["height",0],["pressure",0],["tiltX",0],["tiltY",0],["pointerType",""],["hwTimestamp",0],["isPrimary", +!1],["type",""],["target",null],["currentTarget",null],["which",0]];function Tj(b,c,d,e,f){vh.call(this,b,c,f);this.a=d;this.originalEvent=d.a;this.pixel=c.Rd(this.originalEvent);this.coordinate=c.ta(this.pixel);this.dragging=m(e)?e:!1}w(Tj,vh);Tj.prototype.preventDefault=function(){Tj.aa.preventDefault.call(this);this.a.preventDefault()};Tj.prototype.ob=function(){Tj.aa.ob.call(this);this.a.ob()};function Uj(b,c,d,e,f){Tj.call(this,b,c,d.a,e,f);this.b=d}w(Uj,Tj); +function Vj(b){ad.call(this);this.c=b;this.i=0;this.j=!1;this.b=this.l=this.f=null;b=this.c.b;this.A=0;this.v={};this.g=new Mj(b);this.a=null;this.l=x(this.g,vj,this.sk,!1,this);this.B=x(this.g,xj,this.Zn,!1,this)}w(Vj,ad);function Wj(b,c){var d;d=new Uj(Xj,b.c,c);C(b,d);0!==b.i?(ba.clearTimeout(b.i),b.i=0,d=new Uj(Yj,b.c,c),C(b,d)):b.i=ba.setTimeout(ra(function(){this.i=0;var b=new Uj(Zj,this.c,c);C(this,b)},b),250)} +function ak(b,c){c.type==bk||c.type==ck?delete b.v[c.pointerId]:c.type==dk&&(b.v[c.pointerId]=!0);b.A=rb(b.v)}l=Vj.prototype;l.zg=function(b){ak(this,b);var c=new Uj(bk,this.c,b);C(this,c);!this.j&&0===b.button&&Wj(this,this.b);0===this.A&&(Sa(this.f,Xc),this.f=null,this.j=!1,this.b=null,rc(this.a),this.a=null)}; +l.sk=function(b){ak(this,b);var c=new Uj(dk,this.c,b);C(this,c);this.b=b;null===this.f&&(this.a=new Mj(document),this.f=[x(this.a,ek,this.kl,!1,this),x(this.a,bk,this.zg,!1,this),x(this.g,ck,this.zg,!1,this)])};l.kl=function(b){if(b.clientX!=this.b.clientX||b.clientY!=this.b.clientY){this.j=!0;var c=new Uj(fk,this.c,b,this.j);C(this,c)}b.preventDefault()};l.Zn=function(b){C(this,new Uj(b.type,this.c,b,null!==this.b&&(b.clientX!=this.b.clientX||b.clientY!=this.b.clientY)))}; +l.X=function(){null!==this.B&&(Xc(this.B),this.B=null);null!==this.l&&(Xc(this.l),this.l=null);null!==this.f&&(Sa(this.f,Xc),this.f=null);null!==this.a&&(rc(this.a),this.a=null);null!==this.g&&(rc(this.g),this.g=null);Vj.aa.X.call(this)};var Zj="singleclick",Xj="click",Yj="dblclick",fk="pointerdrag",ek="pointermove",dk="pointerdown",bk="pointerup",ck="pointercancel",gk={$o:Zj,Po:Xj,Qo:Yj,To:fk,Wo:ek,So:dk,Zo:bk,Yo:"pointerover",Xo:"pointerout",Uo:"pointerenter",Vo:"pointerleave",Ro:ck};function hk(b){gd.call(this);var c=Cb(b);c.brightness=m(b.brightness)?b.brightness:0;c.contrast=m(b.contrast)?b.contrast:1;c.hue=m(b.hue)?b.hue:0;c.opacity=m(b.opacity)?b.opacity:1;c.saturation=m(b.saturation)?b.saturation:1;c.visible=m(b.visible)?b.visible:!0;c.maxResolution=m(b.maxResolution)?b.maxResolution:Infinity;c.minResolution=m(b.minResolution)?b.minResolution:0;this.I(c)}w(hk,gd);l=hk.prototype;l.Jb=function(){return this.get("brightness")};l.Kb=function(){return this.get("contrast")}; +l.Lb=function(){return this.get("hue")};function ik(b){var c=b.Jb(),d=b.Kb(),e=b.Lb(),f=b.Sb(),g=b.Ob(),h=b.nf(),k=b.nb(),n=b.R(),p=b.Mb(),q=b.Nb();return{layer:b,brightness:Wb(c,-1,1),contrast:Math.max(d,0),hue:e,opacity:Wb(f,0,1),saturation:Math.max(g,0),l:h,visible:k,Qb:!0,extent:n,maxResolution:p,minResolution:Math.max(q,0)}}l.R=function(){return this.get("extent")};l.Mb=function(){return this.get("maxResolution")};l.Nb=function(){return this.get("minResolution")};l.Sb=function(){return this.get("opacity")}; +l.Ob=function(){return this.get("saturation")};l.nb=function(){return this.get("visible")};l.pc=function(b){this.set("brightness",b)};l.qc=function(b){this.set("contrast",b)};l.rc=function(b){this.set("hue",b)};l.jc=function(b){this.set("extent",b)};l.sc=function(b){this.set("maxResolution",b)};l.tc=function(b){this.set("minResolution",b)};l.kc=function(b){this.set("opacity",b)};l.uc=function(b){this.set("saturation",b)};l.vc=function(b){this.set("visible",b)};function jk(){};function kk(b,c,d,e,f,g){sc.call(this,b,c);this.vectorContext=d;this.frameState=e;this.context=f;this.glContext=g}w(kk,sc);function G(b){var c=Cb(b);delete c.source;hk.call(this,c);this.i=this.K=this.C=null;m(b.map)&&this.setMap(b.map);x(this,id("source"),this.zk,!1,this);this.Tc(m(b.source)?b.source:null)}w(G,hk);function lk(b,c){return b.visible&&c>=b.minResolution&&c<b.maxResolution}l=G.prototype;l.lf=function(b){b=m(b)?b:[];b.push(ik(this));return b};l.ca=function(){var b=this.get("source");return m(b)?b:null};l.nf=function(){var b=this.ca();return null===b?"undefined":b.A};l.Xl=function(){this.s()}; +l.zk=function(){null!==this.i&&(Xc(this.i),this.i=null);var b=this.ca();null!==b&&(this.i=x(b,"change",this.Xl,!1,this));this.s()};l.setMap=function(b){Xc(this.C);this.s();Xc(this.K);null!==b&&(this.C=x(b,"precompose",function(b){var d=ik(this);d.Qb=!1;b.frameState.layerStatesArray.push(d);b.frameState.layerStates[ma(this)]=d},!1,this),this.K=x(this,"change",b.render,!1,b))};l.Tc=function(b){this.set("source",b)};function mk(b,c,d,e,f){ad.call(this);this.i=f;this.extent=b;this.g=d;this.resolution=c;this.state=e}w(mk,ad);function nk(b){C(b,"change")}mk.prototype.R=function(){return this.extent};function ok(b,c,d,e,f,g,h,k){Ed(b);0===c&&0===d||Hd(b,c,d);1==e&&1==f||Id(b,e,f);0!==g&&Jd(b,g);0===h&&0===k||Hd(b,h,k);return b}function pk(b,c){return b[0]==c[0]&&b[1]==c[1]&&b[4]==c[4]&&b[5]==c[5]&&b[12]==c[12]&&b[13]==c[13]}function qk(b,c,d){var e=b[1],f=b[5],g=b[13],h=c[0];c=c[1];d[0]=b[0]*h+b[4]*c+b[12];d[1]=e*h+f*c+g;return d};function rk(b){dd.call(this);this.b=b}w(rk,dd);l=rk.prototype;l.Va=ca;l.lc=function(b,c,d,e){b=b.slice();qk(c.pixelToCoordinateMatrix,b,b);if(this.Va(b,c,pe,this))return d.call(e,this.b)};l.re=oe;l.Od=function(b,c){return function(d,e){return Sh(b,d,e,function(b){c[d]||(c[d]={});c[d][b.a.toString()]=b})}};l.$l=function(b){2===b.target.state&&sk(this)};function tk(b,c){var d=c.state;2!=d&&3!=d&&x(c,"change",b.$l,!1,b);0==d&&(c.load(),d=c.state);return 2==d} +function sk(b){var c=b.b;c.nb()&&"ready"==c.nf()&&b.s()}function uk(b,c){Ah(c.b)&&b.postRenderFunctions.push(sa(function(b,c,f){c=ma(b).toString();b=b.b;f=f.usedTiles[c];for(var g;Ah(b)&&!(c=b.a.zc,g=c.a[0].toString(),g in f&&f[g].contains(c.a));)b.pop().jd()},c))}function vk(b,c){if(null!=c){var d,e,f;e=0;for(f=c.length;e<f;++e)d=c[e],b[ma(d).toString()]=d}}function wk(b,c){var d=c.U;m(d)&&(ia(d)?b.logos[d]="":la(d)&&(b.logos[d.src]=d.href))} +function xk(b,c,d,e){c=ma(c).toString();d=d.toString();c in b?d in b[c]?(b=b[c][d],e.a<b.a&&(b.a=e.a),e.f>b.f&&(b.f=e.f),e.b<b.b&&(b.b=e.b),e.c>b.c&&(b.c=e.c)):b[c][d]=e:(b[c]={},b[c][d]=e)}function yk(b,c,d){return[c*(Math.round(b[0]/c)+d[0]%2/2),c*(Math.round(b[1]/c)+d[1]%2/2)]} +function zk(b,c,d,e,f,g,h,k,n,p){var q=ma(c).toString();q in b.wantedTiles||(b.wantedTiles[q]={});var r=b.wantedTiles[q];b=b.tileQueue;var t=d.minZoom,v,B,z,E,A,y;for(y=h;y>=t;--y)for(B=jg(d,g,y,B),z=d.ua(y),E=B.a;E<=B.f;++E)for(A=B.b;A<=B.c;++A)h-y<=k?(v=c.cc(y,E,A,e,f),0==v.state&&(r[eg(v.a)]=!0,v.pb()in b.c||Ak(b,[v,q,gg(d,v.a),z])),m(n)&&n.call(p,v)):c.Pf(y,E,A)};function Bk(b){this.u=b.opacity;this.G=b.rotateWithView;this.B=b.rotation;this.v=b.scale;this.fa=b.snapToPixel}l=Bk.prototype;l.ve=function(){return this.u};l.Xd=function(){return this.G};l.we=function(){return this.B};l.xe=function(){return this.v};l.Yd=function(){return this.fa};l.ye=function(b){this.B=b};l.ze=function(b){this.v=b};function Ck(b){b=m(b)?b:{};this.g=m(b.anchor)?b.anchor:[.5,.5];this.f=null;this.b=m(b.anchorOrigin)?b.anchorOrigin:"top-left";this.j=m(b.anchorXUnits)?b.anchorXUnits:"fraction";this.l=m(b.anchorYUnits)?b.anchorYUnits:"fraction";var c=m(b.crossOrigin)?b.crossOrigin:null,d=m(b.img)?b.img:null,e=m(b.imgSize)?b.imgSize:null,f=b.src;m(f)&&0!==f.length||null===d||(f=d.src);var g=m(b.src)?0:2,h=Dk.Pa(),k=h.get(f,c);null===k&&(k=new Ek(d,f,e,c,g),h.set(f,c,k));this.a=k;this.da=m(b.offset)?b.offset:[0,0]; +this.c=m(b.offsetOrigin)?b.offsetOrigin:"top-left";this.i=null;this.A=m(b.size)?b.size:null;Bk.call(this,{opacity:m(b.opacity)?b.opacity:1,rotation:m(b.rotation)?b.rotation:0,scale:m(b.scale)?b.scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0,rotateWithView:m(b.rotateWithView)?b.rotateWithView:!1})}w(Ck,Bk);l=Ck.prototype; +l.zb=function(){if(null!==this.f)return this.f;var b=this.g,c=this.fb();if("fraction"==this.j||"fraction"==this.l){if(null===c)return null;b=this.g.slice();"fraction"==this.j&&(b[0]*=c[0]);"fraction"==this.l&&(b[1]*=c[1])}if("top-left"!=this.b){if(null===c)return null;b===this.g&&(b=this.g.slice());if("top-right"==this.b||"bottom-right"==this.b)b[0]=-b[0]+c[0];if("bottom-left"==this.b||"bottom-right"==this.b)b[1]=-b[1]+c[1]}return this.f=b};l.Tb=function(){return this.a.a};l.Td=function(){return this.a.b}; +l.wd=function(){return this.a.c};l.ue=function(){var b=this.a;if(null===b.g)if(b.l){var c=b.b[0],d=b.b[1],e=Vi(c,d);e.fillRect(0,0,c,d);b.g=e.canvas}else b.g=b.a;return b.g};l.Eb=function(){if(null!==this.i)return this.i;var b=this.da;if("top-left"!=this.c){var c=this.fb(),d=this.a.b;if(null===c||null===d)return null;b=b.slice();if("top-right"==this.c||"bottom-right"==this.c)b[0]=d[0]-c[0]-b[0];if("bottom-left"==this.c||"bottom-right"==this.c)b[1]=d[1]-c[1]-b[1]}return this.i=b};l.Om=function(){return this.a.i}; +l.fb=function(){return null===this.A?this.a.b:this.A};l.rf=function(b,c){return x(this.a,"change",b,!1,c)};l.load=function(){this.a.load()};l.Of=function(b,c){Wc(this.a,"change",b,!1,c)};function Ek(b,c,d,e,f){ad.call(this);this.g=null;this.a=null===b?new Image:b;null!==e&&(this.a.crossOrigin=e);this.f=null;this.c=f;this.b=d;this.i=c;this.l=!1}w(Ek,ad);Ek.prototype.j=function(){this.c=3;Sa(this.f,Xc);this.f=null;C(this,"change")}; +Ek.prototype.B=function(){this.c=2;this.b=[this.a.width,this.a.height];Sa(this.f,Xc);this.f=null;var b=Vi(1,1);b.drawImage(this.a,0,0);try{b.getImageData(0,0,1,1)}catch(c){this.l=!0}C(this,"change")};Ek.prototype.load=function(){if(0==this.c){this.c=1;this.f=[Vc(this.a,"error",this.j,!1,this),Vc(this.a,"load",this.B,!1,this)];try{this.a.src=this.i}catch(b){this.j()}}};function Dk(){this.a={};this.b=0}da(Dk);Dk.prototype.clear=function(){this.a={};this.b=0}; +Dk.prototype.get=function(b,c){var d=c+":"+b;return d in this.a?this.a[d]:null};Dk.prototype.set=function(b,c,d){this.a[c+":"+b]=d;++this.b};function Fk(b,c){nc.call(this);this.i=c;this.f={};this.v={}}w(Fk,nc);function Gk(b){var c=b.viewState,d=b.coordinateToPixelMatrix;ok(d,b.size[0]/2,b.size[1]/2,1/c.resolution,-1/c.resolution,-c.rotation,-c.center[0],-c.center[1]);Gd(d,b.pixelToCoordinateMatrix)}l=Fk.prototype;l.X=function(){pb(this.f,rc);Fk.aa.X.call(this)}; +function Hk(){var b=Dk.Pa();if(32<b.b){var c=0,d,e;for(d in b.a){e=b.a[d];var f;if(f=0===(c++&3))Cc(e)?e=cd(e,void 0,void 0):(e=Rc(e),e=!!e&&Lc(e,void 0,void 0)),f=!e;f&&(delete b.a[d],--b.b)}}} +l.yf=function(b,c,d,e,f,g){var h,k=c.viewState,n=k.resolution,p=k.projection,k=b;if(p.g){h=p.R();var p=je(h),q=b[0];if(q<h[0]||q>h[2])k=Math.ceil((h[0]-q)/p),k=[q+p*k,b[1]]}p=c.layerStatesArray;for(q=p.length-1;0<=q;--q){h=p[q];var r=h.layer;if(!h.Qb||lk(h,n)&&f.call(g,r))if(h=Ik(this,r).Va(Eh(r.ca())?k:b,c,d,e))return h}}; +l.Xg=function(b,c,d,e,f,g){var h,k=c.viewState.resolution,n=c.layerStatesArray,p;for(p=n.length-1;0<=p;--p){h=n[p];var q=h.layer;if(lk(h,k)&&f.call(g,q)&&(h=Ik(this,q).lc(b,c,d,e)))return h}};l.Yg=function(b,c,d,e){b=this.yf(b,c,pe,this,d,e);return m(b)};function Ik(b,c){var d=ma(c).toString();if(d in b.f)return b.f[d];var e=b.Ze(c);b.f[d]=e;b.v[d]=x(e,"change",b.ik,!1,b);return e}l.ik=function(){this.i.render()};l.Ge=ca; +l.eo=function(b,c){for(var d in this.f)if(!(null!==c&&d in c.layerStates)){var e=d,f=this.f[e];delete this.f[e];Xc(this.v[e]);delete this.v[e];rc(f)}};function Jk(b,c){for(var d in b.f)if(!(d in c.layerStates)){c.postRenderFunctions.push(ra(b.eo,b));break}};function Kk(b,c){this.j=b;this.g=c;this.a=[];this.b=[];this.c={}}Kk.prototype.clear=function(){this.a.length=0;this.b.length=0;yb(this.c)};function Lk(b){var c=b.a,d=b.b,e=c[0];1==c.length?(c.length=0,d.length=0):(c[0]=c.pop(),d[0]=d.pop(),Mk(b,0));c=b.g(e);delete b.c[c];return e}function Ak(b,c){var d=b.j(c);Infinity!=d&&(b.a.push(c),b.b.push(d),b.c[b.g(c)]=!0,Nk(b,0,b.a.length-1))}Kk.prototype.ac=function(){return this.a.length};Kk.prototype.wa=function(){return 0===this.a.length}; +function Mk(b,c){for(var d=b.a,e=b.b,f=d.length,g=d[c],h=e[c],k=c;c<f>>1;){var n=2*c+1,p=2*c+2,n=p<f&&e[p]<e[n]?p:n;d[c]=d[n];e[c]=e[n];c=n}d[c]=g;e[c]=h;Nk(b,k,c)}function Nk(b,c,d){var e=b.a;b=b.b;for(var f=e[d],g=b[d];d>c;){var h=d-1>>1;if(b[h]>g)e[d]=e[h],b[d]=b[h],d=h;else break}e[d]=f;b[d]=g}function Ok(b){var c=b.j,d=b.a,e=b.b,f=0,g=d.length,h,k,n;for(k=0;k<g;++k)h=d[k],n=c(h),Infinity==n?delete b.c[b.g(h)]:(e[f]=n,d[f++]=h);d.length=f;e.length=f;for(c=(b.a.length>>1)-1;0<=c;c--)Mk(b,c)};function Pk(b,c){Kk.call(this,function(c){return b.apply(null,c)},function(b){return b[0].pb()});this.l=c;this.f=0}w(Pk,Kk);Pk.prototype.i=function(b){b=b.target;var c=b.state;if(2===c||3===c||4===c)Wc(b,"change",this.i,!1,this),--this.f,this.l()};function Qk(b,c,d){for(var e=0,f;b.f<c&&e<d&&0<b.ac();)f=Lk(b)[0],0===f.state&&(x(f,"change",b.i,!1,b),f.load(),++b.f,++e)};function Rk(b,c,d){this.f=b;this.c=c;this.i=d;this.a=[];this.b=this.g=0}Rk.prototype.update=function(b,c){this.a.push(b,c,ua())};function Sk(b,c){var d=b.f,e=b.b,f=b.c-e,g=Tk(b);return Zf({source:c,duration:g,easing:function(b){return e*(Math.exp(d*b*g)-1)/f}})}function Tk(b){return Math.log(b.c/b.b)/b.f};function Uk(b){gd.call(this);this.v=null;this.c(!0);this.handleEvent=b.handleEvent}w(Uk,gd);Uk.prototype.b=function(){return this.get("active")};Uk.prototype.c=function(b){this.set("active",b)};Uk.prototype.setMap=function(b){this.v=b};function Vk(b,c,d,e,f){if(null!=d){var g=c.Ea(),h=c.Ka();m(g)&&m(h)&&m(f)&&0<f&&(b.Oa($f({rotation:g,duration:f,easing:Vf})),m(e)&&b.Oa(Zf({source:h,duration:f,easing:Vf})));c.rotate(d,e)}} +function Wk(b,c,d,e,f){var g=c.Da();d=c.constrainResolution(g,d,0);Xk(b,c,d,e,f)}function Xk(b,c,d,e,f){if(null!=d){var g=c.Da(),h=c.Ka();m(g)&&m(h)&&m(f)&&0<f&&(b.Oa(ag({resolution:g,duration:f,easing:Vf})),m(e)&&b.Oa(Zf({source:h,duration:f,easing:Vf})));if(null!=e){var k;b=c.Ka();f=c.Da();m(b)&&m(f)&&(k=[e[0]-d*(e[0]-b[0])/f,e[1]-d*(e[1]-b[1])/f]);c.eb(k)}c.Yb(d)}};function Yk(b){b=m(b)?b:{};this.f=m(b.delta)?b.delta:1;Uk.call(this,{handleEvent:Zk});this.g=m(b.duration)?b.duration:250}w(Yk,Uk);function Zk(b){var c=!1,d=b.a;if(b.type==Yj){var c=b.map,e=b.coordinate,d=d.f?-this.f:this.f,f=c.Y();Wk(c,f,d,e,this.g);b.preventDefault();c=!0}return!c};function $k(b){b=b.a;return b.b&&!b.j&&b.f}function al(b){return"pointermove"==b.type}function bl(b){return b.type==Zj}function cl(b){b=b.a;return!b.b&&!b.j&&!b.f}function dl(b){b=b.a;return!b.b&&!b.j&&b.f}function el(b){b=b.a.target.tagName;return"INPUT"!==b&&"SELECT"!==b&&"TEXTAREA"!==b}function fl(b){return 1==b.b.pointerId};function gl(b){b=m(b)?b:{};Uk.call(this,{handleEvent:m(b.handleEvent)?b.handleEvent:hl});this.Xc=m(b.handleDownEvent)?b.handleDownEvent:oe;this.Qe=m(b.handleDragEvent)?b.handleDragEvent:ca;this.zi=m(b.handleMoveEvent)?b.handleMoveEvent:ca;this.fi=m(b.handleUpEvent)?b.handleUpEvent:oe;this.A=!1;this.$={};this.i=[]}w(gl,Uk);function il(b){for(var c=b.length,d=0,e=0,f=0;f<c;f++)d+=b[f].clientX,e+=b[f].clientY;return[d/c,e/c]} +function hl(b){if(!(b instanceof Uj))return!0;var c=!1,d=b.type;if(d===dk||d===fk||d===bk)d=b.b,b.type==bk?delete this.$[d.pointerId]:b.type==dk?this.$[d.pointerId]=d:d.pointerId in this.$&&(this.$[d.pointerId]=d),this.i=sb(this.$);this.A&&(b.type==fk?this.Qe(b):b.type==bk&&(this.A=this.fi(b)));b.type==dk?(this.A=b=this.Xc(b),c=this.wc(b)):b.type==ek&&this.zi(b);return!c}gl.prototype.wc=re;function jl(b){gl.call(this,{handleDownEvent:kl,handleDragEvent:ll,handleUpEvent:ml});b=m(b)?b:{};this.f=b.kinetic;this.g=this.j=null;this.u=m(b.condition)?b.condition:cl;this.l=!1}w(jl,gl);function ll(b){var c=il(this.i);this.f&&this.f.update(c[0],c[1]);if(null!==this.g){var d=this.g[0]-c[0],e=c[1]-this.g[1];b=b.map;var f=b.Y(),g=Rf(f),e=d=[d,e],h=g.resolution;e[0]*=h;e[1]*=h;sd(d,g.rotation);nd(d,g.center);d=f.Nd(d);b.render();f.eb(d)}this.g=c} +function ml(b){b=b.map;var c=b.Y();if(0===this.i.length){var d;if(d=!this.l&&this.f)if(d=this.f,6>d.a.length)d=!1;else{var e=ua()-d.i,f=d.a.length-3;if(d.a[f+2]<e)d=!1;else{for(var g=f-3;0<g&&d.a[g+2]>e;)g-=3;var e=d.a[f+2]-d.a[g+2],h=d.a[f]-d.a[g],f=d.a[f+1]-d.a[g+1];d.g=Math.atan2(f,h);d.b=Math.sqrt(h*h+f*f)/e;d=d.b>d.c}}d&&(d=this.f,d=(d.c-d.b)/d.f,f=this.f.g,g=c.Ka(),this.j=Sk(this.f,g),b.Oa(this.j),g=b.ya(g),d=b.ta([g[0]-d*Math.cos(f),g[1]-d*Math.sin(f)]),d=c.Nd(d),c.eb(d));Uf(c,-1);b.render(); +return!1}this.g=null;return!0}function kl(b){if(0<this.i.length&&this.u(b)){var c=b.map,d=c.Y();this.g=null;this.A||Uf(d,1);c.render();null!==this.j&&Za(c.C,this.j)&&(d.eb(b.frameState.viewState.center),this.j=null);this.f&&(b=this.f,b.a.length=0,b.g=0,b.b=0);this.l=1<this.i.length;return!0}return!1}jl.prototype.wc=oe;function nl(b){b=m(b)?b:{};gl.call(this,{handleDownEvent:pl,handleDragEvent:ql,handleUpEvent:rl});this.g=m(b.condition)?b.condition:$k;this.f=void 0;this.j=m(b.duration)?b.duration:250}w(nl,gl);function ql(b){if(fl(b)){var c=b.map,d=c.Ca();b=b.pixel;d=Math.atan2(d[1]/2-b[1],b[0]-d[0]/2);if(m(this.f)){b=d-this.f;var e=c.Y(),f=e.Ea();c.render();Vk(c,e,f-b)}this.f=d}} +function rl(b){if(!fl(b))return!0;b=b.map;var c=b.Y();Uf(c,-1);var d=c.Ea(),e=this.j,d=c.constrainRotation(d,0);Vk(b,c,d,void 0,e);return!1}function pl(b){return fl(b)&&Ac(b.a)&&this.g(b)?(b=b.map,Uf(b.Y(),1),b.render(),this.f=void 0,!0):!1}nl.prototype.wc=oe;function sl(b){this.c=this.b=this.f=this.g=this.a=null;this.j=b}w(sl,nc);function tl(b){var c=b.f,d=b.b;b=Ua([c,[c[0],d[1]],d,[d[0],c[1]]],b.a.ta,b.a);b[4]=b[0].slice();return new F([b])}sl.prototype.X=function(){this.setMap(null)};sl.prototype.i=function(b){var c=this.c,d=this.j;b.vectorContext.Cc(Infinity,function(b){b.Ha(d.f,d.c);b.Ia(d.b);b.Zb(c,null)})};sl.prototype.Z=function(){return this.c};function ul(b){null===b.a||null===b.f||null===b.b||b.a.render()} +sl.prototype.setMap=function(b){null!==this.g&&(Xc(this.g),this.g=null,this.a.render(),this.a=null);this.a=b;null!==this.a&&(this.g=x(b,"postcompose",this.i,!1,this),ul(this))};function vl(b,c){sc.call(this,b);this.coordinate=c}w(vl,sc);function wl(b){gl.call(this,{handleDownEvent:xl,handleDragEvent:yl,handleUpEvent:zl});b=m(b)?b:{};this.g=new sl(m(b.style)?b.style:null);this.f=null;this.l=m(b.condition)?b.condition:pe}w(wl,gl);function yl(b){if(fl(b)){var c=this.g;b=b.pixel;c.f=this.f;c.b=b;c.c=tl(c);ul(c)}}wl.prototype.Z=function(){return this.g.Z()};wl.prototype.j=ca; +function zl(b){if(!fl(b))return!0;this.g.setMap(null);var c=b.pixel[0]-this.f[0],d=b.pixel[1]-this.f[1];64<=c*c+d*d&&(this.j(b),C(this,new vl("boxend",b.coordinate)));return!1}function xl(b){if(fl(b)&&Ac(b.a)&&this.l(b)){this.f=b.pixel;this.g.setMap(b.map);var c=this.g,d=this.f;c.f=this.f;c.b=d;c.c=tl(c);ul(c);C(this,new vl("boxstart",b.coordinate));return!0}return!1};function Al(){this.b=-1};function Bl(){this.b=-1;this.b=64;this.a=Array(4);this.g=Array(this.b);this.f=this.c=0;this.a[0]=1732584193;this.a[1]=4023233417;this.a[2]=2562383102;this.a[3]=271733878;this.f=this.c=0}w(Bl,Al); +function Cl(b,c,d){d||(d=0);var e=Array(16);if(ia(c))for(var f=0;16>f;++f)e[f]=c.charCodeAt(d++)|c.charCodeAt(d++)<<8|c.charCodeAt(d++)<<16|c.charCodeAt(d++)<<24;else for(f=0;16>f;++f)e[f]=c[d++]|c[d++]<<8|c[d++]<<16|c[d++]<<24;c=b.a[0];d=b.a[1];var f=b.a[2],g=b.a[3],h=0,h=c+(g^d&(f^g))+e[0]+3614090360&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[1]+3905402710&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[2]+606105819&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^ c))+e[3]+3250441966&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(g^d&(f^g))+e[4]+4118548399&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[5]+1200080426&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[6]+2821735955&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^c))+e[7]+4249261313&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(g^d&(f^g))+e[8]+1770035416&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[9]+2336552879&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+ (d^g&(c^d))+e[10]+4294925233&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^c))+e[11]+2304563134&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(g^d&(f^g))+e[12]+1804603682&4294967295;c=d+(h<<7&4294967295|h>>>25);h=g+(f^c&(d^f))+e[13]+4254626195&4294967295;g=c+(h<<12&4294967295|h>>>20);h=f+(d^g&(c^d))+e[14]+2792965006&4294967295;f=g+(h<<17&4294967295|h>>>15);h=d+(c^f&(g^c))+e[15]+1236535329&4294967295;d=f+(h<<22&4294967295|h>>>10);h=c+(f^g&(d^f))+e[1]+4129170786&4294967295;c=d+(h<<5&4294967295| h>>>27);h=g+(d^f&(c^d))+e[6]+3225465664&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[11]+643717713&4294967295;f=g+(h<<14&4294967295|h>>>18);h=d+(g^c&(f^g))+e[0]+3921069994&4294967295;d=f+(h<<20&4294967295|h>>>12);h=c+(f^g&(d^f))+e[5]+3593408605&4294967295;c=d+(h<<5&4294967295|h>>>27);h=g+(d^f&(c^d))+e[10]+38016083&4294967295;g=c+(h<<9&4294967295|h>>>23);h=f+(c^d&(g^c))+e[15]+3634488961&4294967295;f=g+(h<<14&4294967295|h>>>18);h=d+(g^c&(f^g))+e[4]+3889429448&4294967295;d=f+(h<<20&4294967295| @@ -216,668 +210,744 @@ c^d)+e[7]+4139469664&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[10]+ g=c+(h<<11&4294967295|h>>>21);h=f+(g^c^d)+e[15]+530742520&4294967295;f=g+(h<<16&4294967295|h>>>16);h=d+(f^g^c)+e[2]+3299628645&4294967295;d=f+(h<<23&4294967295|h>>>9);h=c+(f^(d|~g))+e[0]+4096336452&4294967295;c=d+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[7]+1126891415&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[14]+2878612391&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[5]+4237533241&4294967295;d=f+(h<<21&4294967295|h>>>11);h=c+(f^(d|~g))+e[12]+1700485571&4294967295;c=d+ (h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[3]+2399980690&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[10]+4293915773&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[1]+2240044497&4294967295;d=f+(h<<21&4294967295|h>>>11);h=c+(f^(d|~g))+e[8]+1873313359&4294967295;c=d+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[15]+4264355552&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[6]+2734768916&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[13]+1309151649&4294967295; d=f+(h<<21&4294967295|h>>>11);h=c+(f^(d|~g))+e[4]+4149444226&4294967295;c=d+(h<<6&4294967295|h>>>26);h=g+(d^(c|~f))+e[11]+3174756917&4294967295;g=c+(h<<10&4294967295|h>>>22);h=f+(c^(g|~d))+e[2]+718787259&4294967295;f=g+(h<<15&4294967295|h>>>17);h=d+(g^(f|~c))+e[9]+3951481745&4294967295;b.a[0]=b.a[0]+c&4294967295;b.a[1]=b.a[1]+(f+(h<<21&4294967295|h>>>11))&4294967295;b.a[2]=b.a[2]+f&4294967295;b.a[3]=b.a[3]+g&4294967295} -Ll.prototype.update=function(b,c){m(c)||(c=b.length);for(var d=c-this.c,e=this.e,f=this.b,g=0;g<c;){if(0==f)for(;g<=d;)Ml(this,b,g),g+=this.c;if(ia(b))for(;g<c;){if(e[f++]=b.charCodeAt(g++),f==this.c){Ml(this,e);f=0;break}}else for(;g<c;)if(e[f++]=b[g++],f==this.c){Ml(this,e);f=0;break}}this.b=f;this.d+=c};function Nl(b){b=m(b)?b:{};this.a=m(b.color)?b.color:null;this.d=b.lineCap;this.b=m(b.lineDash)?b.lineDash:null;this.e=b.lineJoin;this.f=b.miterLimit;this.c=b.width;this.g=void 0}l=Nl.prototype;l.Vj=function(){return this.a};l.rh=function(){return this.d};l.Wj=function(){return this.b};l.sh=function(){return this.e};l.xh=function(){return this.f};l.Xj=function(){return this.c};l.Yj=function(b){this.a=b;this.g=void 0};l.el=function(b){this.d=b;this.g=void 0};l.Zj=function(b){this.b=b;this.g=void 0}; -l.fl=function(b){this.e=b;this.g=void 0};l.gl=function(b){this.f=b;this.g=void 0};l.ol=function(b){this.c=b;this.g=void 0}; -l.ub=function(){if(!m(this.g)){var b="s"+(null===this.a?"-":wg(this.a))+","+(m(this.d)?this.d.toString():"-")+","+(null===this.b?"-":this.b.toString())+","+(m(this.e)?this.e:"-")+","+(m(this.f)?this.f.toString():"-")+","+(m(this.c)?this.c.toString():"-"),c=new Ll;c.update(b);var d=Array((56>c.b?c.c:2*c.c)-c.b);d[0]=128;for(b=1;b<d.length-8;++b)d[b]=0;for(var e=8*c.d,b=d.length-8;b<d.length;++b)d[b]=e&255,e/=256;c.update(d);d=Array(16);for(b=e=0;4>b;++b)for(var f=0;32>f;f+=8)d[e++]=c.a[b]>>>f&255; -if(8192>d.length)c=String.fromCharCode.apply(null,d);else for(c="",b=0;b<d.length;b+=8192)c+=String.fromCharCode.apply(null,fb(d,b,b+8192));this.g=c}return this.g};var Ol=[0,0,0,1],Pl=[],Ql=[0,0,0,1];function Rl(b){b=m(b)?b:{};this.a=m(b.color)?b.color:null;this.c=void 0}Rl.prototype.b=function(){return this.a};Rl.prototype.d=function(b){this.a=b;this.c=void 0};Rl.prototype.ub=function(){m(this.c)||(this.c="f"+(null===this.a?"-":wg(this.a)));return this.c};function Sl(b){b=m(b)?b:{};this.f=this.a=this.e=null;this.d=m(b.fill)?b.fill:null;this.c=m(b.stroke)?b.stroke:null;this.b=b.radius;this.j=[0,0];this.o=this.t=this.g=null;var c=b.atlasManager,d,e=null,f,g=0;null!==this.c&&(f=wg(this.c.a),g=this.c.c,m(g)||(g=1),e=this.c.b,cg||(e=null));var h=2*(this.b+g)+1;f={strokeStyle:f,Nc:g,size:h,lineDash:e};m(c)?(h=Math.round(h),(e=null===this.d)&&(d=sa(this.Bf,this,f)),g=this.ub(),f=c.add(g,h,h,sa(this.Cf,this,f),d),this.a=f.image,this.j=[f.offsetX,f.offsetY], -d=f.image.width,this.f=e?f.kf:this.a):(this.a=If("CANVAS"),this.a.height=h,this.a.width=h,d=h=this.a.width,c=this.a.getContext("2d"),this.Cf(f,c,0,0),null===this.d?(c=this.f=If("CANVAS"),c.height=f.size,c.width=f.size,c=c.getContext("2d"),this.Bf(f,c,0,0)):this.f=this.a);this.g=[h/2,h/2];this.t=[h,h];this.o=[d,d];Rj.call(this,{opacity:1,rotateWithView:!1,rotation:0,scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0})}u(Sl,Rj);l=Sl.prototype;l.tb=function(){return this.g};l.Mj=function(){return this.d}; -l.te=function(){return this.f};l.yb=function(){return this.a};l.ue=function(){return 2};l.cd=function(){return this.o};l.zb=function(){return this.j};l.Nj=function(){return this.b};l.ab=function(){return this.t};l.Oj=function(){return this.c};l.ne=ca;l.load=ca;l.Ge=ca; -l.Cf=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(b.size/2,b.size/2,this.b,0,2*Math.PI,!0);null!==this.d&&(c.fillStyle=wg(this.d.a),c.fill());null!==this.c&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()}; -l.Bf=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(b.size/2,b.size/2,this.b,0,2*Math.PI,!0);c.fillStyle=Ol;c.fill();null!==this.c&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()};l.ub=function(){var b=null===this.c?"-":this.c.ub(),c=null===this.d?"-":this.d.ub();if(null===this.e||b!=this.e[1]||c!=this.e[2]||this.b!=this.e[3])this.e=["c"+b+c+(m(this.b)?this.b.toString():"-"),b,c,this.b];return this.e[0]};function Tl(b){b=m(b)?b:{};this.g=null;this.d=Ul;m(b.geometry)&&this.Ff(b.geometry);this.e=m(b.fill)?b.fill:null;this.f=m(b.image)?b.image:null;this.b=m(b.stroke)?b.stroke:null;this.c=m(b.text)?b.text:null;this.a=b.zIndex}l=Tl.prototype;l.N=function(){return this.g};l.lh=function(){return this.d};l.$j=function(){return this.e};l.ak=function(){return this.f};l.bk=function(){return this.b};l.ck=function(){return this.c};l.Nh=function(){return this.a}; -l.Ff=function(b){ka(b)?this.d=b:ia(b)?this.d=function(c){return c.get(b)}:null===b?this.d=Ul:m(b)&&(this.d=function(){return b});this.g=b};l.ql=function(b){this.a=b};function Vl(b){ka(b)||(b=ga(b)?b:[b],b=bd(b));return b}function Wl(){var b=new Rl({color:"rgba(255,255,255,0.4)"}),c=new Nl({color:"#3399CC",width:1.25}),d=[new Tl({image:new Sl({fill:b,stroke:c,radius:5}),fill:b,stroke:c})];Wl=function(){return d};return d} -function Xl(){var b={},c=[255,255,255,1],d=[0,153,255,1];b.Polygon=[new Tl({fill:new Rl({color:[255,255,255,.5]})})];b.MultiPolygon=b.Polygon;b.LineString=[new Tl({stroke:new Nl({color:c,width:5})}),new Tl({stroke:new Nl({color:d,width:3})})];b.MultiLineString=b.LineString;b.Point=[new Tl({image:new Sl({radius:6,fill:new Rl({color:d}),stroke:new Nl({color:c,width:1.5})}),zIndex:Infinity})];b.MultiPoint=b.Point;b.GeometryCollection=b.Polygon.concat(b.Point);return b}function Ul(b){return b.N()};function Yl(b){var c=m(b)?b:{};b=m(c.condition)?c.condition:yk;c=m(c.style)?c.style:new Tl({stroke:new Nl({color:[0,0,255,1]})});Fl.call(this,{condition:b,style:c})}u(Yl,Fl);Yl.prototype.g=function(){var b=this.n,c=b.a(),d=this.N().D(),e=ke(d),f=b.e(),d=c.n(d,f),d=c.constrainResolution(d,0,void 0);rk(b,c,d,e,200)};function Zl(b){ok.call(this,{handleEvent:$l});b=m(b)?b:{};this.d=m(b.condition)?b.condition:id(xk,zk);this.e=m(b.pixelDelta)?b.pixelDelta:128}u(Zl,ok); -function $l(b){var c=!1;if("key"==b.type){var d=b.a.e;if(this.d(b)&&(40==d||37==d||39==d||38==d)){var e=b.map,c=e.a(),f=$e(c),g=f.resolution*this.e,h=0,k=0;40==d?k=-g:37==d?h=-g:39==d?h=g:k=g;d=[h,k];Dd(d,f.rotation);f=c.a();m(f)&&(m(100)&&e.Ua(gf({source:f,duration:100,easing:ef})),e=c.i([f[0]+d[0],f[1]+d[1]]),c.Oa(e));b.preventDefault();c=!0}}return!c};function am(b){ok.call(this,{handleEvent:bm});b=m(b)?b:{};this.e=m(b.condition)?b.condition:zk;this.d=m(b.delta)?b.delta:1;this.f=m(b.duration)?b.duration:100}u(am,ok);function bm(b){var c=!1;if("key"==b.type){var d=b.a.i;if(this.e(b)&&(43==d||45==d)){c=b.map;d=43==d?this.d:-this.d;c.render();var e=c.a();qk(c,e,d,void 0,this.f);b.preventDefault();c=!0}}return!c};function cm(b){ok.call(this,{handleEvent:dm});b=m(b)?b:{};this.d=0;this.j=m(b.duration)?b.duration:250;this.f=null;this.g=this.e=void 0}u(cm,ok);function dm(b){var c=!1;if("mousewheel"==b.type){var c=b.map,d=b.a;this.f=b.coordinate;this.d+=d.j;m(this.e)||(this.e=ua());d=Math.max(80-(ua()-this.e),0);ba.clearTimeout(this.g);this.g=ba.setTimeout(sa(this.i,this,c),d);b.preventDefault();c=!0}return!c} -cm.prototype.i=function(b){var c=Yb(this.d,-1,1),d=b.a();b.render();qk(b,d,-c,this.f,this.j);this.d=0;this.f=null;this.g=this.e=void 0};function em(b){Bk.call(this,{handleDownEvent:fm,handleDragEvent:gm,handleUpEvent:hm});b=m(b)?b:{};this.f=null;this.g=void 0;this.d=!1;this.i=0;this.j=m(b.threshold)?b.threshold:.3}u(em,Bk); -function gm(b){var c=0,d=this.e[0],e=this.e[1],d=Math.atan2(e.clientY-d.clientY,e.clientX-d.clientX);m(this.g)&&(c=d-this.g,this.i+=c,!this.d&&Math.abs(this.i)>this.j&&(this.d=!0));this.g=d;b=b.map;d=Og(b.b);e=Dk(this.e);e[0]-=d.x;e[1]-=d.y;this.f=b.Ga(e);this.d&&(d=b.a(),e=$e(d),b.render(),pk(b,d,e.rotation+c,this.f))}function hm(b){if(2>this.e.length){b=b.map;var c=b.a();bf(c,-1);if(this.d){var d=$e(c).rotation,e=this.f,d=c.constrainRotation(d,0);pk(b,c,d,e,250)}return!1}return!0} -function fm(b){return 2<=this.e.length?(b=b.map,this.f=null,this.g=void 0,this.d=!1,this.i=0,this.p||bf(b.a(),1),b.render(),!0):!1}em.prototype.q=cd;function im(b){Bk.call(this,{handleDownEvent:jm,handleDragEvent:km,handleUpEvent:lm});b=m(b)?b:{};this.f=null;this.i=m(b.duration)?b.duration:400;this.d=void 0;this.g=1}u(im,Bk);function km(b){var c=1,d=this.e[0],e=this.e[1],f=d.clientX-e.clientX,d=d.clientY-e.clientY,f=Math.sqrt(f*f+d*d);m(this.d)&&(c=this.d/f);this.d=f;1!=c&&(this.g=c);b=b.map;var f=b.a(),d=$e(f),e=Og(b.b),g=Dk(this.e);g[0]-=e.x;g[1]-=e.y;this.f=b.Ga(g);b.render();rk(b,f,d.resolution*c,this.f)} -function lm(b){if(2>this.e.length){b=b.map;var c=b.a();bf(c,-1);var d=$e(c).resolution,e=this.f,f=this.i,d=c.constrainResolution(d,0,this.g-1);rk(b,c,d,e,f);return!1}return!0}function jm(b){return 2<=this.e.length?(b=b.map,this.f=null,this.d=void 0,this.g=1,this.p||bf(b.a(),1),b.render(),!0):!1}im.prototype.q=cd;function mm(b){b=m(b)?b:{};var c=new B,d=new lk(-.005,.05,100);(m(b.altShiftDragRotate)?b.altShiftDragRotate:1)&&c.push(new Ik);(m(b.doubleClickZoom)?b.doubleClickZoom:1)&&c.push(new sk({delta:b.zoomDelta,duration:b.zoomDuration}));(m(b.dragPan)?b.dragPan:1)&&c.push(new Ek({kinetic:d}));(m(b.pinchRotate)?b.pinchRotate:1)&&c.push(new em);(m(b.pinchZoom)?b.pinchZoom:1)&&c.push(new im({duration:b.zoomDuration}));if(m(b.keyboard)?b.keyboard:1)c.push(new Zl),c.push(new am({delta:b.zoomDelta,duration:b.zoomDuration})); -(m(b.mouseWheelZoom)?b.mouseWheelZoom:1)&&c.push(new cm({duration:b.zoomDuration}));(m(b.shiftDragZoom)?b.shiftDragZoom:1)&&c.push(new Yl);return c};function I(b){var c=m(b)?b:{};b=Cb(c);delete b.layers;c=c.layers;D.call(this,b);this.a=null;z(this,xd("layers"),this.ei,!1,this);null!=c?ga(c)&&(c=new B(cb(c))):c=new B;this.r(c)}u(I,D);l=I.prototype;l.ff=function(){this.b()&&this.l()}; -l.ei=function(){null!==this.a&&(Ta(rb(this.a),Yc),this.a=null);var b=this.Yb();if(null!=b){this.a={add:z(b,"add",this.di,!1,this),remove:z(b,"remove",this.fi,!1,this)};var b=b.a,c,d,e;c=0;for(d=b.length;c<d;c++)e=b[c],this.a[ma(e).toString()]=z(e,["propertychange","change"],this.ff,!1,this)}this.l()};l.di=function(b){b=b.element;this.a[ma(b).toString()]=z(b,["propertychange","change"],this.ff,!1,this);this.l()};l.fi=function(b){b=ma(b.element).toString();Yc(this.a[b]);delete this.a[b];this.l()}; -l.Yb=function(){return this.get("layers")};I.prototype.getLayers=I.prototype.Yb;I.prototype.r=function(b){this.set("layers",b)};I.prototype.setLayers=I.prototype.r; -I.prototype.Da=function(b){var c=m(b)?b:[],d=c.length;this.Yb().forEach(function(b){b.Da(c)});b=sj(this);var e,f;for(e=c.length;d<e;d++)f=c[d],f.brightness=Yb(f.brightness+b.brightness,-1,1),f.contrast*=b.contrast,f.hue+=b.hue,f.opacity*=b.opacity,f.saturation*=b.saturation,f.visible=f.visible&&b.visible,f.maxResolution=Math.min(f.maxResolution,b.maxResolution),f.minResolution=Math.max(f.minResolution,b.minResolution),m(b.extent)&&(f.extent=m(f.extent)?pe(f.extent,b.extent):b.extent);return c}; -I.prototype.Ta=function(){return"ready"};function nm(b){Be.call(this,{code:b,units:"m",extent:om,global:!0,worldExtent:pm})}u(nm,Be);nm.prototype.je=function(b,c){var d=c[1]/6378137;return b/((Math.exp(d)+Math.exp(-d))/2)};var qm=6378137*Math.PI,om=[-qm,-qm,qm,qm],pm=[-180,-85,180,85],Me=Va("EPSG:3857 EPSG:102100 EPSG:102113 EPSG:900913 urn:ogc:def:crs:EPSG:6.18:3:3857 urn:ogc:def:crs:EPSG::3857 http://www.opengis.net/gml/srs/epsg.xml#3857".split(" "),function(b){return new nm(b)}); -function Ne(b,c,d){var e=b.length;d=1<d?d:2;m(c)||(2<d?c=b.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=6378137*Math.PI*b[f]/180,c[f+1]=6378137*Math.log(Math.tan(Math.PI*(b[f+1]+90)/360));return c}function Oe(b,c,d){var e=b.length;d=1<d?d:2;m(c)||(2<d?c=b.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=180*b[f]/(6378137*Math.PI),c[f+1]=360*Math.atan(Math.exp(b[f+1]/6378137))/Math.PI-90;return c};function rm(b,c){Be.call(this,{code:b,units:"degrees",extent:sm,axisOrientation:c,global:!0,worldExtent:sm})}u(rm,Be);rm.prototype.je=function(b){return b};var sm=[-180,-90,180,90],Pe=[new rm("CRS:84"),new rm("EPSG:4326","neu"),new rm("urn:ogc:def:crs:EPSG::4326","neu"),new rm("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new rm("urn:ogc:def:crs:OGC:1.3:CRS84"),new rm("urn:ogc:def:crs:OGC:2:84"),new rm("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new rm("urn:x-ogc:def:crs:EPSG:4326","neu")];function tm(){He(Me);He(Pe);Le()};function J(b){E.call(this,m(b)?b:{})}u(J,E);function K(b){E.call(this,m(b)?b:{})}u(K,E);K.prototype.r=function(){return this.get("preload")};K.prototype.getPreload=K.prototype.r;K.prototype.oa=function(b){this.set("preload",b)};K.prototype.setPreload=K.prototype.oa;K.prototype.ea=function(){return this.get("useInterimTilesOnError")};K.prototype.getUseInterimTilesOnError=K.prototype.ea;K.prototype.qa=function(b){this.set("useInterimTilesOnError",b)};K.prototype.setUseInterimTilesOnError=K.prototype.qa;function L(b){b=m(b)?b:{};var c=Cb(b);delete c.style;E.call(this,c);this.Ab=m(b.renderBuffer)?b.renderBuffer:100;this.eb=null;this.r=void 0;this.oa(b.style)}u(L,E);L.prototype.Uc=function(){return this.eb};L.prototype.Vc=function(){return this.r};L.prototype.oa=function(b){this.eb=m(b)?b:Wl;this.r=null===b?void 0:Vl(this.eb);this.l()};function um(b,c,d,e,f){this.p={};this.b=b;this.r=c;this.e=d;this.F=e;this.eb=f;this.f=this.a=this.c=this.Ra=this.ga=this.la=null;this.ea=this.Sa=this.j=this.V=this.Q=this.J=0;this.Da=!1;this.g=this.oa=0;this.Ea=!1;this.ba=0;this.d="";this.i=this.t=this.Ta=this.qa=0;this.ca=this.n=this.o=null;this.q=[];this.ec=Kd()} -function vm(b,c,d){if(null!==b.f){c=Nk(c,0,d,2,b.F,b.q);d=b.b;var e=b.ec,f=d.globalAlpha;1!=b.j&&(d.globalAlpha=f*b.j);var g=b.oa;b.Da&&(g+=b.eb);var h,k;h=0;for(k=c.length;h<k;h+=2){var n=c[h]-b.J,p=c[h+1]-b.Q;b.Ea&&(n=n+.5|0,p=p+.5|0);if(0!==g||1!=b.g){var q=n+b.J,r=p+b.Q;Vj(e,q,r,b.g,b.g,g,-q,-r);d.setTransform(e[0],e[1],e[4],e[5],e[12],e[13])}d.drawImage(b.f,b.Sa,b.ea,b.ba,b.V,n,p,b.ba,b.V)}0===g&&1==b.g||d.setTransform(1,0,0,1,0,0);1!=b.j&&(d.globalAlpha=f)}} -function wm(b,c,d,e){var f=0;if(null!==b.ca&&""!==b.d){null===b.o||xm(b,b.o);null===b.n||ym(b,b.n);var g=b.ca,h=b.b,k=b.Ra;null===k?(h.font=g.font,h.textAlign=g.textAlign,h.textBaseline=g.textBaseline,b.Ra={font:g.font,textAlign:g.textAlign,textBaseline:g.textBaseline}):(k.font!=g.font&&(k.font=h.font=g.font),k.textAlign!=g.textAlign&&(k.textAlign=h.textAlign=g.textAlign),k.textBaseline!=g.textBaseline&&(k.textBaseline=h.textBaseline=g.textBaseline));c=Nk(c,f,d,e,b.F,b.q);for(g=b.b;f<d;f+=e){h=c[f]+ -b.qa;k=c[f+1]+b.Ta;if(0!==b.t||1!=b.i){var n=Vj(b.ec,h,k,b.i,b.i,b.t,-h,-k);g.setTransform(n[0],n[1],n[4],n[5],n[12],n[13])}null===b.n||g.strokeText(b.d,h,k);null===b.o||g.fillText(b.d,h,k)}0===b.t&&1==b.i||g.setTransform(1,0,0,1,0,0)}}function zm(b,c,d,e,f,g){var h=b.b;b=Nk(c,d,e,f,b.F,b.q);h.moveTo(b[0],b[1]);for(c=2;c<b.length;c+=2)h.lineTo(b[c],b[c+1]);g&&h.lineTo(b[0],b[1]);return e}function Am(b,c,d,e,f){var g=b.b,h,k;h=0;for(k=e.length;h<k;++h)d=zm(b,c,d,e[h],f,!0),g.closePath();return d} -l=um.prototype;l.ic=function(b,c){var d=b.toString(),e=this.p[d];m(e)?e.push(c):this.p[d]=[c]};l.jc=function(b){if(qe(this.e,b.D())){if(null!==this.c||null!==this.a){null===this.c||xm(this,this.c);null===this.a||ym(this,this.a);var c;c=b.k;c=null===c?null:Nk(c,0,c.length,b.s,this.F,this.q);var d=c[2]-c[0],e=c[3]-c[1],d=Math.sqrt(d*d+e*e),e=this.b;e.beginPath();e.arc(c[0],c[1],d,0,2*Math.PI);null===this.c||e.fill();null===this.a||e.stroke()}""!==this.d&&wm(this,b.qe(),2,2)}}; -l.ee=function(b,c){var d=(0,c.d)(b);if(null!=d&&qe(this.e,d.D())){var e=c.a;m(e)||(e=0);this.ic(e,function(b){b.wa(c.e,c.b);b.cb(c.f);b.xa(c.c);Bm[d.H()].call(b,d,null)})}};l.Zc=function(b,c){var d=b.d,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e];Bm[g.H()].call(this,g,c)}};l.rb=function(b){var c=b.k;b=b.s;null===this.f||vm(this,c,c.length);""!==this.d&&wm(this,c,c.length,b)};l.qb=function(b){var c=b.k;b=b.s;null===this.f||vm(this,c,c.length);""!==this.d&&wm(this,c,c.length,b)}; -l.Cb=function(b){if(qe(this.e,b.D())){if(null!==this.a){ym(this,this.a);var c=this.b,d=b.k;c.beginPath();zm(this,d,0,d.length,b.s,!1);c.stroke()}""!==this.d&&(b=Cm(b),wm(this,b,2,2))}};l.kc=function(b){var c=b.D();if(qe(this.e,c)){if(null!==this.a){ym(this,this.a);var c=this.b,d=b.k,e=0,f=b.b,g=b.s;c.beginPath();var h,k;h=0;for(k=f.length;h<k;++h)e=zm(this,d,e,f[h],g,!1);c.stroke()}""!==this.d&&(b=Dm(b),wm(this,b,b.length,2))}}; -l.Sb=function(b){if(qe(this.e,b.D())){if(null!==this.a||null!==this.c){null===this.c||xm(this,this.c);null===this.a||ym(this,this.a);var c=this.b;c.beginPath();Am(this,xl(b),0,b.b,b.s);null===this.c||c.fill();null===this.a||c.stroke()}""!==this.d&&(b=yl(b),wm(this,b,2,2))}}; -l.lc=function(b){if(qe(this.e,b.D())){if(null!==this.a||null!==this.c){null===this.c||xm(this,this.c);null===this.a||ym(this,this.a);var c=this.b,d=Em(b),e=0,f=b.b,g=b.s,h,k;h=0;for(k=f.length;h<k;++h){var n=f[h];c.beginPath();e=Am(this,d,e,n,g);null===this.c||c.fill();null===this.a||c.stroke()}}""!==this.d&&(b=Fm(b),wm(this,b,b.length,2))}};function Gm(b){var c=Va(sb(b.p),Number);gb(c);var d,e,f,g,h;d=0;for(e=c.length;d<e;++d)for(f=b.p[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](b)} -function xm(b,c){var d=b.b,e=b.la;null===e?(d.fillStyle=c.fillStyle,b.la={fillStyle:c.fillStyle}):e.fillStyle!=c.fillStyle&&(e.fillStyle=d.fillStyle=c.fillStyle)} -function ym(b,c){var d=b.b,e=b.ga;null===e?(d.lineCap=c.lineCap,cg&&d.setLineDash(c.lineDash),d.lineJoin=c.lineJoin,d.lineWidth=c.lineWidth,d.miterLimit=c.miterLimit,d.strokeStyle=c.strokeStyle,b.ga={lineCap:c.lineCap,lineDash:c.lineDash,lineJoin:c.lineJoin,lineWidth:c.lineWidth,miterLimit:c.miterLimit,strokeStyle:c.strokeStyle}):(e.lineCap!=c.lineCap&&(e.lineCap=d.lineCap=c.lineCap),cg&&!ib(e.lineDash,c.lineDash)&&d.setLineDash(e.lineDash=c.lineDash),e.lineJoin!=c.lineJoin&&(e.lineJoin=d.lineJoin= +Bl.prototype.update=function(b,c){m(c)||(c=b.length);for(var d=c-this.b,e=this.g,f=this.c,g=0;g<c;){if(0==f)for(;g<=d;)Cl(this,b,g),g+=this.b;if(ia(b))for(;g<c;){if(e[f++]=b.charCodeAt(g++),f==this.b){Cl(this,e);f=0;break}}else for(;g<c;)if(e[f++]=b[g++],f==this.b){Cl(this,e);f=0;break}}this.c=f;this.f+=c};function Dl(b){b=m(b)?b:{};this.a=m(b.color)?b.color:null;this.f=b.lineCap;this.c=m(b.lineDash)?b.lineDash:null;this.g=b.lineJoin;this.i=b.miterLimit;this.b=b.width;this.j=void 0}l=Dl.prototype;l.Um=function(){return this.a};l.xj=function(){return this.f};l.Vm=function(){return this.c};l.yj=function(){return this.g};l.Dj=function(){return this.i};l.Wm=function(){return this.b};l.Xm=function(b){this.a=b;this.j=void 0};l.qo=function(b){this.f=b;this.j=void 0};l.Ym=function(b){this.c=b;this.j=void 0}; +l.ro=function(b){this.g=b;this.j=void 0};l.so=function(b){this.i=b;this.j=void 0};l.Ao=function(b){this.b=b;this.j=void 0}; +l.Ab=function(){if(!m(this.j)){var b="s"+(null===this.a?"-":yg(this.a))+","+(m(this.f)?this.f.toString():"-")+","+(null===this.c?"-":this.c.toString())+","+(m(this.g)?this.g:"-")+","+(m(this.i)?this.i.toString():"-")+","+(m(this.b)?this.b.toString():"-"),c=new Bl;c.update(b);var d=Array((56>c.c?c.b:2*c.b)-c.c);d[0]=128;for(b=1;b<d.length-8;++b)d[b]=0;for(var e=8*c.f,b=d.length-8;b<d.length;++b)d[b]=e&255,e/=256;c.update(d);d=Array(16);for(b=e=0;4>b;++b)for(var f=0;32>f;f+=8)d[e++]=c.a[b]>>>f&255; +if(8192>d.length)c=String.fromCharCode.apply(null,d);else for(c="",b=0;b<d.length;b+=8192)c+=String.fromCharCode.apply(null,eb(d,b,b+8192));this.j=c}return this.j};var El=[0,0,0,1],Fl=[],Gl=[0,0,0,1];function Hl(b){b=m(b)?b:{};this.a=m(b.color)?b.color:null;this.b=void 0}Hl.prototype.c=function(){return this.a};Hl.prototype.f=function(b){this.a=b;this.b=void 0};Hl.prototype.Ab=function(){m(this.b)||(this.b="f"+(null===this.a?"-":yg(this.a)));return this.b};function Il(b){b=m(b)?b:{};this.i=this.a=this.g=null;this.f=m(b.fill)?b.fill:null;this.b=m(b.stroke)?b.stroke:null;this.c=b.radius;this.A=[0,0];this.l=this.da=this.j=null;var c=b.atlasManager,d,e=null,f,g=0;null!==this.b&&(f=yg(this.b.a),g=this.b.b,m(g)||(g=1),e=this.b.c,dj||(e=null));var h=2*(this.c+g)+1;f={strokeStyle:f,Ad:g,size:h,lineDash:e};m(c)?(h=Math.round(h),(e=null===this.f)&&(d=ra(this.dh,this,f)),g=this.Ab(),f=c.add(g,h,h,ra(this.eh,this,f),d),this.a=f.image,this.A=[f.offsetX,f.offsetY], +d=f.image.width,this.i=e?f.Cg:this.a):(this.a=Pg("CANVAS"),this.a.height=h,this.a.width=h,d=h=this.a.width,c=this.a.getContext("2d"),this.eh(f,c,0,0),null===this.f?(c=this.i=Pg("CANVAS"),c.height=f.size,c.width=f.size,c=c.getContext("2d"),this.dh(f,c,0,0)):this.i=this.a);this.j=[h/2,h/2];this.da=[h,h];this.l=[d,d];Bk.call(this,{opacity:1,rotateWithView:!1,rotation:0,scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0})}w(Il,Bk);l=Il.prototype;l.zb=function(){return this.j};l.Lm=function(){return this.f}; +l.ue=function(){return this.i};l.Tb=function(){return this.a};l.wd=function(){return 2};l.Td=function(){return this.l};l.Eb=function(){return this.A};l.Mm=function(){return this.c};l.fb=function(){return this.da};l.Nm=function(){return this.b};l.rf=ca;l.load=ca;l.Of=ca; +l.eh=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(b.size/2,b.size/2,this.c,0,2*Math.PI,!0);null!==this.f&&(c.fillStyle=yg(this.f.a),c.fill());null!==this.b&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Ad,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()}; +l.dh=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();c.arc(b.size/2,b.size/2,this.c,0,2*Math.PI,!0);c.fillStyle=El;c.fill();null!==this.b&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Ad,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()};l.Ab=function(){var b=null===this.b?"-":this.b.Ab(),c=null===this.f?"-":this.f.Ab();if(null===this.g||b!=this.g[1]||c!=this.g[2]||this.c!=this.g[3])this.g=["c"+b+c+(m(this.c)?this.c.toString():"-"),b,c,this.c];return this.g[0]};function Jl(b){b=m(b)?b:{};this.j=null;this.g=Kl;m(b.geometry)&&this.hh(b.geometry);this.f=m(b.fill)?b.fill:null;this.i=m(b.image)?b.image:null;this.c=m(b.stroke)?b.stroke:null;this.b=m(b.text)?b.text:null;this.a=b.zIndex}l=Jl.prototype;l.Z=function(){return this.j};l.rj=function(){return this.g};l.Zm=function(){return this.f};l.$m=function(){return this.i};l.an=function(){return this.c};l.bn=function(){return this.b};l.Vj=function(){return this.a}; +l.hh=function(b){ka(b)?this.g=b:ia(b)?this.g=function(c){return c.get(b)}:null===b?this.g=Kl:m(b)&&(this.g=function(){return b});this.j=b};l.Co=function(b){this.a=b};function Ll(b){ka(b)||(b=ga(b)?b:[b],b=ne(b));return b}function Ml(){var b=new Hl({color:"rgba(255,255,255,0.4)"}),c=new Dl({color:"#3399CC",width:1.25}),d=[new Jl({image:new Il({fill:b,stroke:c,radius:5}),fill:b,stroke:c})];Ml=function(){return d};return d} +function Nl(){var b={},c=[255,255,255,1],d=[0,153,255,1];b.Polygon=[new Jl({fill:new Hl({color:[255,255,255,.5]})})];b.MultiPolygon=b.Polygon;b.LineString=[new Jl({stroke:new Dl({color:c,width:5})}),new Jl({stroke:new Dl({color:d,width:3})})];b.MultiLineString=b.LineString;b.Circle=b.Polygon.concat(b.LineString);b.Point=[new Jl({image:new Il({radius:6,fill:new Hl({color:d}),stroke:new Dl({color:c,width:1.5})}),zIndex:Infinity})];b.MultiPoint=b.Point;b.GeometryCollection=b.Polygon.concat(b.Point); +return b}function Kl(b){return b.Z()};function Ol(b){var c=m(b)?b:{};b=m(c.condition)?c.condition:dl;this.u=m(c.duration)?c.duration:200;c=m(c.style)?c.style:new Jl({stroke:new Dl({color:[0,0,255,1]})});wl.call(this,{condition:b,style:c})}w(Ol,wl);Ol.prototype.j=function(){var b=this.v,c=b.Y(),d=this.Z().R(),e=ee(d),f=b.Ca(),d=Math.max(je(d)/f[0],ge(d)/f[1]),f=this.u,d=c.constrainResolution(d,0,void 0);Xk(b,c,d,e,f)};function Pl(b){Uk.call(this,{handleEvent:Ql});b=m(b)?b:{};this.f=m(b.condition)?b.condition:ue(cl,el);this.g=m(b.duration)?b.duration:100;this.i=m(b.pixelDelta)?b.pixelDelta:128}w(Pl,Uk); +function Ql(b){var c=!1;if("key"==b.type){var d=b.a.g;if(this.f(b)&&(40==d||37==d||39==d||38==d)){var e=b.map,c=e.Y(),f=Rf(c),g=f.resolution*this.i,h=0,k=0;40==d?k=-g:37==d?h=-g:39==d?h=g:k=g;d=[h,k];sd(d,f.rotation);f=this.g;g=c.Ka();m(g)&&(m(f)&&0<f&&e.Oa(Zf({source:g,duration:f,easing:Xf})),e=c.Nd([g[0]+d[0],g[1]+d[1]]),c.eb(e));b.preventDefault();c=!0}}return!c};function Rl(b){Uk.call(this,{handleEvent:Sl});b=m(b)?b:{};this.g=m(b.condition)?b.condition:el;this.f=m(b.delta)?b.delta:1;this.i=m(b.duration)?b.duration:100}w(Rl,Uk);function Sl(b){var c=!1;if("key"==b.type){var d=b.a.B;if(this.g(b)&&(43==d||45==d)){c=b.map;d=43==d?this.f:-this.f;c.render();var e=c.Y();Wk(c,e,d,void 0,this.i);b.preventDefault();c=!0}}return!c};function Tl(b){Uk.call(this,{handleEvent:Ul});b=m(b)?b:{};this.f=0;this.A=m(b.duration)?b.duration:250;this.i=null;this.j=this.g=void 0}w(Tl,Uk);function Ul(b){var c=!1;if("mousewheel"==b.type){var c=b.map,d=b.a;this.i=b.coordinate;this.f+=d.A;m(this.g)||(this.g=ua());d=Math.max(80-(ua()-this.g),0);ba.clearTimeout(this.j);this.j=ba.setTimeout(ra(this.l,this,c),d);b.preventDefault();c=!0}return!c} +Tl.prototype.l=function(b){var c=Wb(this.f,-1,1),d=b.Y();b.render();Wk(b,d,-c,this.i,this.A);this.f=0;this.i=null;this.j=this.g=void 0};function Vl(b){gl.call(this,{handleDownEvent:Wl,handleDragEvent:Xl,handleUpEvent:Yl});b=m(b)?b:{};this.g=null;this.j=void 0;this.f=!1;this.l=0;this.G=m(b.threshold)?b.threshold:.3;this.u=m(b.duration)?b.duration:250}w(Vl,gl); +function Xl(b){var c=0,d=this.i[0],e=this.i[1],d=Math.atan2(e.clientY-d.clientY,e.clientX-d.clientX);m(this.j)&&(c=d-this.j,this.l+=c,!this.f&&Math.abs(this.l)>this.G&&(this.f=!0));this.j=d;b=b.map;d=lh(b.b);e=il(this.i);e[0]-=d.x;e[1]-=d.y;this.g=b.ta(e);this.f&&(d=b.Y(),e=d.Ea(),b.render(),Vk(b,d,e+c,this.g))}function Yl(b){if(2>this.i.length){b=b.map;var c=b.Y();Uf(c,-1);if(this.f){var d=c.Ea(),e=this.g,f=this.u,d=c.constrainRotation(d,0);Vk(b,c,d,e,f)}return!1}return!0} +function Wl(b){return 2<=this.i.length?(b=b.map,this.g=null,this.j=void 0,this.f=!1,this.l=0,this.A||Uf(b.Y(),1),b.render(),!0):!1}Vl.prototype.wc=oe;function Zl(b){gl.call(this,{handleDownEvent:$l,handleDragEvent:am,handleUpEvent:bm});b=m(b)?b:{};this.g=null;this.l=m(b.duration)?b.duration:400;this.f=void 0;this.j=1}w(Zl,gl);function am(b){var c=1,d=this.i[0],e=this.i[1],f=d.clientX-e.clientX,d=d.clientY-e.clientY,f=Math.sqrt(f*f+d*d);m(this.f)&&(c=this.f/f);this.f=f;1!=c&&(this.j=c);b=b.map;var f=b.Y(),d=f.Da(),e=lh(b.b),g=il(this.i);g[0]-=e.x;g[1]-=e.y;this.g=b.ta(g);b.render();Xk(b,f,d*c,this.g)} +function bm(b){if(2>this.i.length){b=b.map;var c=b.Y();Uf(c,-1);var d=c.Da(),e=this.g,f=this.l,d=c.constrainResolution(d,0,this.j-1);Xk(b,c,d,e,f);return!1}return!0}function $l(b){return 2<=this.i.length?(b=b.map,this.g=null,this.f=void 0,this.j=1,this.A||Uf(b.Y(),1),b.render(),!0):!1}Zl.prototype.wc=oe;function cm(b){b=m(b)?b:{};var c=new rg,d=new Rk(-.005,.05,100);(m(b.altShiftDragRotate)?b.altShiftDragRotate:1)&&c.push(new nl);(m(b.doubleClickZoom)?b.doubleClickZoom:1)&&c.push(new Yk({delta:b.zoomDelta,duration:b.zoomDuration}));(m(b.dragPan)?b.dragPan:1)&&c.push(new jl({kinetic:d}));(m(b.pinchRotate)?b.pinchRotate:1)&&c.push(new Vl);(m(b.pinchZoom)?b.pinchZoom:1)&&c.push(new Zl({duration:b.zoomDuration}));if(m(b.keyboard)?b.keyboard:1)c.push(new Pl),c.push(new Rl({delta:b.zoomDelta,duration:b.zoomDuration})); +(m(b.mouseWheelZoom)?b.mouseWheelZoom:1)&&c.push(new Tl({duration:b.zoomDuration}));(m(b.shiftDragZoom)?b.shiftDragZoom:1)&&c.push(new Ol);return c};function dm(b){var c=m(b)?b:{};b=Cb(c);delete b.layers;c=c.layers;hk.call(this,b);this.c=[];this.b={};x(this,id("layers"),this.kk,!1,this);null!=c?ga(c)&&(c=new rg(c.slice())):c=new rg;this.Lh(c)}w(dm,hk);l=dm.prototype;l.ce=function(){this.nb()&&this.s()}; +l.kk=function(){Sa(this.c,Xc);this.c.length=0;var b=this.Kc();this.c.push(x(b,"add",this.jk,!1,this),x(b,"remove",this.lk,!1,this));pb(this.b,function(b){Sa(b,Xc)});yb(this.b);var b=b.b,c,d,e;c=0;for(d=b.length;c<d;c++)e=b[c],this.b[ma(e).toString()]=[x(e,"propertychange",this.ce,!1,this),x(e,"change",this.ce,!1,this)];this.s()};l.jk=function(b){b=b.element;var c=ma(b).toString();this.b[c]=[x(b,"propertychange",this.ce,!1,this),x(b,"change",this.ce,!1,this)];this.s()}; +l.lk=function(b){b=ma(b.element).toString();Sa(this.b[b],Xc);delete this.b[b];this.s()};l.Kc=function(){return this.get("layers")};l.Lh=function(b){this.set("layers",b)}; +l.lf=function(b){var c=m(b)?b:[],d=c.length;this.Kc().forEach(function(b){b.lf(c)});b=ik(this);var e,f;for(e=c.length;d<e;d++)f=c[d],f.brightness=Wb(f.brightness+b.brightness,-1,1),f.contrast*=b.contrast,f.hue+=b.hue,f.opacity*=b.opacity,f.saturation*=b.saturation,f.visible=f.visible&&b.visible,f.maxResolution=Math.min(f.maxResolution,b.maxResolution),f.minResolution=Math.max(f.minResolution,b.minResolution),m(b.extent)&&(f.extent=m(f.extent)?he(f.extent,b.extent):b.extent);return c};l.nf=function(){return"ready"};function em(b){ye.call(this,{code:b,units:"m",extent:fm,global:!0,worldExtent:gm})}w(em,ye);em.prototype.getPointResolution=function(b,c){var d=c[1]/6378137;return b/((Math.exp(d)+Math.exp(-d))/2)};var hm=6378137*Math.PI,fm=[-hm,-hm,hm,hm],gm=[-180,-85,180,85],Ke=Ua("EPSG:3857 EPSG:102100 EPSG:102113 EPSG:900913 urn:ogc:def:crs:EPSG:6.18:3:3857 urn:ogc:def:crs:EPSG::3857 http://www.opengis.net/gml/srs/epsg.xml#3857".split(" "),function(b){return new em(b)}); +function Le(b,c,d){var e=b.length;d=1<d?d:2;m(c)||(2<d?c=b.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=6378137*Math.PI*b[f]/180,c[f+1]=6378137*Math.log(Math.tan(Math.PI*(b[f+1]+90)/360));return c}function Me(b,c,d){var e=b.length;d=1<d?d:2;m(c)||(2<d?c=b.slice():c=Array(e));for(var f=0;f<e;f+=d)c[f]=180*b[f]/(6378137*Math.PI),c[f+1]=360*Math.atan(Math.exp(b[f+1]/6378137))/Math.PI-90;return c};function im(b,c){ye.call(this,{code:b,units:"degrees",extent:jm,axisOrientation:c,global:!0,worldExtent:jm})}w(im,ye);im.prototype.getPointResolution=function(b){return b}; +var jm=[-180,-90,180,90],Ne=[new im("CRS:84"),new im("EPSG:4326","neu"),new im("urn:ogc:def:crs:EPSG::4326","neu"),new im("urn:ogc:def:crs:EPSG:6.6:4326","neu"),new im("urn:ogc:def:crs:OGC:1.3:CRS84"),new im("urn:ogc:def:crs:OGC:2:84"),new im("http://www.opengis.net/gml/srs/epsg.xml#4326","neu"),new im("urn:x-ogc:def:crs:EPSG:4326","neu")];function km(){Be(Ke);Be(Ne);Je()};function I(b){G.call(this,m(b)?b:{})}w(I,G);function K(b){b=m(b)?b:{};var c=Cb(b);delete c.preload;delete c.useInterimTilesOnError;G.call(this,c);this.f(m(b.preload)?b.preload:0);this.g(m(b.useInterimTilesOnError)?b.useInterimTilesOnError:!0)}w(K,G);K.prototype.b=function(){return this.get("preload")};K.prototype.f=function(b){this.set("preload",b)};K.prototype.c=function(){return this.get("useInterimTilesOnError")};K.prototype.g=function(b){this.set("useInterimTilesOnError",b)};function M(b){b=m(b)?b:{};var c=Cb(b);delete c.style;delete c.renderBuffer;delete c.updateWhileAnimating;delete c.updateWhileInteracting;G.call(this,c);this.c=m(b.renderBuffer)?b.renderBuffer:100;this.j=null;this.b=void 0;this.g(b.style);this.A=m(b.updateWhileAnimating)?b.updateWhileAnimating:!1;this.u=m(b.updateWhileInteracting)?b.updateWhileInteracting:!1}w(M,G);M.prototype.U=function(){return this.j};M.prototype.T=function(){return this.b}; +M.prototype.g=function(b){this.j=m(b)?b:Ml;this.b=null===b?void 0:Ll(this.j);this.s()};function lm(b,c,d,e,f){this.u={};this.c=b;this.fa=c;this.g=d;this.C=e;this.Xc=f;this.i=this.a=this.b=this.Ma=this.qa=this.ea=null;this.Na=this.xa=this.A=this.T=this.U=this.K=0;this.Xa=!1;this.j=this.rb=0;this.sb=!1;this.$=0;this.f="";this.B=this.da=this.ub=this.tb=0;this.ba=this.v=this.l=null;this.G=[];this.Hd=Ad()} +function mm(b,c,d){if(null!==b.i){c=Ye(c,0,d,2,b.C,b.G);d=b.c;var e=b.Hd,f=d.globalAlpha;1!=b.A&&(d.globalAlpha=f*b.A);var g=b.rb;b.Xa&&(g+=b.Xc);var h,k;h=0;for(k=c.length;h<k;h+=2){var n=c[h]-b.K,p=c[h+1]-b.U;b.sb&&(n=n+.5|0,p=p+.5|0);if(0!==g||1!=b.j){var q=n+b.K,r=p+b.U;ok(e,q,r,b.j,b.j,g,-q,-r);d.setTransform(e[0],e[1],e[4],e[5],e[12],e[13])}d.drawImage(b.i,b.xa,b.Na,b.$,b.T,n,p,b.$,b.T)}0===g&&1==b.j||d.setTransform(1,0,0,1,0,0);1!=b.A&&(d.globalAlpha=f)}} +function nm(b,c,d,e){var f=0;if(null!==b.ba&&""!==b.f){null===b.l||om(b,b.l);null===b.v||pm(b,b.v);var g=b.ba,h=b.c,k=b.Ma;null===k?(h.font=g.font,h.textAlign=g.textAlign,h.textBaseline=g.textBaseline,b.Ma={font:g.font,textAlign:g.textAlign,textBaseline:g.textBaseline}):(k.font!=g.font&&(k.font=h.font=g.font),k.textAlign!=g.textAlign&&(k.textAlign=h.textAlign=g.textAlign),k.textBaseline!=g.textBaseline&&(k.textBaseline=h.textBaseline=g.textBaseline));c=Ye(c,f,d,e,b.C,b.G);for(g=b.c;f<d;f+=e){h=c[f]+ +b.tb;k=c[f+1]+b.ub;if(0!==b.da||1!=b.B){var n=ok(b.Hd,h,k,b.B,b.B,b.da,-h,-k);g.setTransform(n[0],n[1],n[4],n[5],n[12],n[13])}null===b.v||g.strokeText(b.f,h,k);null===b.l||g.fillText(b.f,h,k)}0===b.da&&1==b.B||g.setTransform(1,0,0,1,0,0)}}function qm(b,c,d,e,f,g){var h=b.c;b=Ye(c,d,e,f,b.C,b.G);h.moveTo(b[0],b[1]);for(c=2;c<b.length;c+=2)h.lineTo(b[c],b[c+1]);g&&h.lineTo(b[0],b[1]);return e} +function rm(b,c,d,e,f){var g=b.c,h,k;h=0;for(k=e.length;h<k;++h)d=qm(b,c,d,e[h],f,!0),g.closePath();return d}l=lm.prototype;l.Cc=function(b,c){var d=b.toString(),e=this.u[d];m(e)?e.push(c):this.u[d]=[c]}; +l.Dc=function(b){if(ie(this.g,b.R())){if(null!==this.b||null!==this.a){null===this.b||om(this,this.b);null===this.a||pm(this,this.a);var c;c=b.o;c=null===c?null:Ye(c,0,c.length,b.H,this.C,this.G);var d=c[2]-c[0],e=c[3]-c[1],d=Math.sqrt(d*d+e*e),e=this.c;e.beginPath();e.arc(c[0],c[1],d,0,2*Math.PI);null===this.b||e.fill();null===this.a||e.stroke()}""!==this.f&&nm(this,b.rd(),2,2)}}; +l.$e=function(b,c){var d=(0,c.g)(b);if(null!=d&&ie(this.g,d.R())){var e=c.a;m(e)||(e=0);this.Cc(e,function(b){b.Ha(c.f,c.c);b.hb(c.i);b.Ia(c.b);tm[d.V()].call(b,d,null)})}};l.Pd=function(b,c){var d=b.f,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e];tm[g.V()].call(this,g,c)}};l.xb=function(b){var c=b.o;b=b.H;null===this.i||mm(this,c,c.length);""!==this.f&&nm(this,c,c.length,b)};l.wb=function(b){var c=b.o;b=b.H;null===this.i||mm(this,c,c.length);""!==this.f&&nm(this,c,c.length,b)}; +l.Hb=function(b){if(ie(this.g,b.R())){if(null!==this.a){pm(this,this.a);var c=this.c,d=b.o;c.beginPath();qm(this,d,0,d.length,b.H,!1);c.stroke()}""!==this.f&&(b=um(b),nm(this,b,2,2))}};l.Ec=function(b){var c=b.R();if(ie(this.g,c)){if(null!==this.a){pm(this,this.a);var c=this.c,d=b.o,e=0,f=b.c,g=b.H;c.beginPath();var h,k;h=0;for(k=f.length;h<k;++h)e=qm(this,d,e,f[h],g,!1);c.stroke()}""!==this.f&&(b=vm(b),nm(this,b,b.length,2))}}; +l.Zb=function(b){if(ie(this.g,b.R())){if(null!==this.a||null!==this.b){null===this.b||om(this,this.b);null===this.a||pm(this,this.a);var c=this.c;c.beginPath();rm(this,If(b),0,b.c,b.H);null===this.b||c.fill();null===this.a||c.stroke()}""!==this.f&&(b=Jf(b),nm(this,b,2,2))}}; +l.Fc=function(b){if(ie(this.g,b.R())){if(null!==this.a||null!==this.b){null===this.b||om(this,this.b);null===this.a||pm(this,this.a);var c=this.c,d=wm(b),e=0,f=b.c,g=b.H,h,k;h=0;for(k=f.length;h<k;++h){var n=f[h];c.beginPath();e=rm(this,d,e,n,g);null===this.b||c.fill();null===this.a||c.stroke()}}""!==this.f&&(b=xm(b),nm(this,b,b.length,2))}};function ym(b){var c=Ua(tb(b.u),Number);gb(c);var d,e,f,g,h;d=0;for(e=c.length;d<e;++d)for(f=b.u[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](b)} +function om(b,c){var d=b.c,e=b.ea;null===e?(d.fillStyle=c.fillStyle,b.ea={fillStyle:c.fillStyle}):e.fillStyle!=c.fillStyle&&(e.fillStyle=d.fillStyle=c.fillStyle)} +function pm(b,c){var d=b.c,e=b.qa;null===e?(d.lineCap=c.lineCap,dj&&d.setLineDash(c.lineDash),d.lineJoin=c.lineJoin,d.lineWidth=c.lineWidth,d.miterLimit=c.miterLimit,d.strokeStyle=c.strokeStyle,b.qa={lineCap:c.lineCap,lineDash:c.lineDash,lineJoin:c.lineJoin,lineWidth:c.lineWidth,miterLimit:c.miterLimit,strokeStyle:c.strokeStyle}):(e.lineCap!=c.lineCap&&(e.lineCap=d.lineCap=c.lineCap),dj&&!ib(e.lineDash,c.lineDash)&&d.setLineDash(e.lineDash=c.lineDash),e.lineJoin!=c.lineJoin&&(e.lineJoin=d.lineJoin= c.lineJoin),e.lineWidth!=c.lineWidth&&(e.lineWidth=d.lineWidth=c.lineWidth),e.miterLimit!=c.miterLimit&&(e.miterLimit=d.miterLimit=c.miterLimit),e.strokeStyle!=c.strokeStyle&&(e.strokeStyle=d.strokeStyle=c.strokeStyle))} -l.wa=function(b,c){if(null===b)this.c=null;else{var d=b.a;this.c={fillStyle:wg(null===d?Ol:d)}}if(null===c)this.a=null;else{var d=c.a,e=c.d,f=c.b,g=c.e,h=c.c,k=c.f;this.a={lineCap:m(e)?e:"round",lineDash:null!=f?f:Pl,lineJoin:m(g)?g:"round",lineWidth:this.r*(m(h)?h:1),miterLimit:m(k)?k:10,strokeStyle:wg(null===d?Ql:d)}}}; -l.cb=function(b){if(null===b)this.f=null;else{var c=b.tb(),d=b.yb(1),e=b.zb(),f=b.ab();this.J=c[0];this.Q=c[1];this.V=f[1];this.f=d;this.j=b.p;this.Sa=e[0];this.ea=e[1];this.Da=b.q;this.oa=b.i;this.g=b.n;this.Ea=b.r;this.ba=f[0]}}; -l.xa=function(b){if(null===b)this.d="";else{var c=b.a;null===c?this.o=null:(c=c.a,this.o={fillStyle:wg(null===c?Ol:c)});var d=b.f;if(null===d)this.n=null;else{var c=d.a,e=d.d,f=d.b,g=d.e,h=d.c,d=d.f;this.n={lineCap:m(e)?e:"round",lineDash:null!=f?f:Pl,lineJoin:m(g)?g:"round",lineWidth:m(h)?h:1,miterLimit:m(d)?d:10,strokeStyle:wg(null===c?Ql:c)}}var c=b.d,e=b.i,f=b.n,g=b.e,h=b.c,d=b.b,k=b.g;b=b.o;this.ca={font:m(c)?c:"10px sans-serif",textAlign:m(k)?k:"center",textBaseline:m(b)?b:"middle"};this.d= -m(d)?d:"";this.qa=m(e)?this.r*e:0;this.Ta=m(f)?this.r*f:0;this.t=m(g)?g:0;this.i=this.r*(m(h)?h:1)}};var Bm={Point:um.prototype.rb,LineString:um.prototype.Cb,Polygon:um.prototype.Sb,MultiPoint:um.prototype.qb,MultiLineString:um.prototype.kc,MultiPolygon:um.prototype.lc,GeometryCollection:um.prototype.Zc,Circle:um.prototype.jc};var Hm=["Polygon","LineString","Image","Text"];function Im(b,c,d){this.ga=b;this.V=c;this.d=0;this.resolution=d;this.J=this.F=null;this.c=[];this.coordinates=[];this.ca=Kd();this.a=[];this.ba=[];this.la=Kd()} -function Jm(b,c,d,e,f,g){var h=b.coordinates.length,k=b.he(),n=[c[d],c[d+1]],p=[NaN,NaN],q=!0,r,s,v;for(r=d+f;r<e;r+=f)p[0]=c[r],p[1]=c[r+1],v=be(k,p),v!==s?(q&&(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1]),b.coordinates[h++]=p[0],b.coordinates[h++]=p[1],q=!1):1===v?(b.coordinates[h++]=p[0],b.coordinates[h++]=p[1],q=!1):q=!0,n[0]=p[0],n[1]=p[1],s=v;r===d+f&&(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1]);g&&(b.coordinates[h++]=c[d],b.coordinates[h++]=c[d+1]);return h} -function Km(b,c){b.F=[0,c,0];b.c.push(b.F);b.J=[0,c,0];b.a.push(b.J)} -function Lm(b,c,d,e,f,g,h,k){var n;Wj(e,b.ca)?n=b.ba:(n=Nk(b.coordinates,0,b.coordinates.length,2,e,b.ba),Nd(b.ca,e));e=0;var p=h.length,q=0,r;for(b=b.la;e<p;){var s=h[e],v,y,C,F;switch(s[0]){case 0:q=s[1];q=ma(q).toString();m(x(g,q))?e=s[2]:++e;break;case 1:c.beginPath();++e;break;case 2:q=s[1];r=n[q];var G=n[q+1],w=n[q+2]-r,q=n[q+3]-G;c.arc(r,G,Math.sqrt(w*w+q*q),0,2*Math.PI,!0);++e;break;case 3:c.closePath();++e;break;case 4:q=s[1];r=s[2];v=s[3];C=s[4]*d;var U=s[5]*d,N=s[6];y=s[7];var Y=s[8],T= -s[9],G=s[11],w=s[12],qa=s[13],vb=s[14];for(s[10]&&(G+=f);q<r;q+=2){s=n[q]-C;F=n[q+1]-U;qa&&(s=s+.5|0,F=F+.5|0);if(1!=w||0!==G){var Ka=s+C,ac=F+U;Vj(b,Ka,ac,w,w,G,-Ka,-ac);c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13])}Ka=c.globalAlpha;1!=y&&(c.globalAlpha=Ka*y);c.drawImage(v,Y,T,vb,N,s,F,vb*d,N*d);1!=y&&(c.globalAlpha=Ka);1==w&&0===G||c.setTransform(1,0,0,1,0,0)}++e;break;case 5:q=s[1];r=s[2];C=s[3];U=s[4]*d;N=s[5]*d;G=s[6];w=s[7]*d;v=s[8];for(y=s[9];q<r;q+=2){s=n[q]+U;F=n[q+1]+N;if(1!=w||0!==G)Vj(b, -s,F,w,w,G,-s,-F),c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13]);y&&c.strokeText(C,s,F);v&&c.fillText(C,s,F);1==w&&0===G||c.setTransform(1,0,0,1,0,0)}++e;break;case 6:if(m(k)&&(q=s[1],q=k(q)))return q;++e;break;case 7:c.fill();++e;break;case 8:q=s[1];r=s[2];c.moveTo(n[q],n[q+1]);for(q+=2;q<r;q+=2)c.lineTo(n[q],n[q+1]);++e;break;case 9:c.fillStyle=s[1];++e;break;case 10:q=m(s[7])?s[7]:!0;r=s[2];c.strokeStyle=s[1];c.lineWidth=q?r*d:r;c.lineCap=s[3];c.lineJoin=s[4];c.miterLimit=s[5];cg&&c.setLineDash(s[6]); -++e;break;case 11:c.font=s[1];c.textAlign=s[2];c.textBaseline=s[3];++e;break;case 12:c.stroke();++e;break;default:++e}}}Im.prototype.Hc=function(b,c,d,e,f){Lm(this,b,c,d,e,f,this.c,void 0)};function Mm(b){var c=b.a;c.reverse();var d,e=c.length,f,g,h=-1;for(d=0;d<e;++d)if(f=c[d],g=f[0],6==g)h=d;else if(0==g){f[2]=d;f=b.a;for(g=d;h<g;){var k=f[h];f[h]=f[g];f[g]=k;++h;--g}h=-1}}function Nm(b,c){b.F[2]=b.c.length;b.F=null;b.J[2]=b.a.length;b.J=null;var d=[6,c];b.c.push(d);b.a.push(d)} -Im.prototype.Kb=ca;Im.prototype.he=function(){return this.V};function Om(b,c,d){Im.call(this,b,c,d);this.g=this.Q=null;this.t=this.r=this.q=this.p=this.j=this.n=this.i=this.o=this.f=this.e=this.b=void 0}u(Om,Im); -Om.prototype.rb=function(b,c){if(null!==this.g){Km(this,c);var d=b.k,e=this.coordinates.length,d=Jm(this,d,0,d.length,b.s,!1);this.c.push([4,e,d,this.g,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);this.a.push([4,e,d,this.Q,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);Nm(this,c)}}; -Om.prototype.qb=function(b,c){if(null!==this.g){Km(this,c);var d=b.k,e=this.coordinates.length,d=Jm(this,d,0,d.length,b.s,!1);this.c.push([4,e,d,this.g,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);this.a.push([4,e,d,this.Q,this.b,this.e,this.f,this.o,this.i,this.n,this.j,this.p,this.q,this.r,this.t]);Nm(this,c)}};Om.prototype.Kb=function(){Mm(this);this.e=this.b=void 0;this.g=this.Q=null;this.t=this.r=this.p=this.j=this.n=this.i=this.o=this.q=this.f=void 0}; -Om.prototype.cb=function(b){var c=b.tb(),d=b.ab(),e=b.te(1),f=b.yb(1),g=b.zb();this.b=c[0];this.e=c[1];this.Q=e;this.g=f;this.f=d[1];this.o=b.p;this.i=g[0];this.n=g[1];this.j=b.q;this.p=b.i;this.q=b.n;this.r=b.r;this.t=d[0]};function Pm(b,c,d){Im.call(this,b,c,d);this.b={Cc:void 0,xc:void 0,yc:null,zc:void 0,Ac:void 0,Bc:void 0,me:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}u(Pm,Im); -function Qm(b,c,d,e,f){var g=b.coordinates.length;c=Jm(b,c,d,e,f,!1);g=[8,g,c];b.c.push(g);b.a.push(g);return e}l=Pm.prototype;l.he=function(){var b=this.V;this.d&&(b=Yd(b,this.resolution*(this.d+1)/2));return b}; -function Rm(b){var c=b.b,d=c.strokeStyle,e=c.lineCap,f=c.lineDash,g=c.lineJoin,h=c.lineWidth,k=c.miterLimit;c.Cc==d&&c.xc==e&&ib(c.yc,f)&&c.zc==g&&c.Ac==h&&c.Bc==k||(c.me!=b.coordinates.length&&(b.c.push([12]),c.me=b.coordinates.length),b.c.push([10,d,h,e,g,k,f],[1]),c.Cc=d,c.xc=e,c.yc=f,c.zc=g,c.Ac=h,c.Bc=k)} -l.Cb=function(b,c){var d=this.b,e=d.lineWidth;m(d.strokeStyle)&&m(e)&&(Rm(this),Km(this,c),this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]),d=b.k,Qm(this,d,0,d.length,b.s),this.a.push([12]),Nm(this,c))}; -l.kc=function(b,c){var d=this.b,e=d.lineWidth;if(m(d.strokeStyle)&&m(e)){Rm(this);Km(this,c);this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]);var d=b.b,e=b.k,f=b.s,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Qm(this,e,g,d[h],f);this.a.push([12]);Nm(this,c)}};l.Kb=function(){this.b.me!=this.coordinates.length&&this.c.push([12]);Mm(this);this.b=null}; -l.wa=function(b,c){var d=c.a;this.b.strokeStyle=wg(null===d?Ql:d);d=c.d;this.b.lineCap=m(d)?d:"round";d=c.b;this.b.lineDash=null===d?Pl:d;d=c.e;this.b.lineJoin=m(d)?d:"round";d=c.c;this.b.lineWidth=m(d)?d:1;d=c.f;this.b.miterLimit=m(d)?d:10;this.d=Math.max(this.d,this.b.lineWidth)}; -function Sm(b,c,d){Im.call(this,b,c,d);this.b={Ve:void 0,Cc:void 0,xc:void 0,yc:null,zc:void 0,Ac:void 0,Bc:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}u(Sm,Im); -function Tm(b,c,d,e,f){var g=b.b,h=[1];b.c.push(h);b.a.push(h);var k,h=0;for(k=e.length;h<k;++h){var n=e[h],p=b.coordinates.length;d=Jm(b,c,d,n,f,!0);d=[8,p,d];p=[3];b.c.push(d,p);b.a.push(d,p);d=n}c=[7];b.a.push(c);m(g.fillStyle)&&b.c.push(c);m(g.strokeStyle)&&(g=[12],b.c.push(g),b.a.push(g));return d}l=Sm.prototype; -l.jc=function(b,c){var d=this.b,e=d.strokeStyle;if(m(d.fillStyle)||m(e)){Um(this);Km(this,c);this.a.push([9,wg(Ol)]);m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var f=b.k,e=this.coordinates.length;Jm(this,f,0,f.length,b.s,!1);f=[1];e=[2,e];this.c.push(f,e);this.a.push(f,e);e=[7];this.a.push(e);m(d.fillStyle)&&this.c.push(e);m(d.strokeStyle)&&(d=[12],this.c.push(d),this.a.push(d));Nm(this,c)}}; -l.Sb=function(b,c){var d=this.b,e=d.strokeStyle;if(m(d.fillStyle)||m(e))Um(this),Km(this,c),this.a.push([9,wg(Ol)]),m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]),d=b.b,e=xl(b),Tm(this,e,0,d,b.s),Nm(this,c)}; -l.lc=function(b,c){var d=this.b,e=d.strokeStyle;if(m(d.fillStyle)||m(e)){Um(this);Km(this,c);this.a.push([9,wg(Ol)]);m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var d=b.b,e=Em(b),f=b.s,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Tm(this,e,g,d[h],f);Nm(this,c)}};l.Kb=function(){Mm(this);this.b=null;var b=this.ga;if(0!==b){var c=this.coordinates,d,e;d=0;for(e=c.length;d<e;++d)c[d]=b*Math.round(c[d]/b)}}; -l.he=function(){var b=this.V;this.d&&(b=Yd(b,this.resolution*(this.d+1)/2));return b}; -l.wa=function(b,c){var d=this.b;if(null===b)d.fillStyle=void 0;else{var e=b.a;d.fillStyle=wg(null===e?Ol:e)}null===c?(d.strokeStyle=void 0,d.lineCap=void 0,d.lineDash=null,d.lineJoin=void 0,d.lineWidth=void 0,d.miterLimit=void 0):(e=c.a,d.strokeStyle=wg(null===e?Ql:e),e=c.d,d.lineCap=m(e)?e:"round",e=c.b,d.lineDash=null===e?Pl:e.slice(),e=c.e,d.lineJoin=m(e)?e:"round",e=c.c,d.lineWidth=m(e)?e:1,e=c.f,d.miterLimit=m(e)?e:10,this.d=Math.max(this.d,d.lineWidth))}; -function Um(b){var c=b.b,d=c.fillStyle,e=c.strokeStyle,f=c.lineCap,g=c.lineDash,h=c.lineJoin,k=c.lineWidth,n=c.miterLimit;m(d)&&c.Ve!=d&&(b.c.push([9,d]),c.Ve=c.fillStyle);!m(e)||c.Cc==e&&c.xc==f&&c.yc==g&&c.zc==h&&c.Ac==k&&c.Bc==n||(b.c.push([10,e,k,f,h,n,g]),c.Cc=e,c.xc=f,c.yc=g,c.zc=h,c.Ac=k,c.Bc=n)}function Vm(b,c,d){Im.call(this,b,c,d);this.r=this.q=this.p=null;this.g="";this.j=this.n=this.i=this.o=0;this.f=this.e=this.b=null}u(Vm,Im); -Vm.prototype.sb=function(b,c,d,e,f,g){if(""!==this.g&&null!==this.f&&(null!==this.b||null!==this.e)){if(null!==this.b){f=this.b;var h=this.p;if(null===h||h.fillStyle!=f.fillStyle){var k=[9,f.fillStyle];this.c.push(k);this.a.push(k);null===h?this.p={fillStyle:f.fillStyle}:h.fillStyle=f.fillStyle}}null!==this.e&&(f=this.e,h=this.q,null===h||h.lineCap!=f.lineCap||h.lineDash!=f.lineDash||h.lineJoin!=f.lineJoin||h.lineWidth!=f.lineWidth||h.miterLimit!=f.miterLimit||h.strokeStyle!=f.strokeStyle)&&(k=[10, -f.strokeStyle,f.lineWidth,f.lineCap,f.lineJoin,f.miterLimit,f.lineDash,!1],this.c.push(k),this.a.push(k),null===h?this.q={lineCap:f.lineCap,lineDash:f.lineDash,lineJoin:f.lineJoin,lineWidth:f.lineWidth,miterLimit:f.miterLimit,strokeStyle:f.strokeStyle}:(h.lineCap=f.lineCap,h.lineDash=f.lineDash,h.lineJoin=f.lineJoin,h.lineWidth=f.lineWidth,h.miterLimit=f.miterLimit,h.strokeStyle=f.strokeStyle));f=this.f;h=this.r;if(null===h||h.font!=f.font||h.textAlign!=f.textAlign||h.textBaseline!=f.textBaseline)k= -[11,f.font,f.textAlign,f.textBaseline],this.c.push(k),this.a.push(k),null===h?this.r={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline}:(h.font=f.font,h.textAlign=f.textAlign,h.textBaseline=f.textBaseline);Km(this,g);f=this.coordinates.length;b=Jm(this,b,c,d,e,!1);b=[5,f,b,this.g,this.o,this.i,this.n,this.j,null!==this.b,null!==this.e];this.c.push(b);this.a.push(b);Nm(this,g)}}; -Vm.prototype.xa=function(b){if(null===b)this.g="";else{var c=b.a;null===c?this.b=null:(c=c.a,c=wg(null===c?Ol:c),null===this.b?this.b={fillStyle:c}:this.b.fillStyle=c);var d=b.f;if(null===d)this.e=null;else{var c=d.a,e=d.d,f=d.b,g=d.e,h=d.c,d=d.f,e=m(e)?e:"round",f=null!=f?f.slice():Pl,g=m(g)?g:"round",h=m(h)?h:1,d=m(d)?d:10,c=wg(null===c?Ql:c);if(null===this.e)this.e={lineCap:e,lineDash:f,lineJoin:g,lineWidth:h,miterLimit:d,strokeStyle:c};else{var k=this.e;k.lineCap=e;k.lineDash=f;k.lineJoin=g;k.lineWidth= -h;k.miterLimit=d;k.strokeStyle=c}}var n=b.d,c=b.i,e=b.n,f=b.e,h=b.c,d=b.b,g=b.g,k=b.o;b=m(n)?n:"10px sans-serif";g=m(g)?g:"center";k=m(k)?k:"middle";null===this.f?this.f={font:b,textAlign:g,textBaseline:k}:(n=this.f,n.font=b,n.textAlign=g,n.textBaseline=k);this.g=m(d)?d:"";this.o=m(c)?c:0;this.i=m(e)?e:0;this.n=m(f)?f:0;this.j=m(h)?h:1}};function Wm(b,c,d){this.g=b;this.b=c;this.f=d;this.c={};this.d=Tf(1,1);this.e=Kd()}function Xm(b){for(var c in b.c){var d=b.c[c],e;for(e in d)d[e].Kb()}} -function ck(b,c,d,e,f,g){var h=b.e;Vj(h,.5,.5,1/c,-1/c,-d,-e[0],-e[1]);var k=b.d;k.clearRect(0,0,1,1);return Ym(b,k,h,d,f,function(b){if(0<k.getImageData(0,0,1,1).data[3]){if(b=g(b))return b;k.clearRect(0,0,1,1)}})}Wm.prototype.a=function(b,c){var d=m(b)?b.toString():"0",e=this.c[d];m(e)||(e={},this.c[d]=e);d=e[c];m(d)||(d=new Zm[c](this.g,this.b,this.f),e[c]=d);return d};Wm.prototype.ia=function(){return xb(this.c)}; -function $m(b,c,d,e,f,g){var h=Va(sb(b.c),Number);gb(h);var k=b.b,n=k[0],p=k[1],q=k[2],k=k[3],n=[n,p,n,k,q,k,q,p];Nk(n,0,8,2,e,n);c.save();c.beginPath();c.moveTo(n[0],n[1]);c.lineTo(n[2],n[3]);c.lineTo(n[4],n[5]);c.lineTo(n[6],n[7]);c.closePath();c.clip();for(var r,s,n=0,p=h.length;n<p;++n)for(r=b.c[h[n].toString()],q=0,k=Hm.length;q<k;++q)s=r[Hm[q]],m(s)&&s.Hc(c,d,e,f,g);c.restore()} -function Ym(b,c,d,e,f,g){var h=Va(sb(b.c),Number);gb(h,function(b,c){return c-b});var k,n,p,q,r;k=0;for(n=h.length;k<n;++k)for(q=b.c[h[k].toString()],p=Hm.length-1;0<=p;--p)if(r=q[Hm[p]],m(r)&&(r=Lm(r,c,1,d,e,f,r.a,g)))return r}var Zm={Image:Om,LineString:Pm,Polygon:Sm,Text:Vm};function an(b,c){Hj.call(this,b,c);this.F=Kd()}u(an,Hj); -an.prototype.j=function(b,c,d){bn(this,"precompose",d,b,void 0);var e=this.p();if(null!==e){var f=c.extent,g=m(f);if(g){var h=me(f),k=je(f),n=ie(f),f=he(f);Xj(b.coordinateToPixelMatrix,h,h);Xj(b.coordinateToPixelMatrix,k,k);Xj(b.coordinateToPixelMatrix,n,n);Xj(b.coordinateToPixelMatrix,f,f);d.save();d.beginPath();d.moveTo(h[0],h[1]);d.lineTo(k[0],k[1]);d.lineTo(n[0],n[1]);d.lineTo(f[0],f[1]);d.clip()}h=this.n();k=d.globalAlpha;d.globalAlpha=c.opacity;0===b.viewState.rotation?(c=h[13],n=e.width*h[0], -f=e.height*h[5],d.drawImage(e,0,0,+e.width,+e.height,Math.round(h[12]),Math.round(c),Math.round(n),Math.round(f))):(d.setTransform(h[0],h[1],h[4],h[5],h[12],h[13]),d.drawImage(e,0,0),d.setTransform(1,0,0,1,0,0));d.globalAlpha=k;g&&d.restore()}bn(this,"postcompose",d,b,void 0)};function bn(b,c,d,e,f){var g=b.a;ld(g,c)&&(b=m(f)?f:cn(b,e),b=new um(d,e.pixelRatio,e.extent,b,e.viewState.rotation),g.dispatchEvent(new Al(c,g,b,null,e,d,null)),Gm(b))} -function cn(b,c){var d=c.viewState,e=c.pixelRatio;return Vj(b.F,e*c.size[0]/2,e*c.size[1]/2,e/d.resolution,-e/d.resolution,-d.rotation,-d.center[0],-d.center[1])}var dn=function(){var b=null,c=null;return function(d){if(null===b){b=Tf(1,1);c=b.createImageData(1,1);var e=c.data;e[0]=42;e[1]=84;e[2]=126;e[3]=255}var e=b.canvas,f=d[0]<=e.width&&d[1]<=e.height;f||(e.width=d[0],e.height=d[1],e=d[0]-1,d=d[1]-1,b.putImageData(c,e,d),d=b.getImageData(e,d,1,1),f=ib(c.data,d.data));return f}}();function en(b,c){an.call(this,b,c);this.c=null;this.d=Kd()}u(en,an);en.prototype.Zb=function(b,c,d,e){var f=this.a;return f.a().yd(c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};en.prototype.p=function(){return null===this.c?null:this.c.c()};en.prototype.n=function(){return this.d}; -en.prototype.i=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.center,g=e.resolution,h=e.rotation,k,n=this.a.a(),p=b.viewHints;k=b.extent;m(c.extent)&&(k=pe(k,c.extent));p[0]||p[1]||se(k)||(e=e.projection,p=n.g,null===p||(e=p),k=n.rc(k,g,d,e),null!==k&&(e=k.state,0==e?(Wc(k,"change",this.o,!1,this),k.load()):2==e&&(this.c=k)));if(null!==this.c){k=this.c;var e=k.D(),p=k.resolution,q=k.e,g=d*p/(g*q);Vj(this.d,d*b.size[0]/2,d*b.size[1]/2,g,g,h,q*(e[0]-f[0])/p,q*(f[1]-e[3])/p);Kj(b.attributions,k.f); -Lj(b,n)}return!0};function fn(b,c){an.call(this,b,c);this.c=this.e=null;this.g=!1;this.q=null;this.r=Kd();this.t=NaN;this.f=this.d=null}u(fn,an);fn.prototype.p=function(){return this.e};fn.prototype.n=function(){return this.r}; -fn.prototype.i=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.a,h=g.a(),k=Gj(h,f),n=h.bd(),p=ec(k.a,e.resolution,0),q=h.Gc(p,b.pixelRatio,f),r=k.ka(p),s=r/(q/k.sa(p)),v=e.center,y;r==e.resolution?(v=Oj(v,r,b.size),y=ne(v,r,e.rotation,b.size)):y=b.extent;m(c.extent)&&(y=pe(y,c.extent));if(se(y))return!1;var C=Aj(k,y,r),F=q*(C.d-C.a+1),G=q*(C.c-C.b+1),w,U;null===this.e?(U=Tf(F,G),this.e=U.canvas,this.c=[F,G],this.q=U,this.g=!dn(this.c)):(w=this.e,U=this.q,this.c[0]<F||this.c[1]< -G||this.g&&(this.c[0]>F||this.c[1]>G)?(w.width=F,w.height=G,this.c=[F,G],this.g=!dn(this.c),this.d=null):(F=this.c[0],G=this.c[1],p==this.t&&rf(this.d,C)||(this.d=null)));var N,Y;null===this.d?(F/=q,G/=q,N=C.a-Math.floor((F-(C.d-C.a+1))/2),Y=C.b-Math.floor((G-(C.c-C.b+1))/2),this.t=p,this.d=new of(N,N+F-1,Y,Y+G-1),this.f=Array(F*G),G=this.d):(G=this.d,F=G.d-G.a+1);w={};w[p]={};var T=[],qa=sa(h.fe,h,w,Nj(function(b){return null!==b&&2==b.state},h,d,f)),vb=g.ea();m(vb)||(vb=!0);var Ka=Vd(),ac=new of(0, -0,0,0),Sb,La,Pa;for(Y=C.a;Y<=C.d;++Y)for(Pa=C.b;Pa<=C.c;++Pa)La=h.Fb(p,Y,Pa,d,f),N=La.state,2==N||4==N||3==N&&!vb?w[p][nf(La.a)]=La:(Sb=k.$c(La.a,qa,null,ac,Ka),Sb||(T.push(La),Sb=k.kd(La.a,ac,Ka),null===Sb||qa(p+1,Sb)));qa=0;for(Sb=T.length;qa<Sb;++qa)La=T[qa],Y=q*(La.a[1]-G.a),Pa=q*(G.c-La.a[2]),U.clearRect(Y,Pa,q,q);T=Va(sb(w),Number);gb(T);var Ud=h.t,qd=me(yj(k,[p,G.a,G.c],Ka)),fd,ve,Wi,Eh,Bf,Gl,qa=0;for(Sb=T.length;qa<Sb;++qa)if(fd=T[qa],q=h.Gc(fd,d,f),Eh=w[fd],fd==p)for(Wi in Eh)La=Eh[Wi],ve= -(La.a[2]-G.b)*F+(La.a[1]-G.a),this.f[ve]!=La&&(Y=q*(La.a[1]-G.a),Pa=q*(G.c-La.a[2]),N=La.state,4!=N&&(3!=N||vb)&&Ud||U.clearRect(Y,Pa,q,q),2==N&&U.drawImage(La.Na(),n,n,q,q,Y,Pa,q,q),this.f[ve]=La);else for(Wi in fd=k.ka(fd)/r,Eh)for(La=Eh[Wi],ve=yj(k,La.a,Ka),Y=(ve[0]-qd[0])/s,Pa=(qd[1]-ve[3])/s,Gl=fd*q,Bf=fd*q,N=La.state,4!=N&&Ud||U.clearRect(Y,Pa,Gl,Bf),2==N&&U.drawImage(La.Na(),n,n,q,q,Y,Pa,Gl,Bf),La=zj(k,ve,p,ac),N=Math.max(La.a,G.a),Pa=Math.min(La.d,G.d),Y=Math.max(La.b,G.b),La=Math.min(La.c, -G.c);N<=Pa;++N)for(Bf=Y;Bf<=La;++Bf)ve=(Bf-G.b)*F+(N-G.a),this.f[ve]=void 0;Mj(b.usedTiles,h,p,C);Pj(b,h,k,d,f,y,p,g.r());Jj(b,h);Lj(b,h);Vj(this.r,d*b.size[0]/2,d*b.size[1]/2,d*s/e.resolution,d*s/e.resolution,e.rotation,(qd[0]-v[0])/s,(v[1]-qd[1])/s);return!0};function gn(b,c,d){Ok.call(this);this.ag(b,m(c)?c:0,d)}u(gn,Ok);l=gn.prototype;l.clone=function(){var b=new gn(null);Qk(b,this.a,this.k.slice());b.l();return b};l.Va=function(b,c,d,e){var f=this.k;b-=f[0];var g=c-f[1];c=b*b+g*g;if(c<e){if(0===c)for(e=0;e<this.s;++e)d[e]=f[e];else for(e=this.vf()/Math.sqrt(c),d[0]=f[0]+e*b,d[1]=f[1]+e*g,e=2;e<this.s;++e)d[e]=f[e];d.length=this.s;return c}return e};l.Jb=function(b,c){var d=this.k,e=b-d[0],d=c-d[1];return e*e+d*d<=hn(this)}; -l.qe=function(){return this.k.slice(0,this.s)};l.D=function(b){if(this.g!=this.c){var c=this.k,d=c[this.s]-c[0];this.extent=Xd(c[0]-d,c[1]-d,c[0]+d,c[1]+d,this.extent);this.g=this.c}return te(this.extent,b)};l.vf=function(){return Math.sqrt(hn(this))};function hn(b){var c=b.k[b.s]-b.k[0];b=b.k[b.s+1]-b.k[1];return c*c+b*b}l.H=function(){return"Circle"};l.kj=function(b){var c=this.s,d=b.slice();d[c]=d[0]+(this.k[c]-this.k[0]);var e;for(e=1;e<c;++e)d[c+e]=b[e];Qk(this,this.a,d);this.l()}; -l.ag=function(b,c,d){if(null===b)Qk(this,"XY",null);else{Rk(this,d,b,0);null===this.k&&(this.k=[]);d=this.k;b=al(d,b);d[b++]=d[0]+c;var e;c=1;for(e=this.s;c<e;++c)d[b++]=d[c];d.length=b}this.l()};l.jl=function(b){this.k[this.s]=this.k[0]+b;this.l()};function jn(b){Mk.call(this);this.d=m(b)?b:null;kn(this)}u(jn,Mk);function ln(b){var c=[],d,e;d=0;for(e=b.length;d<e;++d)c.push(b[d].clone());return c}function mn(b){var c,d;if(null!==b.d)for(c=0,d=b.d.length;c<d;++c)Xc(b.d[c],"change",b.l,!1,b)}function kn(b){var c,d;if(null!==b.d)for(c=0,d=b.d.length;c<d;++c)z(b.d[c],"change",b.l,!1,b)}l=jn.prototype;l.clone=function(){var b=new jn(null);b.bg(this.d);return b}; -l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;var f=this.d,g,h;g=0;for(h=f.length;g<h;++g)e=f[g].Va(b,c,d,e);return e};l.Jb=function(b,c){var d=this.d,e,f;e=0;for(f=d.length;e<f;++e)if(d[e].Jb(b,c))return!0;return!1};l.D=function(b){if(this.g!=this.c){var c=Xd(Infinity,Infinity,-Infinity,-Infinity,this.extent),d=this.d,e,f;e=0;for(f=d.length;e<f;++e)ee(c,d[e].D());this.extent=c;this.g=this.c}return te(this.extent,b)};l.af=function(){return ln(this.d)}; -l.ke=function(b){this.j!=this.c&&(yb(this.o),this.i=0,this.j=this.c);if(0>b||0!==this.i&&b<this.i)return this;var c=b.toString();if(this.o.hasOwnProperty(c))return this.o[c];var d=[],e=this.d,f=!1,g,h;g=0;for(h=e.length;g<h;++g){var k=e[g],n=k.ke(b);d.push(n);n!==k&&(f=!0)}if(f)return b=new jn(null),mn(b),b.d=d,kn(b),b.l(),this.o[c]=b;this.i=b;return this};l.H=function(){return"GeometryCollection"};l.ha=function(b){var c=this.d,d,e;d=0;for(e=c.length;d<e;++d)if(c[d].ha(b))return!0;return!1}; -l.ia=function(){return 0==this.d.length};l.bg=function(b){b=ln(b);mn(this);this.d=b;kn(this);this.l()};l.ma=function(b){var c=this.d,d,e;d=0;for(e=c.length;d<e;++d)c[d].ma(b);this.l()};l.Aa=function(b,c){var d=this.d,e,f;e=0;for(f=d.length;e<f;++e)d[e].Aa(b,c);this.l()};l.M=function(){mn(this);jn.S.M.call(this)};function nn(b,c,d,e,f){var g=NaN,h=NaN,k=(d-c)/e;if(0!==k)if(1==k)g=b[c],h=b[c+1];else if(2==k)g=.5*b[c]+.5*b[c+e],h=.5*b[c+1]+.5*b[c+e+1];else{var h=b[c],k=b[c+1],n=0,g=[0],p;for(p=c+e;p<d;p+=e){var q=b[p],r=b[p+1],n=n+Math.sqrt((q-h)*(q-h)+(r-k)*(r-k));g.push(n);h=q;k=r}d=.5*n;for(var s,h=hb,k=0,n=g.length;k<n;)p=k+n>>1,q=h(d,g[p]),0<q?k=p+1:(n=p,s=!q);s=s?k:~k;0>s?(d=(d-g[-s-2])/(g[-s-1]-g[-s-2]),c+=(-s-2)*e,g=$b(b[c],b[c+e],d),h=$b(b[c+1],b[c+e+1],d)):(g=b[c+s*e],h=b[c+s*e+1])}return null!=f? -(f[0]=g,f[1]=h,f):[g,h]}function on(b,c,d,e,f,g){if(d==c)return null;if(f<b[c+e-1])return g?(d=b.slice(c,c+e),d[e-1]=f,d):null;if(b[d-1]<f)return g?(d=b.slice(d-e,d),d[e-1]=f,d):null;if(f==b[c+e-1])return b.slice(c,c+e);c/=e;for(d/=e;c<d;)g=c+d>>1,f<b[(g+1)*e-1]?d=g:c=g+1;d=b[c*e-1];if(f==d)return b.slice((c-1)*e,(c-1)*e+e);g=(f-d)/(b[(c+1)*e-1]-d);d=[];var h;for(h=0;h<e-1;++h)d.push($b(b[(c-1)*e+h],b[c*e+h],g));d.push(f);return d} -function pn(b,c,d,e,f,g){var h=0;if(g)return on(b,h,c[c.length-1],d,e,f);if(e<b[d-1])return f?(b=b.slice(0,d),b[d-1]=e,b):null;if(b[b.length-1]<e)return f?(b=b.slice(b.length-d),b[d-1]=e,b):null;f=0;for(g=c.length;f<g;++f){var k=c[f];if(h!=k){if(e<b[h+d-1])break;if(e<=b[k-1])return on(b,h,k,d,e,!1);h=k}}return null};function M(b,c){Ok.call(this);this.b=null;this.p=this.q=this.n=-1;this.U(b,c)}u(M,Ok);l=M.prototype;l.Sg=function(b){null===this.k?this.k=b.slice():db(this.k,b);this.l()};l.clone=function(){var b=new M(null);qn(b,this.a,this.k.slice());return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.p!=this.c&&(this.q=Math.sqrt(Xk(this.k,0,this.k.length,this.s,0)),this.p=this.c);return Zk(this.k,0,this.k.length,this.s,this.q,!1,b,c,d,e)}; -l.lj=function(b,c){return"XYM"!=this.a&&"XYZM"!=this.a?null:on(this.k,0,this.k.length,this.s,b,m(c)?c:!1)};l.K=function(){return dl(this.k,0,this.k.length,this.s)};l.mj=function(){var b=this.k,c=this.s,d=b[0],e=b[1],f=0,g;for(g=0+c;g<this.k.length;g+=c)var h=b[g],k=b[g+1],f=f+Math.sqrt((h-d)*(h-d)+(k-e)*(k-e)),d=h,e=k;return f};function Cm(b){b.n!=b.c&&(b.b=nn(b.k,0,b.k.length,b.s,b.b),b.n=b.c);return b.b} -l.mc=function(b){var c=[];c.length=fl(this.k,0,this.k.length,this.s,b,c,0);b=new M(null);qn(b,"XY",c);return b};l.H=function(){return"LineString"};l.ha=function(b){return rl(this.k,0,this.k.length,this.s,b)};l.U=function(b,c){null===b?qn(this,"XY",null):(Rk(this,c,b,1),null===this.k&&(this.k=[]),this.k.length=bl(this.k,0,b,this.s),this.l())};function qn(b,c,d){Qk(b,c,d);b.l()};function rn(b,c){Ok.call(this);this.b=[];this.n=this.p=-1;this.U(b,c)}u(rn,Ok);l=rn.prototype;l.Tg=function(b){null===this.k?this.k=b.k.slice():db(this.k,b.k.slice());this.b.push(this.k.length);this.l()};l.clone=function(){var b=new rn(null);sn(b,this.a,this.k.slice(),this.b.slice());return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;this.n!=this.c&&(this.p=Math.sqrt(Yk(this.k,0,this.b,this.s,0)),this.n=this.c);return $k(this.k,0,this.b,this.s,this.p,!1,b,c,d,e)}; -l.oj=function(b,c,d){return"XYM"!=this.a&&"XYZM"!=this.a||0===this.k.length?null:pn(this.k,this.b,this.s,b,m(c)?c:!1,m(d)?d:!1)};l.K=function(){return el(this.k,0,this.b,this.s)};l.th=function(b){if(0>b||this.b.length<=b)return null;var c=new M(null);qn(c,this.a,this.k.slice(0===b?0:this.b[b-1],this.b[b]));return c};l.Ec=function(){var b=this.k,c=this.b,d=this.a,e=[],f=0,g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],n=new M(null);qn(n,d,b.slice(f,k));e.push(n);f=k}return e}; -function Dm(b){var c=[],d=b.k,e=0,f=b.b;b=b.s;var g,h;g=0;for(h=f.length;g<h;++g){var k=f[g],e=nn(d,e,k,b);db(c,e);e=k}return c}l.mc=function(b){var c=[],d=[],e=this.k,f=this.b,g=this.s,h=0,k=0,n,p;n=0;for(p=f.length;n<p;++n){var q=f[n],k=fl(e,h,q,g,b,c,k);d.push(k);h=q}c.length=k;b=new rn(null);sn(b,"XY",c,d);return b};l.H=function(){return"MultiLineString"};l.ha=function(b){a:{var c=this.k,d=this.b,e=this.s,f=0,g,h;g=0;for(h=d.length;g<h;++g){if(rl(c,f,d[g],e,b)){b=!0;break a}f=d[g]}b=!1}return b}; -l.U=function(b,c){if(null===b)sn(this,"XY",null,this.b);else{Rk(this,c,b,2);null===this.k&&(this.k=[]);var d=cl(this.k,0,b,this.s,this.b);this.k.length=0===d.length?0:d[d.length-1];this.l()}};function sn(b,c,d,e){Qk(b,c,d);b.b=e;b.l()}function tn(b,c){var d="XY",e=[],f=[],g,h;g=0;for(h=c.length;g<h;++g){var k=c[g];0===g&&(d=k.a);db(e,k.k);f.push(e.length)}sn(b,d,e,f)};function un(b,c){Ok.call(this);this.U(b,c)}u(un,Ok);l=un.prototype;l.Vg=function(b){null===this.k?this.k=b.k.slice():db(this.k,b.k);this.l()};l.clone=function(){var b=new un(null);Qk(b,this.a,this.k.slice());b.l();return b};l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;var f=this.k,g=this.s,h,k,n;h=0;for(k=f.length;h<k;h+=g)if(n=Vk(b,c,f[h],f[h+1]),n<e){e=n;for(n=0;n<g;++n)d[n]=f[h+n];d.length=g}return e};l.K=function(){return dl(this.k,0,this.k.length,this.s)}; -l.Ch=function(b){var c=null===this.k?0:this.k.length/this.s;if(0>b||c<=b)return null;c=new jl(null);kl(c,this.a,this.k.slice(b*this.s,(b+1)*this.s));return c};l.xd=function(){var b=this.k,c=this.a,d=this.s,e=[],f,g;f=0;for(g=b.length;f<g;f+=d){var h=new jl(null);kl(h,c,b.slice(f,f+d));e.push(h)}return e};l.H=function(){return"MultiPoint"};l.ha=function(b){var c=this.k,d=this.s,e,f,g,h;e=0;for(f=c.length;e<f;e+=d)if(g=c[e],h=c[e+1],ae(b,g,h))return!0;return!1}; -l.U=function(b,c){null===b?Qk(this,"XY",null):(Rk(this,c,b,1),null===this.k&&(this.k=[]),this.k.length=bl(this.k,0,b,this.s));this.l()};function vn(b,c){Ok.call(this);this.b=[];this.p=-1;this.q=null;this.F=this.r=this.t=-1;this.n=null;this.U(b,c)}u(vn,Ok);l=vn.prototype;l.Wg=function(b){if(null===this.k)this.k=b.k.slice(),b=b.b.slice(),this.b.push();else{var c=this.k.length;db(this.k,b.k);b=b.b.slice();var d,e;d=0;for(e=b.length;d<e;++d)b[d]+=c}this.b.push(b);this.l()};l.clone=function(){var b=new vn(null);wn(b,this.a,this.k.slice(),this.b.slice());return b}; -l.Va=function(b,c,d,e){if(e<Zd(this.D(),b,c))return e;if(this.r!=this.c){var f=this.b,g=0,h=0,k,n;k=0;for(n=f.length;k<n;++k)var p=f[k],h=Yk(this.k,g,p,this.s,h),g=p[p.length-1];this.t=Math.sqrt(h);this.r=this.c}f=Em(this);g=this.b;h=this.s;k=this.t;n=0;var p=m(void 0)?void 0:[NaN,NaN],q,r;q=0;for(r=g.length;q<r;++q){var s=g[q];e=$k(f,n,s,h,k,!0,b,c,d,e,p);n=s[s.length-1]}return e}; -l.Jb=function(b,c){var d;a:{d=Em(this);var e=this.b,f=0;if(0!==e.length){var g,h;g=0;for(h=e.length;g<h;++g){var k=e[g];if(nl(d,f,k,this.s,b,c)){d=!0;break a}f=k[k.length-1]}}d=!1}return d};l.pj=function(){var b=Em(this),c=this.b,d=0,e=0,f,g;f=0;for(g=c.length;f<g;++f)var h=c[f],e=e+Tk(b,d,h,this.s),d=h[h.length-1];return e};l.K=function(){var b=this.k,c=this.b,d=this.s,e=0,f=m(void 0)?void 0:[],g=0,h,k;h=0;for(k=c.length;h<k;++h){var n=c[h];f[g++]=el(b,e,n,d,f[g]);e=n[n.length-1]}f.length=g;return f}; -function Fm(b){if(b.p!=b.c){var c=b.k,d=b.b,e=b.s,f=0,g=[],h,k,n=Vd();h=0;for(k=d.length;h<k;++h){var p=d[h],n=fe(Xd(Infinity,Infinity,-Infinity,-Infinity,void 0),c,f,p[0],e);g.push((n[0]+n[2])/2,(n[1]+n[3])/2);f=p[p.length-1]}c=Em(b);d=b.b;e=b.s;f=0;h=[];k=0;for(n=d.length;k<n;++k)p=d[k],h=pl(c,f,p,e,g,2*k,h),f=p[p.length-1];b.q=h;b.p=b.c}return b.q}l.qh=function(){var b=new un(null),c=Fm(this).slice();Qk(b,"XY",c);b.l();return b}; -function Em(b){if(b.F!=b.c){var c=b.k,d;a:{d=b.b;var e,f;e=0;for(f=d.length;e<f;++e)if(!ul(c,d[e],b.s)){d=!1;break a}d=!0}if(d)b.n=c;else{b.n=c.slice();d=c=b.n;e=b.b;f=b.s;var g=0,h,k;h=0;for(k=e.length;h<k;++h)g=vl(d,g,e[h],f);c.length=g}b.F=b.c}return b.n}l.mc=function(b){var c=[],d=[],e=this.k,f=this.b,g=this.s;b=Math.sqrt(b);var h=0,k=0,n,p;n=0;for(p=f.length;n<p;++n){var q=f[n],r=[],k=gl(e,h,q,g,b,c,k,r);d.push(r);h=q[q.length-1]}c.length=k;e=new vn(null);wn(e,"XY",c,d);return e}; -l.Dh=function(b){if(0>b||this.b.length<=b)return null;var c;0===b?c=0:(c=this.b[b-1],c=c[c.length-1]);b=this.b[b].slice();var d=b[b.length-1];if(0!==c){var e,f;e=0;for(f=b.length;e<f;++e)b[e]-=c}e=new H(null);wl(e,this.a,this.k.slice(c,d),b);return e};l.gd=function(){var b=this.a,c=this.k,d=this.b,e=[],f=0,g,h,k,n;g=0;for(h=d.length;g<h;++g){var p=d[g].slice(),q=p[p.length-1];if(0!==f)for(k=0,n=p.length;k<n;++k)p[k]-=f;k=new H(null);wl(k,b,c.slice(f,q),p);e.push(k);f=q}return e};l.H=function(){return"MultiPolygon"}; -l.ha=function(b){a:{var c=Em(this),d=this.b,e=this.s,f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];if(sl(c,f,k,e,b)){b=!0;break a}f=k[k.length-1]}b=!1}return b};l.U=function(b,c){if(null===b)wn(this,"XY",null,this.b);else{Rk(this,c,b,3);null===this.k&&(this.k=[]);var d=this.k,e=this.s,f=this.b,g=0,f=m(f)?f:[],h=0,k,n;k=0;for(n=b.length;k<n;++k)g=cl(d,g,b[k],e,f[h]),f[h++]=g,g=g[g.length-1];f.length=h;0===f.length?this.k.length=0:(d=f[f.length-1],this.k.length=0===d.length?0:d[d.length-1]);this.l()}}; -function wn(b,c,d,e){Qk(b,c,d);b.b=e;b.l()}function xn(b,c){var d="XY",e=[],f=[],g,h,k;g=0;for(h=c.length;g<h;++g){var n=c[g];0===g&&(d=n.a);var p=e.length;k=n.b;var q,r;q=0;for(r=k.length;q<r;++q)k[q]+=p;db(e,n.k);f.push(k)}wn(b,d,e,f)};function yn(b,c){return ma(b)-ma(c)}function zn(b,c){var d=.5*b/c;return d*d}function An(b,c,d,e,f,g){var h=!1,k,n;k=d.f;null===k?Bn(b,c,d,e):(n=k.ue(),2==n||3==n?(k.Ge(f,g),2==n&&Bn(b,c,d,e)):(0==n&&k.load(),k.ne(f,g),h=!0));return h}function Bn(b,c,d,e){var f=(0,d.d)(c);null!=f&&(e=f.ke(e),(0,Cn[e.H()])(b,e,d,c))} -var Cn={Point:function(b,c,d,e){var f=d.f;if(null!==f){var g=b.a(d.a,"Image");g.cb(f);g.rb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(c.K(),0,2,2,c,e))},LineString:function(b,c,d,e){var f=d.b;if(null!==f){var g=b.a(d.a,"LineString");g.wa(null,f);g.Cb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(Cm(c),0,2,2,c,e))},Polygon:function(b,c,d,e){var f=d.e,g=d.b;if(null!==f||null!==g){var h=b.a(d.a,"Polygon");h.wa(f,g);h.Sb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(yl(c),0,2, -2,c,e))},MultiPoint:function(b,c,d,e){var f=d.f;if(null!==f){var g=b.a(d.a,"Image");g.cb(f);g.qb(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),d=c.k,b.sb(d,0,d.length,c.s,c,e))},MultiLineString:function(b,c,d,e){var f=d.b;if(null!==f){var g=b.a(d.a,"LineString");g.wa(null,f);g.kc(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),d=Dm(c),b.sb(d,0,d.length,2,c,e))},MultiPolygon:function(b,c,d,e){var f=d.e,g=d.b;if(null!==g||null!==f){var h=b.a(d.a,"Polygon");h.wa(f,g);h.lc(c,e)}f=d.c;null!==f&&(b=b.a(d.a, -"Text"),b.xa(f),d=Fm(c),b.sb(d,0,d.length,2,c,e))},GeometryCollection:function(b,c,d,e){c=c.d;var f,g;f=0;for(g=c.length;f<g;++f)(0,Cn[c[f].H()])(b,c[f],d,e)},Circle:function(b,c,d,e){var f=d.e,g=d.b;if(null!==f||null!==g){var h=b.a(d.a,"Polygon");h.wa(f,g);h.jc(c,e)}f=d.c;null!==f&&(b=b.a(d.a,"Text"),b.xa(f),b.sb(c.qe(),0,2,2,c,e))}};function Dn(b,c){an.call(this,b,c);this.d=!1;this.r=-1;this.q=NaN;this.f=Vd();this.c=this.g=null;this.e=Tf()}u(Dn,an); -Dn.prototype.j=function(b,c,d){var e=cn(this,b);bn(this,"precompose",d,b,e);var f=this.c;if(null!==f&&!f.ia()){var g;ld(this.a,"render")?(this.e.canvas.width=d.canvas.width,this.e.canvas.height=d.canvas.height,g=this.e):g=d;var h=g.globalAlpha;g.globalAlpha=c.opacity;$m(f,g,b.pixelRatio,e,b.viewState.rotation,b.skippedFeatureUids);g!=d&&(bn(this,"render",g,b,e),d.drawImage(g.canvas,0,0));g.globalAlpha=h}bn(this,"postcompose",d,b,e)}; -Dn.prototype.Zb=function(b,c,d,e){if(null!==this.c){var f=this.a,g={};return ck(this.c,c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,d.call(e,b,f)})}};Dn.prototype.t=function(){Ij(this)}; -Dn.prototype.i=function(b){function c(b){var c;m(b.a)?c=b.a.call(b,k):m(d.r)&&(c=(0,d.r)(b,k));if(null!=c){if(null!=c){var e,f,g=!1;e=0;for(f=c.length;e<f;++e)g=An(q,b,c[e],zn(k,n),this.t,this)||g;b=g}else b=!1;this.d=this.d||b}}var d=this.a,e=d.a();Kj(b.attributions,e.e);Lj(b,e);if(!this.d&&(b.viewHints[0]||b.viewHints[1]))return!0;var f=b.extent,g=b.viewState,h=g.projection,k=g.resolution,n=b.pixelRatio;b=d.c;var p=d.Ab,g=d.get("renderOrder");m(g)||(g=yn);f=Yd(f,p*k);if(!this.d&&this.q==k&&this.r== -b&&this.g==g&&$d(this.f,f))return!0;tc(this.c);this.c=null;this.d=!1;var q=new Wm(.5*k/n,f,k);e.Hb(f,k,h);if(null===g)e.Db(f,k,c,this);else{var r=[];e.Db(f,k,function(b){r.push(b)},this);gb(r,g);Ta(r,c,this)}Xm(q);this.q=k;this.r=b;this.g=g;this.f=f;this.c=q;return!0};function En(b,c){Yj.call(this,0,c);this.o=Tf();this.a=this.o.canvas;this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Mf(b,this.a,0);this.c=!0;this.i=Kd()}u(En,Yj);En.prototype.Yc=function(b){return b instanceof J?new en(this,b):b instanceof K?new fn(this,b):b instanceof L?new Dn(this,b):null}; -function Fn(b,c,d){var e=b.e,f=b.o;if(ld(e,c)){var g=d.extent,h=d.pixelRatio,k=d.viewState,n=k.resolution,p=k.rotation;Vj(b.i,b.a.width/2,b.a.height/2,h/n,-h/n,-p,-k.center[0],-k.center[1]);k=new Wm(.5*n/h,g,n);g=new um(f,h,g,b.i,p);e.dispatchEvent(new Al(c,e,g,k,d,f,null));Xm(k);k.ia()||$m(k,f,h,b.i,p,{});Gm(g);b.g=k}}En.prototype.H=function(){return"canvas"}; -En.prototype.Ld=function(b){if(null===b)this.c&&(Sg(this.a,!1),this.c=!1);else{var c=this.o,d=b.size[0]*b.pixelRatio,e=b.size[1]*b.pixelRatio;this.a.width!=d||this.a.height!=e?(this.a.width=d,this.a.height=e):c.clearRect(0,0,this.a.width,this.a.height);Zj(b);Fn(this,"precompose",b);var d=b.layerStatesArray,e=b.viewState.resolution,f,g,h,k;f=0;for(g=d.length;f<g;++f)k=d[f],h=k.layer,h=dk(this,h),k.visible&&e>=k.minResolution&&e<k.maxResolution&&"ready"==k.gc&&h.i(b,k)&&h.j(b,k,c);Fn(this,"postcompose", -b);this.c||(Sg(this.a,!0),this.c=!0);ek(this,b);b.postRenderFunctions.push(ak)}};function Gn(b,c,d){Hj.call(this,b,c);this.target=d}u(Gn,Hj);Gn.prototype.e=ca;Gn.prototype.i=ca;function Hn(b,c){var d=If("DIV");d.style.position="absolute";Gn.call(this,b,c,d);this.c=null;this.d=Md()}u(Hn,Gn);Hn.prototype.Zb=function(b,c,d,e){var f=this.a;return f.a().yd(c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};Hn.prototype.e=function(){Kf(this.target);this.c=null}; -Hn.prototype.f=function(b,c){var d=b.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.c,k=this.a.a(),n=b.viewHints,p=b.extent;m(c.extent)&&(p=pe(p,c.extent));n[0]||n[1]||se(p)||(d=d.projection,n=k.g,null===n||(d=n),p=k.rc(p,f,b.pixelRatio,d),null!==p&&(d=p.state,0==d?(Wc(p,"change",this.o,!1,this),p.load()):2==d&&(h=p)));null!==h&&(d=h.D(),n=h.resolution,p=Kd(),Vj(p,b.size[0]/2,b.size[1]/2,n/f,n/f,g,(d[0]-e[0])/n,(e[1]-d[3])/n),h!=this.c&&(e=h.c(this),e.style.maxWidth="none",e.style.position= -"absolute",Kf(this.target),this.target.appendChild(e),this.c=h),Wj(p,this.d)||(Xf(this.target,p),Nd(this.d,p)),Kj(b.attributions,h.f),Lj(b,k));return!0};function In(b,c){var d=If("DIV");d.style.position="absolute";Gn.call(this,b,c,d);this.d=!0;this.n=1;this.g=0;this.c={}}u(In,Gn);In.prototype.e=function(){Kf(this.target);this.g=0}; -In.prototype.f=function(b,c){if(!c.visible)return this.d&&(Sg(this.target,!1),this.d=!1),!0;var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.a,h=g.a(),k=Gj(h,f),n=h.bd(),p=ec(k.a,e.resolution,0),q=k.ka(p),r=e.center,s;q==e.resolution?(r=Oj(r,q,b.size),s=ne(r,q,e.rotation,b.size)):s=b.extent;m(c.extent)&&(s=pe(s,c.extent));var q=Aj(k,s,q),v={};v[p]={};var y=sa(h.fe,h,v,Nj(function(b){return null!==b&&2==b.state},h,d,f)),C=g.ea();m(C)||(C=!0);var F=Vd(),G=new of(0,0,0,0),w,U,N,Y;for(N=q.a;N<= -q.d;++N)for(Y=q.b;Y<=q.c;++Y)w=h.Fb(p,N,Y,d,f),U=w.state,2==U?v[p][nf(w.a)]=w:4==U||3==U&&!C||(U=k.$c(w.a,y,null,G,F),U||(w=k.kd(w.a,G,F),null===w||y(p+1,w)));var T;if(this.g!=h.c){for(T in this.c)C=this.c[+T],Nf(C.target);this.c={};this.g=h.c}F=Va(sb(v),Number);gb(F);var y={},qa;N=0;for(Y=F.length;N<Y;++N){T=F[N];T in this.c?C=this.c[T]:(C=k.Fc(r,T),C=new Jn(k,C),y[T]=!0,this.c[T]=C);T=v[T];for(qa in T)Kn(C,T[qa],n);Ln(C)}n=Va(sb(this.c),Number);gb(n);N=Kd();qa=0;for(F=n.length;qa<F;++qa)if(T=n[qa], -C=this.c[T],T in v)if(w=C.g,Y=C.f,Vj(N,b.size[0]/2,b.size[1]/2,w/e.resolution,w/e.resolution,e.rotation,(Y[0]-r[0])/w,(r[1]-Y[1])/w),Mn(C,N),T in y){for(--T;0<=T;--T)if(T in this.c){Lf(C.target,this.c[T].target);break}0>T&&Mf(this.target,C.target,0)}else b.viewHints[0]||b.viewHints[1]||Nn(C,s,G);else Nf(C.target),delete this.c[T];c.opacity!=this.n&&(this.n=this.target.style.opacity=c.opacity);c.visible&&!this.d&&(Sg(this.target,!0),this.d=!0);Mj(b.usedTiles,h,p,q);Pj(b,h,k,d,f,s,p,g.r());Jj(b,h); -Lj(b,h);return!0};function Jn(b,c){this.target=If("DIV");this.target.style.position="absolute";this.target.style.width="100%";this.target.style.height="100%";this.d=b;this.b=c;this.f=me(yj(b,c));this.g=b.ka(c[0]);this.c={};this.a=null;this.e=Md()} -function Kn(b,c,d){var e=c.a,f=e[0],g=e[1],h=e[2],e=nf(e);if(!(e in b.c)){var f=b.d.sa(f),k=c.Na(b),n=k.style;n.maxWidth="none";var p,q;0<d?(p=If("DIV"),q=p.style,q.overflow="hidden",q.width=f+"px",q.height=f+"px",n.position="absolute",n.left=-d+"px",n.top=-d+"px",n.width=f+2*d+"px",n.height=f+2*d+"px",p.appendChild(k)):(n.width=f+"px",n.height=f+"px",p=k,q=n);q.position="absolute";q.left=(g-b.b[1])*f+"px";q.top=(b.b[2]-h)*f+"px";null===b.a&&(b.a=document.createDocumentFragment());b.a.appendChild(p); -b.c[e]=c}}function Ln(b){null!==b.a&&(b.target.appendChild(b.a),b.a=null)}function Nn(b,c,d){var e=zj(b.d,c,b.b[0],d);c=[];for(var f in b.c)d=b.c[f],e.contains(d.a)||c.push(d);var g,e=0;for(g=c.length;e<g;++e)d=c[e],f=nf(d.a),Nf(d.Na(b)),delete b.c[f]}function Mn(b,c){Wj(c,b.e)||(Xf(b.target,c),Nd(b.e,c))};function On(b,c){this.g=Tf();var d=this.g.canvas;d.style.maxWidth="none";d.style.position="absolute";Gn.call(this,b,c,d);this.d=!1;this.q=-1;this.p=NaN;this.n=Vd();this.c=this.j=null;this.r=Kd()}u(On,Gn); -On.prototype.i=function(b,c){var d=b.viewState,e=d.rotation,f=b.pixelRatio,d=Vj(this.r,f*b.size[0]/2,f*b.size[1]/2,f/d.resolution,-f/d.resolution,-d.rotation,-d.center[0],-d.center[1]),g=this.g;g.canvas.width=b.size[0];g.canvas.height=b.size[1];Pn(this,"precompose",b,d);var h=this.c;null===h||h.ia()||(g.globalAlpha=c.opacity,$m(h,g,f,d,e,b.skippedFeatureUids),Pn(this,"render",b,d));Pn(this,"postcompose",b,d)}; -function Pn(b,c,d,e){var f=b.g;b=b.a;ld(b,c)&&(e=new um(f,d.pixelRatio,d.extent,e,d.viewState.rotation),b.dispatchEvent(new Al(c,b,e,null,d,f,null)),Gm(e))}On.prototype.Zb=function(b,c,d,e){if(null!==this.c){var f=this.a,g={};return ck(this.c,c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,d.call(e,b,f)})}};On.prototype.t=function(){Ij(this)}; -On.prototype.f=function(b){function c(b){var c;m(b.a)?c=b.a.call(b,k):m(d.r)&&(c=(0,d.r)(b,k));if(null!=c){if(null!=c){var e,f,g=!1;e=0;for(f=c.length;e<f;++e)g=An(q,b,c[e],zn(k,n),this.t,this)||g;b=g}else b=!1;this.d=this.d||b}}var d=this.a,e=d.a();Kj(b.attributions,e.e);Lj(b,e);if(!this.d&&(b.viewHints[0]||b.viewHints[1]))return!0;var f=b.extent,g=b.viewState,h=g.projection,k=g.resolution,n=b.pixelRatio;b=d.c;var p=d.Ab,g=d.get("renderOrder");m(g)||(g=yn);f=Yd(f,p*k);if(!this.d&&this.p==k&&this.q== -b&&this.j==g&&$d(this.n,f))return!0;tc(this.c);this.c=null;this.d=!1;var q=new Wm(.5*k/n,f,k);e.Hb(f,k,h);if(null===g)e.Db(f,k,c,this);else{var r=[];e.Db(f,k,function(b){r.push(b)},this);gb(r,g);Ta(r,c,this)}Xm(q);this.p=k;this.q=b;this.j=g;this.n=f;this.c=q;return!0};function Qn(b,c){Yj.call(this,0,c);this.c=null;this.c=Tf();var d=this.c.canvas;d.style.position="absolute";d.style.width="100%";d.style.height="100%";d.className="ol-unselectable";Mf(b,d,0);this.i=Kd();this.a=If("DIV");this.a.className="ol-unselectable";d=this.a.style;d.position="absolute";d.width="100%";d.height="100%";z(this.a,"touchstart",wc);Mf(b,this.a,0);this.o=!0}u(Qn,Yj);Qn.prototype.M=function(){Nf(this.a);Qn.S.M.call(this)}; -Qn.prototype.Yc=function(b){if(b instanceof J)b=new Hn(this,b);else if(b instanceof K)b=new In(this,b);else if(b instanceof L)b=new On(this,b);else return null;return b}; -function Rn(b,c,d){var e=b.e;if(ld(e,c)){var f=d.extent,g=d.pixelRatio,h=d.viewState,k=h.resolution,n=h.rotation,p=b.c,q=p.canvas;Vj(b.i,q.width/2,q.height/2,g/h.resolution,-g/h.resolution,-h.rotation,-h.center[0],-h.center[1]);h=new um(p,g,f,b.i,n);f=new Wm(.5*k/g,f,k);e.dispatchEvent(new Al(c,e,h,f,d,p,null));Xm(f);f.ia()||$m(f,p,g,b.i,n,{});Gm(h);b.g=f}}Qn.prototype.H=function(){return"dom"}; -Qn.prototype.Ld=function(b){if(null===b)this.o&&(Sg(this.a,!1),this.o=!1);else{var c;c=function(b,c){Mf(this.a,b,c)};var d=this.e;if(ld(d,"precompose")||ld(d,"postcompose"))d=this.c.canvas,d.width=b.size[0],d.height=b.size[1];Rn(this,"precompose",b);var d=b.layerStatesArray,e,f,g,h;e=0;for(f=d.length;e<f;++e)h=d[e],g=h.layer,g=dk(this,g),c.call(this,g.target,e),"ready"==h.gc?g.f(b,h)&&g.i(b,h):g.e();c=b.layerStates;for(var k in this.b)k in c||(g=this.b[k],Nf(g.target));this.o||(Sg(this.a,!0),this.o= -!0);Zj(b);ek(this,b);b.postRenderFunctions.push(ak);Rn(this,"postcompose",b)}};function Sn(b){this.a=b}function Tn(b){this.a=b}u(Tn,Sn);Tn.prototype.H=function(){return 35632};function Un(b){this.a=b}u(Un,Sn);Un.prototype.H=function(){return 35633};function Vn(){this.a="precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}"}u(Vn,Tn);da(Vn); -function Wn(){this.a="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}u(Wn,Un);da(Wn); -function Xn(b,c){this.n=b.getUniformLocation(c,"k");this.o=b.getUniformLocation(c,"j");this.i=b.getUniformLocation(c,"i");this.f=b.getUniformLocation(c,"l");this.g=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.c=b.getAttribLocation(c,"f");this.d=b.getAttribLocation(c,"c");this.b=b.getAttribLocation(c,"g");this.e=b.getAttribLocation(c,"d")};function Yn(){this.a="precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}"}u(Yn,Tn);da(Yn); -function Zn(){this.a="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}u(Zn,Un);da(Zn); -function $n(b,c){this.o=b.getUniformLocation(c,"j");this.i=b.getUniformLocation(c,"i");this.f=b.getUniformLocation(c,"k");this.g=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.c=b.getAttribLocation(c,"f");this.d=b.getAttribLocation(c,"c");this.b=b.getAttribLocation(c,"g");this.e=b.getAttribLocation(c,"d")};function ao(b){this.a=m(b)?b:[];this.c=m(void 0)?void 0:35044};function bo(b,c){this.n=this.i=void 0;this.Sa=new yg;this.e=ke(c);this.o=[];this.q=void 0;this.b=[];this.t=this.r=void 0;this.c=[];this.p=this.j=this.d=null;this.F=void 0;this.ga=Md();this.Ra=Md();this.Q=this.J=void 0;this.ea=Md();this.ca=this.ba=this.V=void 0;this.f=[];this.a=[];this.g=null;this.la=void 0}function co(b,c){var d=b.g,e=b.d,f=b.f,g=c.a;return function(){if(!g.isContextLost()){var b,k;b=0;for(k=f.length;b<k;++b)g.deleteTexture(f[b])}eo(c,d);eo(c,e)}} -function fo(b,c,d,e){var f=b.i,g=b.n,h=b.q,k=b.r,n=b.t,p=b.F,q=b.J,r=b.Q,s=b.V?1:0,v=b.ba,y=b.ca,C=b.la,F=Math.cos(v),v=Math.sin(v),G=b.c.length,w=b.a.length,U,N,Y,T,qa,vb;for(U=0;U<d;U+=e)qa=c[U]-b.e[0],vb=c[U+1]-b.e[1],N=w/8,Y=-y*f,T=-y*(h-g),b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=q/n,b.a[w++]=(r+h)/k,b.a[w++]=p,b.a[w++]=s,Y=y*(C-f),T=-y*(h-g),b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=(q+C)/n,b.a[w++]=(r+h)/k,b.a[w++]=p,b.a[w++]=s,Y=y*(C-f),T= -y*g,b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=(q+C)/n,b.a[w++]=r/k,b.a[w++]=p,b.a[w++]=s,Y=-y*f,T=y*g,b.a[w++]=qa,b.a[w++]=vb,b.a[w++]=Y*F-T*v,b.a[w++]=Y*v+T*F,b.a[w++]=q/n,b.a[w++]=r/k,b.a[w++]=p,b.a[w++]=s,b.c[G++]=N,b.c[G++]=N+1,b.c[G++]=N+2,b.c[G++]=N,b.c[G++]=N+2,b.c[G++]=N+3}l=bo.prototype;l.qb=function(b){var c=b.k;fo(this,c,c.length,b.s)};l.rb=function(b){var c=b.k;fo(this,c,c.length,b.s)}; -l.Kb=function(b){var c=b.a;this.o.push(this.c.length);this.g=new ao(this.a);go(b,34962,this.g);this.d=new ao(this.c);go(b,34963,this.d);var d,e,f={},g,h=this.b.length;for(g=0;g<h;++g)d=this.b[g],e=ma(d).toString(),e in f?b=x(f,e):(b=c.createTexture(),c.bindTexture(3553,b),c.texParameteri(3553,10242,33071),c.texParameteri(3553,10243,33071),c.texParameteri(3553,10241,9729),c.texParameteri(3553,10240,9729),c.texImage2D(3553,0,6408,6408,5121,d),f[e]=b),this.f[g]=b;this.q=this.n=this.i=void 0;this.b=null; -this.t=this.r=void 0;this.c=null;this.ca=this.ba=this.V=this.Q=this.J=this.F=void 0;this.a=null;this.la=void 0}; -l.Hc=function(b,c,d,e,f,g,h,k,n,p,q){g=b.a;go(b,34962,this.g);go(b,34963,this.d);var r=k||1!=n||p||1!=q,s,v;r?(s=Vn.Ja(),v=Wn.Ja()):(s=Yn.Ja(),v=Zn.Ja());v=ho(b,s,v);r?null===this.j?this.j=s=new Xn(g,v):s=this.j:null===this.p?this.p=s=new $n(g,v):s=this.p;b.Gd(v);g.enableVertexAttribArray(s.d);g.vertexAttribPointer(s.d,2,5126,!1,32,0);g.enableVertexAttribArray(s.a);g.vertexAttribPointer(s.a,2,5126,!1,32,8);g.enableVertexAttribArray(s.e);g.vertexAttribPointer(s.e,2,5126,!1,32,16);g.enableVertexAttribArray(s.c); -g.vertexAttribPointer(s.c,1,5126,!1,32,24);g.enableVertexAttribArray(s.b);g.vertexAttribPointer(s.b,1,5126,!1,32,28);v=this.ea;Vj(v,0,0,2/(d*f[0]),2/(d*f[1]),-e,-(c[0]-this.e[0]),-(c[1]-this.e[1]));c=this.Ra;d=2/f[0];f=2/f[1];Od(c);c[0]=d;c[5]=f;c[10]=1;c[15]=1;f=this.ga;Od(f);0!==e&&Sd(f,-e);g.uniformMatrix4fv(s.g,!1,v);g.uniformMatrix4fv(s.i,!1,c);g.uniformMatrix4fv(s.o,!1,f);g.uniform1f(s.f,h);r&&g.uniformMatrix4fv(s.n,!1,zg(this.Sa,k,n,p,q));e=0;h=this.f.length;for(k=0;e<h;++e)g.bindTexture(3553, -this.f[e]),n=this.o[e],g.drawElements(4,n-k,b.e?5125:5123,k*(b.e?4:2)),k=n;g.disableVertexAttribArray(s.d);g.disableVertexAttribArray(s.a);g.disableVertexAttribArray(s.e);g.disableVertexAttribArray(s.c);g.disableVertexAttribArray(s.b)}; -l.cb=function(b){var c=b.tb(),d=b.yb(1),e=b.cd(),f=b.p,g=b.zb(),h=b.q,k=b.i,n=b.ab();b=b.n;0===this.b.length?this.b.push(d):ma(this.b[this.b.length-1])!=ma(d)&&(this.o.push(this.c.length),this.b.push(d));this.i=c[0];this.n=c[1];this.q=n[1];this.r=e[1];this.t=e[0];this.F=f;this.J=g[0];this.Q=g[1];this.ba=k;this.V=h;this.ca=b;this.la=n[0]};function io(b,c){this.b=c;this.d=b;this.c={}}function jo(b,c){var d=[],e;for(e in b.c)d.push(co(b.c[e],c));return hd.apply(null,d)} -function ko(b,c){for(var d in b.c)b.c[d].Kb(c)}io.prototype.a=function(b,c){var d=this.c[c];m(d)||(d=new lo[c](this.d,this.b),this.c[c]=d);return d};io.prototype.ia=function(){return xb(this.c)};function mo(b,c,d,e,f,g,h,k,n,p,q,r,s){var v,y,C;v=0;for(y=Hm.length;v<y&&(C=b.c[Hm[v]],!m(C)||!(C=C.Hc(c,d,e,f,g,h,k,n,p,q,r,s)));++v);}var lo={Image:bo};function no(b,c,d,e,f,g,h){this.b=b;this.e=c;this.a=g;this.f=h;this.i=f;this.o=e;this.g=d;this.d=null;this.c={}}l=no.prototype;l.ic=function(b,c){var d=b.toString(),e=this.c[d];m(e)?e.push(c):this.c[d]=[c]};l.jc=function(){};l.ee=function(b,c){var d=(0,c.d)(b);if(null!=d&&qe(this.a,d.D())){var e=c.a;m(e)||(e=0);this.ic(e,function(b){b.wa(c.e,c.b);b.cb(c.f);b.xa(c.c);var e=oo[d.H()];e&&e.call(b,d,null)})}}; -l.Zc=function(b,c){var d=b.d,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e],h=oo[g.H()];h&&h.call(this,g,c)}};l.rb=function(b,c){var d=this.b,e=(new io(1,this.a)).a(0,"Image");e.cb(this.d);e.rb(b,c);e.Kb(d);e.Hc(this.b,this.e,this.g,this.o,this.i,this.a,this.f,1,0,1,0,1,{});co(e,d)()};l.Cb=function(){};l.kc=function(){};l.qb=function(b,c){var d=this.b,e=(new io(1,this.a)).a(0,"Image");e.cb(this.d);e.qb(b,c);e.Kb(d);e.Hc(this.b,this.e,this.g,this.o,this.i,this.a,this.f,1,0,1,0,1,{});co(e,d)()};l.lc=function(){}; -l.Sb=function(){};l.sb=function(){};l.wa=function(){};l.cb=function(b){this.d=b};l.xa=function(){};var oo={Point:no.prototype.rb,MultiPoint:no.prototype.qb,GeometryCollection:no.prototype.Zc};function po(){this.a="precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}"}u(po,Tn);da(po);function qo(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}u(qo,Un);da(qo); -function ro(b,c){this.g=b.getUniformLocation(c,"f");this.b=b.getUniformLocation(c,"g");this.d=b.getUniformLocation(c,"e");this.f=b.getUniformLocation(c,"d");this.e=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"b");this.c=b.getAttribLocation(c,"c")};function so(){this.a="precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}"}u(so,Tn);da(so);function to(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}u(to,Un);da(to); -function uo(b,c){this.b=b.getUniformLocation(c,"f");this.d=b.getUniformLocation(c,"e");this.f=b.getUniformLocation(c,"d");this.e=b.getUniformLocation(c,"g");this.a=b.getAttribLocation(c,"b");this.c=b.getAttribLocation(c,"c")};function vo(b,c){Hj.call(this,b,c);this.J=new ao([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.e=this.Qa=null;this.f=void 0;this.q=Kd();this.F=Md();this.Q=new yg;this.i=this.g=null}u(vo,Hj); -function wo(b,c,d){var e=b.b.d;if(m(b.f)&&b.f==d)e.bindFramebuffer(36160,b.e);else{c.postRenderFunctions.push(ta(function(b,c,d){b.isContextLost()||(b.deleteFramebuffer(c),b.deleteTexture(d))},e,b.e,b.Qa));c=e.createTexture();e.bindTexture(3553,c);e.texImage2D(3553,0,6408,d,d,0,6408,5121,null);e.texParameteri(3553,10240,9729);e.texParameteri(3553,10241,9729);var f=e.createFramebuffer();e.bindFramebuffer(36160,f);e.framebufferTexture2D(36160,36064,3553,c,0);b.Qa=c;b.e=f;b.f=d}} -vo.prototype.xf=function(b,c,d){xo(this,"precompose",d,b);go(d,34962,this.J);var e=d.a,f=c.brightness||1!=c.contrast||c.hue||1!=c.saturation,g,h;f?(g=po.Ja(),h=qo.Ja()):(g=so.Ja(),h=to.Ja());g=ho(d,g,h);f?null===this.g?this.g=h=new ro(e,g):h=this.g:null===this.i?this.i=h=new uo(e,g):h=this.i;d.Gd(g)&&(e.enableVertexAttribArray(h.a),e.vertexAttribPointer(h.a,2,5126,!1,16,0),e.enableVertexAttribArray(h.c),e.vertexAttribPointer(h.c,2,5126,!1,16,8),e.uniform1i(h.e,0));e.uniformMatrix4fv(h.f,!1,this.q); -e.uniformMatrix4fv(h.d,!1,this.F);f&&e.uniformMatrix4fv(h.g,!1,zg(this.Q,c.brightness,c.contrast,c.hue,c.saturation));e.uniform1f(h.b,c.opacity);e.bindTexture(3553,this.Qa);e.drawArrays(5,0,4);xo(this,"postcompose",d,b)};function xo(b,c,d,e){b=b.a;if(ld(b,c)){var f=e.viewState;b.dispatchEvent(new Al(c,b,new no(d,f.center,f.resolution,f.rotation,e.size,e.extent,e.pixelRatio),null,e,null,d))}}vo.prototype.n=function(){this.e=this.Qa=null;this.f=void 0};function yo(b,c){vo.call(this,b,c);this.c=null}u(yo,vo);function zo(b,c){var d=c.c(),e=b.b.d,f=e.createTexture();e.bindTexture(3553,f);e.texImage2D(3553,0,6408,6408,5121,d);e.texParameteri(3553,10242,33071);e.texParameteri(3553,10243,33071);e.texParameteri(3553,10241,9729);e.texParameteri(3553,10240,9729);return f}yo.prototype.Zb=function(b,c,d,e){var f=this.a;return f.a().yd(c.viewState.resolution,c.viewState.rotation,b,c.skippedFeatureUids,function(b){return d.call(e,b,f)})}; -yo.prototype.re=function(b,c){var d=this.b.d,e=b.viewState,f=e.center,g=e.resolution,h=e.rotation,k=this.c,n=this.Qa,p=this.a.a(),q=b.viewHints,r=b.extent;m(c.extent)&&(r=pe(r,c.extent));q[0]||q[1]||se(r)||(e=e.projection,q=p.g,null===q||(e=q),r=p.rc(r,g,b.pixelRatio,e),null!==r&&(e=r.state,0==e?(Wc(r,"change",this.o,!1,this),r.load()):2==e&&(k=r,n=zo(this,r),null===this.Qa||b.postRenderFunctions.push(ta(function(b,c){b.isContextLost()||b.deleteTexture(c)},d,this.Qa)))));null!==k&&(d=this.b.f.g,Ao(this, -d.width,d.height,f,g,h,k.D()),f=this.q,Od(f),Rd(f,1,-1),Qd(f,0,-1),this.c=k,this.Qa=n,Kj(b.attributions,k.f),Lj(b,p));return!0};function Ao(b,c,d,e,f,g,h){c*=f;d*=f;b=b.F;Od(b);Rd(b,2/c,2/d);Sd(b,-g);Qd(b,h[0]-e[0],h[1]-e[1]);Rd(b,(h[2]-h[0])/2,(h[3]-h[1])/2);Qd(b,1,1)};function Bo(){this.a="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}u(Bo,Tn);da(Bo);function Co(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}"}u(Co,Un);da(Co);function Do(b,c){this.b=b.getUniformLocation(c,"e");this.d=b.getUniformLocation(c,"d");this.a=b.getAttribLocation(c,"b");this.c=b.getAttribLocation(c,"c")};function Eo(b,c){vo.call(this,b,c);this.t=Bo.Ja();this.V=Co.Ja();this.c=null;this.r=new ao([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.p=this.d=null;this.j=-1}u(Eo,vo);Eo.prototype.M=function(){eo(this.b.f,this.r);Eo.S.M.call(this)};Eo.prototype.n=function(){Eo.S.n.call(this);this.c=null}; -Eo.prototype.re=function(b,c,d){var e=this.b,f=d.a,g=b.viewState,h=g.projection,k=this.a,n=k.a(),p=Gj(n,h),q=ec(p.a,g.resolution,0),r=p.ka(q),s=n.Gc(q,b.pixelRatio,h),v=s/p.sa(q),y=r/v,C=n.bd(),F=g.center,G;r==g.resolution?(F=Oj(F,r,b.size),G=ne(F,r,g.rotation,b.size)):G=b.extent;r=Aj(p,G,r);if(null!==this.d&&sf(this.d,r)&&this.j==n.c)y=this.p;else{var w=[r.d-r.a+1,r.c-r.b+1],w=Math.max(w[0]*s,w[1]*s),U=Math.pow(2,Math.ceil(Math.log(w)/Math.LN2)),w=y*U,N=p.Lb(q),Y=N[0]+r.a*s*y,y=N[1]+r.b*s*y,y=[Y, -y,Y+w,y+w];wo(this,b,U);f.viewport(0,0,U,U);f.clearColor(0,0,0,0);f.clear(16384);f.disable(3042);U=ho(d,this.t,this.V);d.Gd(U);null===this.c&&(this.c=new Do(f,U));go(d,34962,this.r);f.enableVertexAttribArray(this.c.a);f.vertexAttribPointer(this.c.a,2,5126,!1,16,0);f.enableVertexAttribArray(this.c.c);f.vertexAttribPointer(this.c.c,2,5126,!1,16,8);f.uniform1i(this.c.b,0);d={};d[q]={};var T=sa(n.fe,n,d,Nj(function(b){return null!==b&&2==b.state&&Fo(e.c,b.mb())},n,v,h)),qa=k.ea();m(qa)||(qa=!0);var U= -!0,Y=Vd(),vb=new of(0,0,0,0),Ka,ac,Sb;for(ac=r.a;ac<=r.d;++ac)for(Sb=r.b;Sb<=r.c;++Sb){N=n.Fb(q,ac,Sb,v,h);if(m(c.extent)&&(Ka=yj(p,N.a,Y),!qe(Ka,c.extent)))continue;Ka=N.state;if(2==Ka){if(Fo(e.c,N.mb())){d[q][nf(N.a)]=N;continue}}else if(4==Ka||3==Ka&&!qa)continue;U=!1;Ka=p.$c(N.a,T,null,vb,Y);Ka||(N=p.kd(N.a,vb,Y),null===N||T(q+1,N))}c=Va(sb(d),Number);gb(c);for(var T=new Float32Array(4),La,Pa,Ud,qa=0,vb=c.length;qa<vb;++qa)for(La in Pa=d[c[qa]],Pa)N=Pa[La],Ka=yj(p,N.a,Y),ac=2*(Ka[2]-Ka[0])/w, -Sb=2*(Ka[3]-Ka[1])/w,Ud=2*(Ka[0]-y[0])/w-1,Ka=2*(Ka[1]-y[1])/w-1,Jd(T,ac,Sb,Ud,Ka),f.uniform4fv(this.c.d,T),Go(e,N,s,C*v),f.drawArrays(5,0,4);U?(this.d=r,this.p=y,this.j=n.c):(this.p=this.d=null,this.j=-1,b.animate=!0)}Mj(b.usedTiles,n,q,r);var qd=e.i;Pj(b,n,p,v,h,G,q,k.r(),function(b){var c;(c=2!=b.state||Fo(e.c,b.mb()))||(c=b.mb()in qd.b);c||Qj(qd,[b,Cj(p,b.a),p.ka(b.a[0]),s,C*v])},this);Jj(b,n);Lj(b,n);f=this.q;Od(f);Qd(f,(F[0]-y[0])/(y[2]-y[0]),(F[1]-y[1])/(y[3]-y[1]));0!==g.rotation&&Sd(f,g.rotation); -Rd(f,b.size[0]*g.resolution/(y[2]-y[0]),b.size[1]*g.resolution/(y[3]-y[1]));Qd(f,-.5,-.5);return!0};function Ho(b,c){vo.call(this,b,c);this.d=!1;this.t=-1;this.r=NaN;this.j=Vd();this.c=this.p=null}u(Ho,vo);l=Ho.prototype;l.xf=function(b,c,d){var e=b.viewState,f=this.c;null===f||f.ia()||mo(f,d,e.center,e.resolution,e.rotation,b.size,b.pixelRatio,c.opacity,c.brightness,c.contrast,c.hue,c.saturation,b.skippedFeatureUids)};l.M=function(){var b=this.c;null!==b&&(jo(b,this.b.f)(),this.c=null);Ho.S.M.call(this)};l.Zb=function(){};l.vj=function(){Ij(this)}; -l.re=function(b,c,d){function e(b){var c;m(b.a)?c=b.a.call(b,n):m(f.r)&&(c=(0,f.r)(b,n));if(null!=c){if(null!=c){var d,e,g=!1;d=0;for(e=c.length;d<e;++d)g=An(s,b,c[d],zn(n,p),this.vj,this)||g;b=g}else b=!1;this.d=this.d||b}}var f=this.a;c=f.a();Kj(b.attributions,c.e);Lj(b,c);if(!this.d&&(b.viewHints[0]||b.viewHints[1]))return!0;var g=b.extent,h=b.viewState,k=h.projection,n=h.resolution,p=b.pixelRatio,h=f.c,q=f.Ab,r=f.get("renderOrder");m(r)||(r=yn);g=Yd(g,q*n);if(!this.d&&this.r==n&&this.t==h&&this.p== -r&&$d(this.j,g))return!0;null===this.c||b.postRenderFunctions.push(jo(this.c,d));this.d=!1;var s=new io(.5*n/p,g);c.Hb(g,n,k);if(null===r)c.Db(g,n,e,this);else{var v=[];c.Db(g,n,function(b){v.push(b)},this);gb(v,r);Ta(v,e,this)}ko(s,d);this.r=n;this.t=h;this.p=r;this.j=g;this.c=s;return!0};function Io(){this.b=0;this.d={};this.c=this.a=null}l=Io.prototype;l.clear=function(){this.b=0;this.d={};this.c=this.a=null};function Fo(b,c){return b.d.hasOwnProperty(c)}l.forEach=function(b,c){for(var d=this.a;null!==d;)b.call(c,d.dc,d.ud,this),d=d.Za};l.get=function(b){b=this.d[b];if(b===this.c)return b.dc;b===this.a?(this.a=this.a.Za,this.a.Mb=null):(b.Za.Mb=b.Mb,b.Mb.Za=b.Za);b.Za=null;b.Mb=this.c;this.c=this.c.Za=b;return b.dc};l.Ub=function(){return this.b}; -l.I=function(){var b=Array(this.b),c=0,d;for(d=this.c;null!==d;d=d.Mb)b[c++]=d.ud;return b};l.kb=function(){var b=Array(this.b),c=0,d;for(d=this.c;null!==d;d=d.Mb)b[c++]=d.dc;return b};l.pop=function(){var b=this.a;delete this.d[b.ud];null!==b.Za&&(b.Za.Mb=null);this.a=b.Za;null===this.a&&(this.c=null);--this.b;return b.dc};l.set=function(b,c){var d={ud:b,Za:null,Mb:this.c,dc:c};null===this.c?this.a=d:this.c.Za=d;this.c=d;this.d[b]=d;++this.b};function Jo(b,c){this.g=b;this.a=c;this.c={};this.d={};this.b={};this.f=null;(this.e=Za(wa,"OES_element_index_uint"))&&c.getExtension("OES_element_index_uint");z(this.g,"webglcontextlost",this.lk,!1,this);z(this.g,"webglcontextrestored",this.mk,!1,this)} -function go(b,c,d){var e=b.a,f=d.a,g=ma(d);if(g in b.c)e.bindBuffer(c,b.c[g].buffer);else{var h=e.createBuffer();e.bindBuffer(c,h);var k;34962==c?k=new Float32Array(f):34963==c&&(k=b.e?new Uint32Array(f):new Uint16Array(f));e.bufferData(c,k,d.c);b.c[g]={b:d,buffer:h}}}function eo(b,c){var d=b.a,e=ma(c),f=b.c[e];d.isContextLost()||d.deleteBuffer(f.buffer);delete b.c[e]}l=Jo.prototype; -l.M=function(){var b=this.a;b.isContextLost()||(ob(this.c,function(c){b.deleteBuffer(c.buffer)}),ob(this.b,function(c){b.deleteProgram(c)}),ob(this.d,function(c){b.deleteShader(c)}))};l.kk=function(){return this.a};function Ko(b,c){var d=ma(c);if(d in b.d)return b.d[d];var e=b.a,f=e.createShader(c.H());e.shaderSource(f,c.a);e.compileShader(f);return b.d[d]=f} -function ho(b,c,d){var e=ma(c)+"/"+ma(d);if(e in b.b)return b.b[e];var f=b.a,g=f.createProgram();f.attachShader(g,Ko(b,c));f.attachShader(g,Ko(b,d));f.linkProgram(g);return b.b[e]=g}l.lk=function(){yb(this.c);yb(this.d);yb(this.b);this.f=null};l.mk=function(){};l.Gd=function(b){if(b==this.f)return!1;this.a.useProgram(b);this.f=b;return!0};function Lo(b,c){Yj.call(this,0,c);this.a=If("CANVAS");this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Mf(b,this.a,0);this.p=0;this.q=Tf();this.n=!0;this.d=Zf(this.a,{antialias:!0,depth:!1,bh:!0,preserveDrawingBuffer:!1,stencil:!0});this.f=new Jo(this.a,this.d);z(this.a,"webglcontextlost",this.tj,!1,this);z(this.a,"webglcontextrestored",this.uj,!1,this);this.c=new Io;this.j=null;this.i=new fk(sa(function(b){var c=b[1];b=b[2];var f=c[0]-this.j[0],c=c[1]-this.j[1]; -return 65536*Math.log(b)+Math.sqrt(f*f+c*c)/b},this),function(b){return b[0].mb()});this.r=sa(function(){if(!this.i.ia()){jk(this.i);var b=gk(this.i);Go(this,b[0],b[3],b[4])}},this);this.o=0;Mo(this)}u(Lo,Yj); -function Go(b,c,d,e){var f=b.d,g=c.mb();if(Fo(b.c,g))b=b.c.get(g),f.bindTexture(3553,b.Qa),9729!=b.nf&&(f.texParameteri(3553,10240,9729),b.nf=9729),9729!=b.of&&(f.texParameteri(3553,10240,9729),b.of=9729);else{var h=f.createTexture();f.bindTexture(3553,h);if(0<e){var k=b.q.canvas,n=b.q;b.p!=d?(k.width=d,k.height=d,b.p=d):n.clearRect(0,0,d,d);n.drawImage(c.Na(),e,e,d,d,0,0,d,d);f.texImage2D(3553,0,6408,6408,5121,k)}else f.texImage2D(3553,0,6408,6408,5121,c.Na());f.texParameteri(3553,10240,9729);f.texParameteri(3553, -10241,9729);f.texParameteri(3553,10242,33071);f.texParameteri(3553,10243,33071);b.c.set(g,{Qa:h,nf:9729,of:9729})}}l=Lo.prototype;l.Yc=function(b){return b instanceof J?new yo(this,b):b instanceof K?new Eo(this,b):b instanceof L?new Ho(this,b):null}; -function No(b,c,d){var e=b.e;if(ld(e,c)){var f=b.f,g=d.extent,h=d.size,k=d.viewState,n=d.pixelRatio,p=k.resolution,q=k.center,r=k.rotation,k=new no(f,q,p,r,h,g,n),g=new io(.5*p/n,g);e.dispatchEvent(new Al(c,e,k,g,d,null,f));ko(g,f);g.ia()||mo(g,f,q,p,r,h,n,1,0,1,0,1,{});jo(g,f)();c=Va(sb(k.c),Number);gb(c);d=0;for(e=c.length;d<e;++d)for(f=k.c[c[d].toString()],h=0,n=f.length;h<n;++h)f[h](k);b.g=g}} -l.M=function(){var b=this.d;b.isContextLost()||this.c.forEach(function(c){null===c||b.deleteTexture(c.Qa)});tc(this.f);Lo.S.M.call(this)};l.$g=function(b,c){for(var d=this.d,e;1024<this.c.Ub()-this.o;){e=this.c.a.dc;if(null===e)if(+this.c.a.ud==c.index)break;else--this.o;else d.deleteTexture(e.Qa);this.c.pop()}};l.H=function(){return"webgl"};l.tj=function(b){b.preventDefault();this.c.clear();this.o=0;ob(this.b,function(b){b.n()})};l.uj=function(){Mo(this);this.e.render()}; -function Mo(b){b=b.d;b.activeTexture(33984);b.blendFuncSeparate(770,771,1,771);b.disable(2884);b.disable(2929);b.disable(3089);b.disable(2960)} -l.Ld=function(b){var c=this.f,d=this.d;if(d.isContextLost())return!1;if(null===b)return this.n&&(Sg(this.a,!1),this.n=!1),!1;this.j=b.focus;this.c.set((-b.index).toString(),null);++this.o;var e=[],f=b.layerStatesArray,g=b.viewState.resolution,h,k,n,p;h=0;for(k=f.length;h<k;++h)p=f[h],p.visible&&g>=p.minResolution&&g<p.maxResolution&&"ready"==p.gc&&(n=dk(this,p.layer),n.re(b,p,c)&&e.push(p));f=b.size[0]*b.pixelRatio;g=b.size[1]*b.pixelRatio;if(this.a.width!=f||this.a.height!=g)this.a.width=f,this.a.height= -g;d.bindFramebuffer(36160,null);d.clearColor(0,0,0,0);d.clear(16384);d.enable(3042);d.viewport(0,0,this.a.width,this.a.height);No(this,"precompose",b);h=0;for(k=e.length;h<k;++h)p=e[h],n=dk(this,p.layer),n.xf(b,p,c);this.n||(Sg(this.a,!0),this.n=!0);Zj(b);1024<this.c.Ub()-this.o&&b.postRenderFunctions.push(sa(this.$g,this));this.i.ia()||(b.postRenderFunctions.push(this.r),b.animate=!0);No(this,"postcompose",b);ek(this,b);b.postRenderFunctions.push(ak)};var Oo=["canvas","webgl","dom"]; -function O(b){td.call(this);var c=Po(b);this.Yd=m(b.pixelRatio)?b.pixelRatio:ag;this.Xd=c.logos;this.q=new Vh(this.Yk,void 0,this);sc(this,this.q);this.Uc=Kd();this.Zd=Kd();this.Wd=0;this.d=null;this.Ea=Vd();this.p=this.Q=null;this.b=Ff("DIV","ol-viewport");this.b.style.position="relative";this.b.style.overflow="hidden";this.b.style.width="100%";this.b.style.height="100%";this.b.style.msTouchAction="none";gg&&(this.b.className="ol-touch");this.Da=Ff("DIV","ol-overlaycontainer");this.b.appendChild(this.Da); -this.t=Ff("DIV","ol-overlaycontainer-stopevent");z(this.t,["click","dblclick","mousedown","touchstart","MSPointerDown",mj,Ib?"DOMMouseScroll":"mousewheel"],vc);this.b.appendChild(this.t);b=new ej(this);z(b,rb(pj),this.gf,!1,this);sc(this,b);this.ga=c.keyboardEventTarget;this.r=new ji;z(this.r,"key",this.ef,!1,this);sc(this,this.r);b=new ri(this.b);z(b,"mousewheel",this.ef,!1,this);sc(this,b);this.i=c.controls;this.Vc=c.deviceOptions;this.g=c.interactions;this.n=c.overlays;this.ba=new c.$k(this.b, -this);sc(this,this.ba);this.gc=new ei;sc(this,this.gc);z(this.gc,"resize",this.j,!1,this);this.V=null;this.F=[];this.oa=[];this.Ab=new kk(sa(this.Jh,this),sa(this.ri,this));this.ca={};z(this,xd("layergroup"),this.ai,!1,this);z(this,xd("view"),this.$i,!1,this);z(this,xd("size"),this.pi,!1,this);z(this,xd("target"),this.qi,!1,this);this.G(c.xl);this.i.forEach(function(b){b.setMap(this)},this);z(this.i,"add",function(b){b.element.setMap(this)},!1,this);z(this.i,"remove",function(b){b.element.setMap(null)}, -!1,this);this.g.forEach(function(b){b.setMap(this)},this);z(this.g,"add",function(b){b.element.setMap(this)},!1,this);z(this.g,"remove",function(b){b.element.setMap(null)},!1,this);this.n.forEach(function(b){b.setMap(this)},this);z(this.n,"add",function(b){b.element.setMap(this)},!1,this);z(this.n,"remove",function(b){b.element.setMap(null)},!1,this)}u(O,td);l=O.prototype;l.Qg=function(b){this.i.push(b)};l.Rg=function(b){this.g.push(b)};l.Se=function(b){this.Eb().Yb().push(b)};l.Te=function(b){this.n.push(b)}; -l.Ua=function(b){this.render();Array.prototype.push.apply(this.F,arguments)};l.M=function(){Nf(this.b);O.S.M.call(this)};l.oe=function(b,c,d,e,f){if(null!==this.d)return b=this.Ga(b),bk(this.ba,b,this.d,c,m(d)?d:null,m(e)?e:dd,m(f)?f:null)};l.ih=function(b){return this.Ga(this.ad(b))}; -l.ad=function(b){if(m(b.offsetX)&&m(b.offsetY))return[b.offsetX,b.offsetY];if(m(b.changedTouches)){var c=b.changedTouches[0];b=Og(this.b);return[c.clientX-b.x,c.clientY-b.y]}c=this.b;b=Og(b);c=Og(c);c=new vf(b.x-c.x,b.y-c.y);return[c.x,c.y]};l.qc=function(){return this.get("target")};O.prototype.getTarget=O.prototype.qc;l=O.prototype;l.Fh=function(){var b=this.qc();return m(b)?Af(b):null};l.Ga=function(b){var c=this.d;if(null===c)return null;b=b.slice();return Xj(c.pixelToCoordinateMatrix,b,b)}; -l.hh=function(){return this.i};l.Ah=function(){return this.n};l.oh=function(){return this.g};l.Eb=function(){return this.get("layergroup")};O.prototype.getLayerGroup=O.prototype.Eb;O.prototype.ea=function(){return this.Eb().Yb()};O.prototype.f=function(b){var c=this.d;if(null===c)return null;b=b.slice(0,2);return Xj(c.coordinateToPixelMatrix,b,b)};O.prototype.e=function(){return this.get("size")};O.prototype.getSize=O.prototype.e;O.prototype.a=function(){return this.get("view")}; -O.prototype.getView=O.prototype.a;l=O.prototype;l.Lh=function(){return this.b};l.Jh=function(b,c,d,e){var f=this.d;if(!(null!==f&&c in f.wantedTiles&&f.wantedTiles[c][nf(b.a)]))return Infinity;b=d[0]-f.focus[0];d=d[1]-f.focus[1];return 65536*Math.log(e)+Math.sqrt(b*b+d*d)/e};l.ef=function(b,c){var d=new cj(c||b.type,this,b);this.gf(d)}; -l.gf=function(b){if(null!==this.d){this.V=b.coordinate;b.frameState=this.d;var c=this.g.a,d;if(!1!==this.dispatchEvent(b))for(d=c.length-1;0<=d;d--){var e=c[d];if(e.a()&&!e.handleEvent(b))break}}}; -l.ni=function(){var b=this.d,c=this.Ab;if(!c.ia()){var d=16,e=d,f=0;if(null!==b){var f=b.viewHints,g=this.Vc;f[0]&&(d=!1===g.loadTilesWhileAnimating?0:8,e=2);f[1]&&(d=!1===g.loadTilesWhileInteracting?0:8,e=2);f=qb(b.wantedTiles)}d*=f;e*=f;if(c.d<d){jk(c);d=Math.min(d-c.d,e,c.Ub());for(e=0;e<d;++e)f=gk(c)[0],Wc(f,"change",c.g,!1,c),f.load();c.d+=d}}c=this.oa;d=0;for(e=c.length;d<e;++d)c[d](this,b);c.length=0};l.pi=function(){this.render()}; -l.qi=function(){var b=this.qc(),b=m(b)?Af(b):null;qi(this.r);null===b?Nf(this.b):(b.appendChild(this.b),ki(this.r,null===this.ga?b:this.ga));this.j()};l.ri=function(){this.render()};l.si=function(){this.render()};l.$i=function(){null!==this.Q&&(Yc(this.Q),this.Q=null);var b=this.a();null!==b&&(this.Q=z(b,"propertychange",this.si,!1,this));this.render()};l.bi=function(){this.render()};l.ci=function(){this.render()}; -l.ai=function(){if(null!==this.p){for(var b=this.p.length,c=0;c<b;++c)Yc(this.p[c]);this.p=null}b=this.Eb();null!=b&&(this.p=[z(b,"propertychange",this.ci,!1,this),z(b,"change",this.bi,!1,this)]);this.render()};l.Zk=function(){var b=this.q;Wh(b);b.Ye()};l.render=function(){null!=this.q.X||this.q.start()};l.Uk=function(b){if(m(this.i.remove(b)))return b};l.Vk=function(b){var c;m(this.g.remove(b))&&(c=b);return c};l.Wk=function(b){return this.Eb().Yb().remove(b)};l.Xk=function(b){if(m(this.n.remove(b)))return b}; -l.Yk=function(b){var c,d,e,f=this.e(),g=this.a(),h=null;if(m(f)&&0<f[0]&&0<f[1]&&null!==g&&af(g)){var h=cb(g.j),k=this.Eb().Da(),n={};c=0;for(d=k.length;c<d;++c)n[ma(k[c].layer)]=k[c];e=$e(g);h={animate:!1,attributions:{},coordinateToPixelMatrix:this.Uc,extent:null,focus:null===this.V?e.center:this.V,index:this.Wd++,layerStates:n,layerStatesArray:k,logos:Cb(this.Xd),pixelRatio:this.Yd,pixelToCoordinateMatrix:this.Zd,postRenderFunctions:[],size:f,skippedFeatureUids:this.ca,tileQueue:this.Ab,time:b, -usedTiles:{},viewState:e,viewHints:h,wantedTiles:{}}}if(null!==h){b=this.F;c=f=0;for(d=b.length;c<d;++c)g=b[c],g(this,h)&&(b[f++]=g);b.length=f;h.extent=ne(e.center,e.resolution,e.rotation,h.size)}this.d=h;this.ba.Ld(h);null!==h&&(h.animate&&this.render(),Array.prototype.push.apply(this.oa,h.postRenderFunctions),0!==this.F.length||h.viewHints[0]||h.viewHints[1]||de(h.extent,this.Ea)||(this.dispatchEvent(new Zg("moveend",this,h)),c=h.extent,d=this.Ea,m(d)&&(d[0]=c[0],d[1]=c[1],d[2]=c[2],d[3]=c[3]))); -this.dispatchEvent(new Zg("postrender",this,h));$h(this.ni,this)};l.cg=function(b){this.set("layergroup",b)};O.prototype.setLayerGroup=O.prototype.cg;O.prototype.J=function(b){this.set("size",b)};O.prototype.setSize=O.prototype.J;O.prototype.qa=function(b){this.set("target",b)};O.prototype.setTarget=O.prototype.qa;O.prototype.Ta=function(b){this.set("view",b)};O.prototype.setView=O.prototype.Ta;O.prototype.eb=function(b){b=ma(b).toString();this.ca[b]=!0;this.render()}; -O.prototype.j=function(){var b=this.qc(),b=m(b)?Af(b):null;if(null===b)this.J(void 0);else{var c=zf(b),d=Hb&&b.currentStyle;d&&Rf(xf(c))&&"auto"!=d.width&&"auto"!=d.height&&!d.boxSizing?(c=Tg(b,d.width,"width","pixelWidth"),b=Tg(b,d.height,"height","pixelHeight"),b=new wf(c,b)):(d=new wf(b.offsetWidth,b.offsetHeight),c=Vg(b,"padding"),b=Yg(b),b=new wf(d.width-b.left-c.left-c.right-b.right,d.height-b.top-c.top-c.bottom-b.bottom));this.J([b.width,b.height])}}; -O.prototype.fc=function(b){b=ma(b).toString();delete this.ca[b];this.render()}; -function Po(b){var c=null;m(b.keyboardEventTarget)&&(c=ia(b.keyboardEventTarget)?document.getElementById(b.keyboardEventTarget):b.keyboardEventTarget);var d={},e={};if(!m(b.logo)||"boolean"==typeof b.logo&&b.logo)e["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszWWMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvYasvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvXH1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1VkbMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLPVcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqTacrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaarldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+HizeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDnBAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSFhYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJREFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxCBrb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7ahgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCnB3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDgq82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC"]="http://openlayers.org/"; -else{var f=b.logo;ia(f)?e[f]="":la(f)&&(e[f.src]=f.href)}f=b.layers instanceof I?b.layers:new I({layers:b.layers});d.layergroup=f;d.target=b.target;d.view=m(b.view)?b.view:new A;var f=Yj,g;m(b.renderer)?ga(b.renderer)?g=b.renderer:ia(b.renderer)&&(g=[b.renderer]):g=Oo;var h,k;h=0;for(k=g.length;h<k;++h){var n=g[h];if("canvas"==n){if(dg){f=En;break}}else if("dom"==n){f=Qn;break}else if("webgl"==n&&$f){f=Lo;break}}var p;m(b.controls)?p=ga(b.controls)?new B(cb(b.controls)):b.controls:p=gh();g=m(b.deviceOptions)? -b.deviceOptions:{};var q;m(b.interactions)?q=ga(b.interactions)?new B(cb(b.interactions)):b.interactions:q=mm();b=m(b.overlays)?ga(b.overlays)?new B(cb(b.overlays)):b.overlays:new B;return{controls:p,deviceOptions:g,interactions:q,keyboardEventTarget:c,logos:e,overlays:b,$k:f,xl:d}}tm();function P(b){td.call(this);this.q=m(b.insertFirst)?b.insertFirst:!0;this.r=m(b.stopEvent)?b.stopEvent:!0;this.aa=If("DIV");this.aa.style.position="absolute";this.a={Wc:"",vd:"",Md:"",Nd:"",visible:!0};this.b=null;z(this,xd("element"),this.Uh,!1,this);z(this,xd("map"),this.hi,!1,this);z(this,xd("offset"),this.ji,!1,this);z(this,xd("position"),this.li,!1,this);z(this,xd("positioning"),this.mi,!1,this);m(b.element)&&this.Ee(b.element);this.j(m(b.offset)?b.offset:[0,0]);this.p(m(b.positioning)?b.positioning: -"top-left");m(b.position)&&this.f(b.position)}u(P,td);P.prototype.d=function(){return this.get("element")};P.prototype.getElement=P.prototype.d;P.prototype.e=function(){return this.get("map")};P.prototype.getMap=P.prototype.e;P.prototype.g=function(){return this.get("offset")};P.prototype.getOffset=P.prototype.g;P.prototype.n=function(){return this.get("position")};P.prototype.getPosition=P.prototype.n;P.prototype.i=function(){return this.get("positioning")};P.prototype.getPositioning=P.prototype.i; -l=P.prototype;l.Uh=function(){Kf(this.aa);var b=this.d();null!=b&&Jf(this.aa,b)};l.hi=function(){null!==this.b&&(Nf(this.aa),Yc(this.b),this.b=null);var b=this.e();null!=b&&(this.b=z(b,"postrender",this.render,!1,this),Qo(this),b=this.r?b.t:b.Da,this.q?Mf(b,this.aa,0):Jf(b,this.aa))};l.render=function(){Qo(this)};l.ji=function(){Qo(this)};l.li=function(){Qo(this)};l.mi=function(){Qo(this)};l.Ee=function(b){this.set("element",b)};P.prototype.setElement=P.prototype.Ee; -P.prototype.setMap=function(b){this.set("map",b)};P.prototype.setMap=P.prototype.setMap;P.prototype.j=function(b){this.set("offset",b)};P.prototype.setOffset=P.prototype.j;P.prototype.f=function(b){this.set("position",b)};P.prototype.setPosition=P.prototype.f;P.prototype.p=function(b){this.set("positioning",b)};P.prototype.setPositioning=P.prototype.p; -function Qo(b){var c=b.e(),d=b.n();if(m(c)&&null!==c.d&&m(d)){var d=c.f(d),e=c.e(),c=b.aa.style,f=b.g(),g=b.i(),h=f[0],f=f[1];if("bottom-right"==g||"center-right"==g||"top-right"==g)""!==b.a.vd&&(b.a.vd=c.left=""),h=Math.round(e[0]-d[0]-h)+"px",b.a.Md!=h&&(b.a.Md=c.right=h);else{""!==b.a.Md&&(b.a.Md=c.right="");if("bottom-center"==g||"center-center"==g||"top-center"==g)h-=Qg(b.aa).width/2;h=Math.round(d[0]+h)+"px";b.a.vd!=h&&(b.a.vd=c.left=h)}if("bottom-left"==g||"bottom-center"==g||"bottom-right"== -g)""!==b.a.Nd&&(b.a.Nd=c.top=""),d=Math.round(e[1]-d[1]-f)+"px",b.a.Wc!=d&&(b.a.Wc=c.bottom=d);else{""!==b.a.Wc&&(b.a.Wc=c.bottom="");if("center-left"==g||"center-center"==g||"center-right"==g)f-=Qg(b.aa).height/2;d=Math.round(d[1]+f)+"px";b.a.Nd!=d&&(b.a.Nd=c.top=d)}b.a.visible||(Sg(b.aa,!0),b.a.visible=!0)}else b.a.visible&&(Sg(b.aa,!1),b.a.visible=!1)};function Ro(b){b=m(b)?b:{};this.e=m(b.collapsed)?b.collapsed:!0;this.f=m(b.collapsible)?b.collapsible:!0;this.f||(this.e=!1);var c=m(b.className)?b.className:"ol-overviewmap",d=m(b.tipLabel)?b.tipLabel:"Overview map";this.j=m(b.collapseLabel)?b.collapseLabel:"\u00ab";this.q=m(b.label)?b.label:"\u00bb";this.p=Ff("SPAN",{},this.f&&!this.e?this.j:this.q);d=Ff("BUTTON",{type:"button",title:d},this.p);z(d,"click",this.hj,!1,this);z(d,["mouseout",xc],function(){this.blur()},!1);var e=Ff("DIV","ol-overviewmap-map"), -f=this.b=new O({controls:new B,interactions:new B,target:e});m(b.layers)&&b.layers.forEach(function(b){f.Se(b)},this);var g=Ff("DIV","ol-overviewmap-box");this.g=new P({position:[0,0],positioning:"bottom-left",element:g});this.b.Te(this.g);c=Ff("DIV",c+" ol-unselectable ol-control"+(this.e&&this.f?" ol-collapsed":"")+(this.f?"":" ol-uncollapsible"),e,d);$g.call(this,{element:c,render:m(b.render)?b.render:So,target:b.target})}u(Ro,$g);l=Ro.prototype; -l.setMap=function(b){var c=this.a;null===b&&null!==c&&Xc(c,xd("view"),this.uf,!1,this);Ro.S.setMap.call(this,b);null!==b&&(0===this.b.ea().Ib()&&this.b.O("layergroup",b),To(this),z(b,xd("view"),this.uf,!1,this),this.b.j(),Uo(this))};function To(b){var c=b.a.a();null===c||b.b.a().O("rotation",c)} -function So(){var b=this.a,c=this.b;if(null!==b.d&&null!==c.d){var d=b.e(),b=b.a().g(d),e=c.e(),d=c.a().g(e),f=c.f(me(b)),c=c.f(ie(b)),c=new wf(Math.abs(f[0]-c[0]),Math.abs(f[1]-c[1])),f=e[0],e=e[1];c.width<.1*f||c.height<.1*e||c.width>.75*f||c.height>.75*e?Uo(this):$d(d,b)||(b=this.b,d=this.a.a(),b.a().Oa(d.a()))}Vo(this)}l.uf=function(){To(this)};function Uo(b){var c=b.a;b=b.b;var d=c.e(),c=c.a().g(d),d=b.e();b=b.a();var e=Math.log(7.5)/Math.LN2;ue(c,1/(.1*Math.pow(2,e/2)));b.ge(c,d)} -function Vo(b){var c=b.a,d=b.b;if(null!==c.d&&null!==d.d){var e=c.e(),f=c.a(),g=d.a();d.e();var c=f.e(),h=b.g,d=b.g.d(),f=f.g(e),e=g.b(),g=he(f),f=je(f),k;b=b.a.a().a();m(b)&&(k=[g[0]-b[0],g[1]-b[1]],Dd(k,c),yd(k,b));h.f(k);null!=d&&(k=new wf(Math.abs((g[0]-f[0])/e),Math.abs((f[1]-g[1])/e)),c=Rf(xf(zf(d))),!Hb||Tb("10")||c&&Tb("8")?(d=d.style,Ib?d.MozBoxSizing="border-box":Jb?d.WebkitBoxSizing="border-box":d.boxSizing="border-box",d.width=Math.max(k.width,0)+"px",d.height=Math.max(k.height,0)+"px"): -(b=d.style,c?(c=Vg(d,"padding"),d=Yg(d),b.pixelWidth=k.width-d.left-c.left-c.right-d.right,b.pixelHeight=k.height-d.top-c.top-c.bottom-d.bottom):(b.pixelWidth=k.width,b.pixelHeight=k.height)))}}l.hj=function(b){b.preventDefault();Wo(this)};function Wo(b){Eg(b.element,"ol-collapsed");Qf(b.p,b.e?b.j:b.q);b.e=!b.e;var c=b.b;b.e||null!==c.d||(c.j(),Uo(b),Wc(c,"postrender",function(){Vo(this)},!1,b))}l.gj=function(){return this.f}; -l.jj=function(b){this.f!==b&&(this.f=b,Eg(this.element,"ol-uncollapsible"),!b&&this.e&&Wo(this))};l.ij=function(b){this.f&&this.e!==b&&Wo(this)};l.fj=function(){return this.e};function Xo(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-scale-line";this.f=Ff("DIV",c+"-inner");this.aa=Ff("DIV",c+" ol-unselectable",this.f);this.q=null;this.g=m(b.minWidth)?b.minWidth:64;this.b=!1;this.t=void 0;this.r="";this.e=null;$g.call(this,{element:this.aa,render:m(b.render)?b.render:Yo,target:b.target});z(this,xd("units"),this.F,!1,this);this.p(b.units||"metric")}u(Xo,$g);var Zo=[1,2,5];Xo.prototype.j=function(){return this.get("units")};Xo.prototype.getUnits=Xo.prototype.j; -function Yo(b){b=b.frameState;null===b?this.q=null:this.q=b.viewState;$o(this)}Xo.prototype.F=function(){$o(this)};Xo.prototype.p=function(b){this.set("units",b)};Xo.prototype.setUnits=Xo.prototype.p; -function $o(b){var c=b.q;if(null===c)b.b&&(Sg(b.aa,!1),b.b=!1);else{var d=c.center,e=c.projection,c=e.je(c.resolution,d),f=e.c,g=b.j();"degrees"!=f||"metric"!=g&&"imperial"!=g&&"us"!=g&&"nautical"!=g?"ft"!=f&&"m"!=f||"degrees"!=g?b.e=null:(null===b.e&&(b.e=De(e,Ee("EPSG:4326"))),d=Math.cos(bc(b.e(d)[1])),e=ze.radius,"ft"==f&&(e/=.3048),c*=180/(Math.PI*d*e)):(b.e=null,d=Math.cos(bc(d[1])),c*=Math.PI*d*ze.radius/180);d=b.g*c;f="";"degrees"==g?d<1/60?(f="\u2033",c*=3600):1>d?(f="\u2032",c*=60):f="\u00b0": -"imperial"==g?.9144>d?(f="in",c/=.0254):1609.344>d?(f="ft",c/=.3048):(f="mi",c/=1609.344):"nautical"==g?(c/=1852,f="nm"):"metric"==g?1>d?(f="mm",c*=1E3):1E3>d?f="m":(f="km",c/=1E3):"us"==g&&(.9144>d?(f="in",c*=39.37):1609.344>d?(f="ft",c/=.30480061):(f="mi",c/=1609.3472));for(d=3*Math.floor(Math.log(b.g*c)/Math.log(10));;){e=Zo[d%3]*Math.pow(10,Math.floor(d/3));g=Math.round(e/c);if(isNaN(g)){Sg(b.aa,!1);b.b=!1;return}if(g>=b.g)break;++d}c=e+f;b.r!=c&&(b.f.innerHTML=c,b.r=c);b.t!=g&&(b.f.style.width= -g+"px",b.t=g);b.b||(Sg(b.aa,!0),b.b=!0)}};function ap(b){pc.call(this);this.c=b;this.a={}}u(ap,pc);var bp=[];ap.prototype.La=function(b,c,d,e){ga(c)||(c&&(bp[0]=c.toString()),c=bp);for(var f=0;f<c.length;f++){var g=z(b,c[f],d||this.handleEvent,e||!1,this.c||this);if(!g)break;this.a[g.key]=g}return this}; -ap.prototype.Fe=function(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)this.Fe(b,c[g],d,e,f);else d=d||this.handleEvent,f=f||this.c||this,d=Qc(d),e=!!e,c=Ec(b)?Lc(b.gb,String(c),d,e,f):b?(b=Sc(b))?Lc(b,c,d,e,f):null:null,c&&(Yc(c),delete this.a[c.key]);return this};function cp(b){ob(b.a,Yc);b.a={}}ap.prototype.M=function(){ap.S.M.call(this);cp(this)};ap.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};function dp(b,c,d){jd.call(this);this.target=b;this.handle=c||b;this.a=d||new Gg(NaN,NaN,NaN,NaN);this.b=zf(b);this.c=new ap(this);sc(this,this.c);z(this.handle,["touchstart","mousedown"],this.df,!1,this)}u(dp,jd);var ep=Hb||Ib&&Tb("1.9.3");l=dp.prototype;l.clientX=0;l.clientY=0;l.screenX=0;l.screenY=0;l.dg=0;l.eg=0;l.nc=0;l.oc=0;l.Rb=!1;l.M=function(){dp.S.M.call(this);Xc(this.handle,["touchstart","mousedown"],this.df,!1,this);cp(this.c);ep&&this.b.releaseCapture();this.handle=this.target=null}; -l.df=function(b){var c="mousedown"==b.type;if(this.Rb||c&&!Cc(b))this.dispatchEvent("earlycancel");else if(fp(b),this.dispatchEvent(new gp("start",this,b.clientX,b.clientY))){this.Rb=!0;b.preventDefault();var c=this.b,d=c.documentElement,e=!ep;this.c.La(c,["touchmove","mousemove"],this.ii,e);this.c.La(c,["touchend","mouseup"],this.od,e);ep?(d.setCapture(!1),this.c.La(d,"losecapture",this.od)):this.c.La(c?c.parentWindow||c.defaultView:window,"blur",this.od);this.e&&this.c.La(this.e,"scroll",this.sk, -e);this.clientX=this.dg=b.clientX;this.clientY=this.eg=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;this.nc=this.target.offsetLeft;this.oc=this.target.offsetTop;this.d=Sf(xf(this.b));ua()}};l.od=function(b){cp(this.c);ep&&this.b.releaseCapture();if(this.Rb){fp(b);this.Rb=!1;var c=hp(this,this.nc),d=ip(this,this.oc);this.dispatchEvent(new gp("end",this,b.clientX,b.clientY,0,c,d))}else this.dispatchEvent("earlycancel")}; -function fp(b){var c=b.type;"touchstart"==c||"touchmove"==c?Ac(b,b.a.targetTouches[0],b.b):"touchend"!=c&&"touchcancel"!=c||Ac(b,b.a.changedTouches[0],b.b)} -l.ii=function(b){fp(b);var c=1*(b.clientX-this.clientX),d=b.clientY-this.clientY;this.clientX=b.clientX;this.clientY=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;if(!this.Rb){var e=this.dg-this.clientX,f=this.eg-this.clientY;if(0<e*e+f*f)if(this.dispatchEvent(new gp("start",this,b.clientX,b.clientY)))this.Rb=!0;else{this.Sa||this.od(b);return}}d=jp(this,c,d);c=d.x;d=d.y;this.Rb&&this.dispatchEvent(new gp("beforedrag",this,b.clientX,b.clientY,0,c,d))&&(kp(this,b,c,d),b.preventDefault())}; -function jp(b,c,d){var e=Sf(xf(b.b));c+=e.x-b.d.x;d+=e.y-b.d.y;b.d=e;b.nc+=c;b.oc+=d;c=hp(b,b.nc);b=ip(b,b.oc);return new vf(c,b)}l.sk=function(b){var c=jp(this,0,0);b.clientX=this.clientX;b.clientY=this.clientY;kp(this,b,c.x,c.y)};function kp(b,c,d,e){b.target.style.left=d+"px";b.target.style.top=e+"px";b.dispatchEvent(new gp("drag",b,c.clientX,c.clientY,0,d,e))} -function hp(b,c){var d=b.a,e=isNaN(d.left)?null:d.left,d=isNaN(d.width)?0:d.width;return Math.min(null!=e?e+d:Infinity,Math.max(null!=e?e:-Infinity,c))}function ip(b,c){var d=b.a,e=isNaN(d.top)?null:d.top,d=isNaN(d.height)?0:d.height;return Math.min(null!=e?e+d:Infinity,Math.max(null!=e?e:-Infinity,c))}function gp(b,c,d,e,f,g,h){uc.call(this,b);this.clientX=d;this.clientY=e;this.left=m(g)?g:c.nc;this.top=m(h)?h:c.oc}u(gp,uc);function lp(b){b=m(b)?b:{};this.e=void 0;this.f=mp;this.g=null;this.j=!1;var c=m(b.className)?b.className:"ol-zoomslider",d=Ff("DIV",[c+"-thumb","ol-unselectable"]),c=Ff("DIV",[c,"ol-unselectable","ol-control"],d);this.b=new dp(d);sc(this,this.b);z(this.b,"start",this.Th,!1,this);z(this.b,"drag",this.Rh,!1,this);z(this.b,"end",this.Sh,!1,this);z(c,"click",this.Qh,!1,this);z(d,"click",vc);$g.call(this,{element:c,render:m(b.render)?b.render:np})}u(lp,$g);var mp=0;l=lp.prototype; -l.setMap=function(b){lp.S.setMap.call(this,b);null===b||b.render()}; -function np(b){if(null!==b.frameState){if(!this.j){var c=this.element,d=Qg(c),e=Of(c),c=Vg(e,"margin"),f=new wf(e.offsetWidth,e.offsetHeight),e=f.width+c.right+c.left,c=f.height+c.top+c.bottom;this.g=[e,c];e=d.width-e;c=d.height-c;d.width>d.height?(this.f=1,d=new Gg(0,0,e,0)):(this.f=mp,d=new Gg(0,0,0,c));this.b.a=d||new Gg(NaN,NaN,NaN,NaN);this.j=!0}b=b.frameState.viewState.resolution;b!==this.e&&(this.e=b,b=1-Ze(this.a.a())(b),d=this.b,c=Of(this.element),1==this.f?Kg(c,d.a.left+d.a.width*b):Kg(c, -d.a.left,d.a.top+d.a.height*b))}}l.Qh=function(b){var c=this.a,d=c.a(),e=d.b();c.Ua(jf({resolution:e,duration:200,easing:cf}));b=op(this,b.offsetX-this.g[0]/2,b.offsetY-this.g[1]/2);b=pp(this,b);d.d(d.constrainResolution(b))};l.Th=function(){bf(this.a.a(),1)};l.Rh=function(b){b=op(this,b.left,b.top);this.e=pp(this,b);this.a.a().d(this.e)};l.Sh=function(){var b=this.a,c=b.a();bf(c,-1);b.Ua(jf({resolution:this.e,duration:200,easing:cf}));b=c.constrainResolution(this.e);c.d(b)}; -function op(b,c,d){var e=b.b.a;return Yb(1===b.f?(c-e.left)/e.width:(d-e.top)/e.height,0,1)}function pp(b,c){return Ye(b.a.a())(1-c)};function qp(b){b=m(b)?b:{};this.b=m(b.extent)?b.extent:null;var c=m(b.className)?b.className:"ol-zoom-extent",d=Ff("BUTTON",{type:"button",title:m(b.tipLabel)?b.tipLabel:"Fit to extent"});z(d,"click",this.e,!1,this);z(d,["mouseout",xc],function(){this.blur()},!1);c=Ff("DIV",c+" ol-unselectable ol-control",d);$g.call(this,{element:c,target:b.target})}u(qp,$g);qp.prototype.e=function(b){b.preventDefault();var c=this.a;b=c.a();var d=null===this.b?b.q.D():this.b,c=c.e();b.ge(d,c)};function Q(b){td.call(this);b=m(b)?b:{};this.a=null;z(this,xd("tracking"),this.n,!1,this);this.b(m(b.tracking)?b.tracking:!1)}u(Q,td);Q.prototype.M=function(){this.b(!1);Q.S.M.call(this)}; -Q.prototype.j=function(b){b=b.a;if(null!=b.alpha){var c=bc(b.alpha);this.set("alpha",c);"boolean"==typeof b.absolute&&b.absolute?this.set("heading",c):null!=b.webkitCompassHeading&&null!=b.webkitCompassAccuracy&&-1!=b.webkitCompassAccuracy&&this.set("heading",bc(b.webkitCompassHeading))}null!=b.beta&&this.set("beta",bc(b.beta));null!=b.gamma&&this.set("gamma",bc(b.gamma));this.l()};Q.prototype.e=function(){return this.get("alpha")};Q.prototype.getAlpha=Q.prototype.e;Q.prototype.f=function(){return this.get("beta")}; -Q.prototype.getBeta=Q.prototype.f;Q.prototype.g=function(){return this.get("gamma")};Q.prototype.getGamma=Q.prototype.g;Q.prototype.i=function(){return this.get("heading")};Q.prototype.getHeading=Q.prototype.i;Q.prototype.d=function(){return this.get("tracking")};Q.prototype.getTracking=Q.prototype.d;Q.prototype.n=function(){if(eg){var b=this.d();b&&null===this.a?this.a=z(ba,"deviceorientation",this.j,!1,this):b||null===this.a||(Yc(this.a),this.a=null)}}; -Q.prototype.b=function(b){this.set("tracking",b)};Q.prototype.setTracking=Q.prototype.b;function rp(b){td.call(this);this.i=b;z(this.i,["change","input"],this.g,!1,this);z(this,xd("value"),this.n,!1,this);z(this,xd("checked"),this.f,!1,this)}u(rp,td);rp.prototype.a=function(){return this.get("checked")};rp.prototype.getChecked=rp.prototype.a;rp.prototype.b=function(){return this.get("value")};rp.prototype.getValue=rp.prototype.b;rp.prototype.e=function(b){this.set("value",b)};rp.prototype.setValue=rp.prototype.e;rp.prototype.d=function(b){this.set("checked",b)}; -rp.prototype.setChecked=rp.prototype.d;rp.prototype.g=function(){var b=this.i;"checkbox"===b.type||"radio"===b.type?this.d(b.checked):this.e(b.value)};rp.prototype.f=function(){this.i.checked=this.a()};rp.prototype.n=function(){this.i.value=this.b()};function R(b){td.call(this);this.X=void 0;this.b="geometry";this.g=null;this.a=void 0;this.f=null;z(this,xd(this.b),this.pd,!1,this);m(b)&&(b instanceof Mk||null===b?this.Ma(b):this.G(b))}u(R,td);R.prototype.clone=function(){var b=new R(this.L());b.e(this.b);var c=this.N();null!=c&&b.Ma(c.clone());c=this.g;null===c||b.i(c);return b};R.prototype.N=function(){return this.get(this.b)};R.prototype.getGeometry=R.prototype.N;l=R.prototype;l.nh=function(){return this.X};l.mh=function(){return this.b}; -l.Si=function(){return this.g};l.Ti=function(){return this.a};l.$h=function(){this.l()};l.pd=function(){null!==this.f&&(Yc(this.f),this.f=null);var b=this.N();null!=b&&(this.f=z(b,"change",this.$h,!1,this),this.l())};l.Ma=function(b){this.set(this.b,b)};R.prototype.setGeometry=R.prototype.Ma;R.prototype.i=function(b){this.g=b;null===b?b=void 0:ka(b)||(b=ga(b)?b:[b],b=bd(b));this.a=b;this.l()};R.prototype.d=function(b){this.X=b;this.l()}; -R.prototype.e=function(b){Xc(this,xd(this.b),this.pd,!1,this);this.b=b;z(this,xd(this.b),this.pd,!1,this);this.pd()};function sp(b){b=m(b)?b:{};this.g=this.d=this.e=this.c=this.b=this.a=null;this.f=void 0;this.tf(m(b.style)?b.style:Wl);m(b.features)?ga(b.features)?this.Mc(new B(cb(b.features))):this.Mc(b.features):this.Mc(new B);m(b.map)&&this.setMap(b.map)}l=sp.prototype;l.rf=function(b){this.a.push(b)};l.Ni=function(){return this.a};l.sf=function(){tp(this)};l.Yh=function(b){b=b.element;this.c[ma(b).toString()]=z(b,"change",this.sf,!1,this);tp(this)}; -l.Zh=function(b){b=ma(b.element).toString();Yc(this.c[b]);delete this.c[b];tp(this)};l.Qi=function(){tp(this)};l.Ri=function(b){if(null!==this.a){var c=this.f;m(c)||(c=Wl);var d=b.a;b=b.frameState;var e=b.viewState.resolution,f=zn(e,b.pixelRatio),g,h,k,n;this.a.forEach(function(b){n=b.a;k=m(n)?n.call(b,e):c(b,e);if(null!=k)for(h=k.length,g=0;g<h;++g)An(d,b,k[g],f,this.Qi,this)},this)}};l.wd=function(b){this.a.remove(b)};function tp(b){null===b.e||b.e.render()} -l.Mc=function(b){null!==this.b&&(Ta(this.b,Yc),this.b=null);null!==this.c&&(Ta(rb(this.c),Yc),this.c=null);this.a=b;null!==b&&(this.b=[z(b,"add",this.Yh,!1,this),z(b,"remove",this.Zh,!1,this)],this.c={},b.forEach(function(b){this.c[ma(b).toString()]=z(b,"change",this.sf,!1,this)},this));tp(this)};l.setMap=function(b){null!==this.d&&(Yc(this.d),this.d=null);tp(this);this.e=b;null!==b&&(this.d=z(b,"postcompose",this.Ri,!1,this),b.render())};l.tf=function(b){this.g=b;this.f=Vl(b);tp(this)};l.Oi=function(){return this.g}; -l.Pi=function(){return this.f};function up(){this.defaultDataProjection=null}function vp(b,c,d){var e;m(d)&&(e={dataProjection:m(d.dataProjection)?d.dataProjection:b.Ba(c),featureProjection:d.featureProjection});return wp(b,e)}function wp(b,c){var d;m(c)&&(d={featureProjection:c.featureProjection,dataProjection:null!=c.dataProjection?c.dataProjection:b.defaultDataProjection});return d} -function xp(b,c,d){var e=m(d)?Ee(d.featureProjection):null;d=m(d)?Ee(d.dataProjection):null;return null===e||null===d||Ue(e,d)?b:b instanceof Mk?(c?b.clone():b).e(c?e:d,c?d:e):Xe(c?cb(b):b,c?e:d,c?d:e)};var yp=ba.JSON.parse,zp=ba.JSON.stringify;function Ap(){this.defaultDataProjection=null}u(Ap,up);function Bp(b){return la(b)?b:ia(b)?(b=yp(b),m(b)?b:null):null}l=Ap.prototype;l.H=function(){return"json"};l.Nb=function(b,c){return Cp(this,Bp(b),vp(this,b,c))};l.ja=function(b,c){return this.b(Bp(b),vp(this,b,c))};l.Jc=function(b,c){var d=Bp(b),e=vp(this,b,c);return Dp(d,e)};l.Ba=function(b){b=Bp(b).crs;return null!=b?"name"==b.type?Ee(b.properties.name):"EPSG"==b.type?Ee("EPSG:"+b.properties.code):null:this.defaultDataProjection}; -l.Pd=function(b,c){return zp(this.a(b,c))};l.Qb=function(b,c){return zp(this.d(b,c))};l.Qc=function(b,c){return zp(this.e(b,c))};function Ep(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee(null!=b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326");this.c=b.geometryName}u(Ep,Ap);function Dp(b,c){return null===b?null:xp((0,Fp[b.type])(b),!1,c)} -var Fp={Point:function(b){return new jl(b.coordinates)},LineString:function(b){return new M(b.coordinates)},Polygon:function(b){return new H(b.coordinates)},MultiPoint:function(b){return new un(b.coordinates)},MultiLineString:function(b){return new rn(b.coordinates)},MultiPolygon:function(b){return new vn(b.coordinates)},GeometryCollection:function(b,c){var d=Va(b.geometries,function(b){return Dp(b,c)});return new jn(d)}},Gp={Point:function(b){return{type:"Point",coordinates:b.K()}},LineString:function(b){return{type:"LineString", -coordinates:b.K()}},Polygon:function(b){return{type:"Polygon",coordinates:b.K()}},MultiPoint:function(b){return{type:"MultiPoint",coordinates:b.K()}},MultiLineString:function(b){return{type:"MultiLineString",coordinates:b.K()}},MultiPolygon:function(b){return{type:"MultiPolygon",coordinates:b.K()}},GeometryCollection:function(b,c){return{type:"GeometryCollection",geometries:Va(b.d,function(b){return(0,Gp[b.H()])(xp(b,!0,c))})}},Circle:function(){return{type:"GeometryCollection",geometries:[]}}}; -function Cp(b,c,d){d=Dp(c.geometry,d);var e=new R;m(b.c)&&e.e(b.c);e.Ma(d);m(c.id)&&e.d(c.id);m(c.properties)&&e.G(c.properties);return e}Ep.prototype.b=function(b,c){if("Feature"==b.type)return[Cp(this,b,c)];if("FeatureCollection"==b.type){var d=[],e=b.features,f,g;f=0;for(g=e.length;f<g;++f)d.push(Cp(this,e[f],c));return d}return[]}; -Ep.prototype.a=function(b,c){c=wp(this,c);var d={type:"Feature"},e=b.X;null!=e&&(d.id=e);e=b.N();null!=e&&(e=(0,Gp[e.H()])(xp(e,!0,c)),d.geometry=e);e=b.L();zb(e,b.b);xb(e)||(d.properties=e);return d};Ep.prototype.d=function(b,c){c=wp(this,c);var d=[],e,f;e=0;for(f=b.length;e<f;++e)d.push(this.a(b[e],c));return{type:"FeatureCollection",features:d}};Ep.prototype.e=function(b,c){return(0,Gp[b.H()])(xp(b,!0,wp(this,c)))};function Hp(b){if("undefined"!=typeof XMLSerializer)return(new XMLSerializer).serializeToString(b);if(b=b.xml)return b;throw Error("Your browser does not support serializing XML documents");};var Ip;a:if(document.implementation&&document.implementation.createDocument)Ip=document.implementation.createDocument("","",null);else{if("undefined"!=typeof ActiveXObject){var Jp=new ActiveXObject("MSXML2.DOMDocument");if(Jp){Jp.resolveExternals=!1;Jp.validateOnParse=!1;try{Jp.setProperty("ProhibitDTD",!0),Jp.setProperty("MaxXMLSize",2048),Jp.setProperty("MaxElementDepth",256)}catch(Kp){}}if(Jp){Ip=Jp;break a}}throw Error("Your browser does not support creating new documents");}var Lp=Ip; -function Mp(b,c){return Lp.createElementNS(b,c)}function Np(b,c){null===b&&(b="");return Lp.createNode(1,c,b)}var Op=document.implementation&&document.implementation.createDocument?Mp:Np;function Pp(b,c){return Qp(b,c,[]).join("")}function Qp(b,c,d){if(4==b.nodeType||3==b.nodeType)c?d.push(String(b.nodeValue).replace(/(\r\n|\r|\n)/g,"")):d.push(b.nodeValue);else for(b=b.firstChild;null!==b;b=b.nextSibling)Qp(b,c,d);return d}function Rp(b){return b.localName} -function Sp(b){var c=b.localName;return m(c)?c:b.baseName}var Tp=Hb?Sp:Rp;function Up(b){return b instanceof Document}function Vp(b){return la(b)&&9==b.nodeType}var Wp=Hb?Vp:Up;function Xp(b){return b instanceof Node}function Yp(b){return la(b)&&m(b.nodeType)}var Zp=Hb?Yp:Xp;function $p(b,c,d){return b.getAttributeNS(c,d)||""}function aq(b,c,d){var e="";b=bq(b,c,d);m(b)&&(e=b.nodeValue);return e}var cq=document.implementation&&document.implementation.createDocument?$p:aq; -function dq(b,c,d){return b.getAttributeNodeNS(c,d)}function eq(b,c,d){var e=null;b=b.attributes;for(var f,g,h=0,k=b.length;h<k;++h)if(f=b[h],f.namespaceURI==c&&(g=f.prefix?f.prefix+":"+d:d,g==f.nodeName)){e=f;break}return e}var bq=document.implementation&&document.implementation.createDocument?dq:eq;function fq(b,c,d,e){b.setAttributeNS(c,d,e)}function gq(b,c,d,e){null===c?b.setAttribute(d,e):(c=b.ownerDocument.createNode(2,d,c),c.nodeValue=e,b.setAttributeNode(c))} -var hq=document.implementation&&document.implementation.createDocument?fq:gq;function iq(b){return(new DOMParser).parseFromString(b,"application/xml")}function jq(b,c){return function(d,e){var f=b.call(c,d,e);m(f)&&db(e[e.length-1],f)}}function kq(b,c){return function(d,e){var f=b.call(m(c)?c:this,d,e);m(f)&&e[e.length-1].push(f)}}function lq(b,c){return function(d,e){var f=b.call(m(c)?c:this,d,e);m(f)&&(e[e.length-1]=f)}} -function mq(b){return function(c,d){var e=b.call(m(void 0)?void 0:this,c,d);m(e)&&Bb(d[d.length-1],m(void 0)?void 0:c.localName).push(e)}}function S(b,c){return function(d,e){var f=b.call(m(void 0)?void 0:this,d,e);m(f)&&(e[e.length-1][m(c)?c:d.localName]=f)}}function nq(b,c,d){return oq(b,c,d)}function V(b,c){return function(d,e,f){b.call(m(c)?c:this,d,e,f);f[f.length-1].node.appendChild(d)}} -function pq(b){var c,d;return function(e,f,g){if(!m(c)){c={};var h={};h[e.localName]=b;c[e.namespaceURI]=h;d=qq(e.localName)}rq(c,d,f,g)}}function qq(b,c){return function(d,e,f){d=e[e.length-1].node;e=b;m(e)||(e=f);f=c;m(c)||(f=d.namespaceURI);return Op(f,e)}}var sq=qq();function tq(b,c){for(var d=c.length,e=Array(d),f=0;f<d;++f)e[f]=b[c[f]];return e}function oq(b,c,d){d=m(d)?d:{};var e,f;e=0;for(f=b.length;e<f;++e)d[b[e]]=c;return d} -function uq(b,c,d,e){for(c=c.firstElementChild;null!==c;c=c.nextElementSibling){var f=b[c.namespaceURI];m(f)&&(f=f[c.localName],m(f)&&f.call(e,c,d))}}function W(b,c,d,e,f){e.push(b);uq(c,d,e,f);return e.pop()}function rq(b,c,d,e,f,g){for(var h=(m(f)?f:d).length,k,n,p=0;p<h;++p)k=d[p],m(k)&&(n=c.call(g,k,e,m(f)?f[p]:void 0),m(n)&&b[n.namespaceURI][n.localName].call(g,n,k,e))}function vq(b,c,d,e,f,g,h){f.push(b);rq(c,d,e,f,g,h);f.pop()};function wq(){this.defaultDataProjection=null}u(wq,up);l=wq.prototype;l.H=function(){return"xml"};l.Nb=function(b,c){if(Wp(b))return xq(this,b,c);if(Zp(b))return this.Pf(b,c);if(ia(b)){var d=iq(b);return xq(this,d,c)}return null};function xq(b,c,d){b=yq(b,c,d);return 0<b.length?b[0]:null}l.ja=function(b,c){if(Wp(b))return yq(this,b,c);if(Zp(b))return this.Ob(b,c);if(ia(b)){var d=iq(b);return yq(this,d,c)}return[]}; -function yq(b,c,d){var e=[];for(c=c.firstChild;null!==c;c=c.nextSibling)1==c.nodeType&&db(e,b.Ob(c,d));return e}l.Jc=function(b,c){if(Wp(b))return this.i(b,c);if(Zp(b)){var d=this.Id(b,[vp(this,b,m(c)?c:{})]);return m(d)?d:null}return ia(b)?(d=iq(b),this.i(d,c)):null};l.Ba=function(b){return Wp(b)?this.Lc(b):Zp(b)?this.tc(b):ia(b)?(b=iq(b),this.Lc(b)):null};l.Pd=function(b,c){var d=this.p(b,c);return Hp(d)};l.Qb=function(b,c){var d=this.a(b,c);return Hp(d)};l.Qc=function(b,c){var d=this.o(b,c);return Hp(d)};function zq(b){b=m(b)?b:{};this.featureType=b.featureType;this.featureNS=b.featureNS;this.srsName=b.srsName;this.schemaLocation="";this.defaultDataProjection=null}u(zq,wq);l=zq.prototype; -l.pe=function(b,c){var d=Tp(b),e;if("FeatureCollection"==d)e=W(null,this.Td,b,c,this);else if("featureMembers"==d||"featureMember"==d){e=c[0];var f=x(e,"featureType");if(!m(f)&&null!==b.firstElementChild){var g=b.firstElementChild,f=g.nodeName.split(":").pop();e.featureType=f;e.featureNS=g.namespaceURI}var g={},h={};g[f]="featureMembers"==d?kq(this.ye,this):lq(this.ye,this);h[x(e,"featureNS")]=g;e=W([],h,b,c)}m(e)||(e=[]);return e}; -l.Td=Object({"http://www.opengis.net/gml":{featureMember:kq(zq.prototype.pe),featureMembers:lq(zq.prototype.pe)}});l.Id=function(b,c){var d=c[0],e=b.firstElementChild.getAttribute("srsName");d.srsName=e;e=W(null,this.Le,b,c,this);if(null!=e)return xp(e,!1,d)}; -l.ye=function(b,c){var d,e=b.getAttribute("fid")||cq(b,"http://www.opengis.net/gml","id"),f={},g;for(d=b.firstElementChild;null!==d;d=d.nextElementSibling){var h=Tp(d);if(0===d.childNodes.length||1===d.childNodes.length&&3===d.firstChild.nodeType){var k=Pp(d,!1);/^[\s\xa0]*$/.test(k)&&(k=void 0);f[h]=k}else"boundedBy"!==h&&(g=h),f[h]=this.Id(d,c)}d=new R(f);m(g)&&d.e(g);e&&d.d(e);return d};l.Vf=function(b,c){var d=this.Hd(b,c);if(null!=d){var e=new jl(null);kl(e,"XYZ",d);return e}}; -l.Tf=function(b,c){var d=W([],this.yg,b,c,this);if(m(d))return new un(d)};l.Sf=function(b,c){var d=W([],this.xg,b,c,this);if(m(d)){var e=new rn(null);tn(e,d);return e}};l.Uf=function(b,c){var d=W([],this.zg,b,c,this);if(m(d)){var e=new vn(null);xn(e,d);return e}};l.Kf=function(b,c){uq(this.Cg,b,c,this)};l.mf=function(b,c){uq(this.vg,b,c,this)};l.Lf=function(b,c){uq(this.Dg,b,c,this)};l.Jd=function(b,c){var d=this.Hd(b,c);if(null!=d){var e=new M(null);qn(e,"XYZ",d);return e}}; -l.Ik=function(b,c){var d=W(null,this.Sc,b,c,this);if(null!=d)return d};l.Rf=function(b,c){var d=this.Hd(b,c);if(m(d)){var e=new hl(null);il(e,"XYZ",d);return e}};l.Kd=function(b,c){var d=W([null],this.Ud,b,c,this);if(m(d)&&null!==d[0]){var e=new H(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)db(f,d[h]),g.push(f.length);wl(e,"XYZ",f,g);return e}};l.Hd=function(b,c){return W(null,this.Sc,b,c,this)};l.yg=Object({"http://www.opengis.net/gml":{pointMember:kq(zq.prototype.Kf),pointMembers:kq(zq.prototype.Kf)}}); -l.xg=Object({"http://www.opengis.net/gml":{lineStringMember:kq(zq.prototype.mf),lineStringMembers:kq(zq.prototype.mf)}});l.zg=Object({"http://www.opengis.net/gml":{polygonMember:kq(zq.prototype.Lf),polygonMembers:kq(zq.prototype.Lf)}});l.Cg=Object({"http://www.opengis.net/gml":{Point:kq(zq.prototype.Hd)}});l.vg=Object({"http://www.opengis.net/gml":{LineString:kq(zq.prototype.Jd)}});l.Dg=Object({"http://www.opengis.net/gml":{Polygon:kq(zq.prototype.Kd)}});l.Tc=Object({"http://www.opengis.net/gml":{LinearRing:lq(zq.prototype.Ik)}}); -l.Ob=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};m(c)&&Eb(d,vp(this,b,c));return this.pe(b,[d])};l.tc=function(b){return Ee(m(this.n)?this.n:b.firstElementChild.getAttribute("srsName"))};function Aq(b){b=Pp(b,!1);return Bq(b)}function Bq(b){if(b=/^\s*(true|1)|(false|0)\s*$/.exec(b))return m(b[1])||!1}function Cq(b){b=Pp(b,!1);if(b=/^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/.exec(b)){var c=Date.UTC(parseInt(b[1],10),parseInt(b[2],10)-1,parseInt(b[3],10),parseInt(b[4],10),parseInt(b[5],10),parseInt(b[6],10))/1E3;if("Z"!=b[7]){var d="-"==b[8]?-1:1,c=c+60*d*parseInt(b[9],10);m(b[10])&&(c+=3600*d*parseInt(b[10],10))}return c}} -function Dq(b){b=Pp(b,!1);return Eq(b)}function Eq(b){if(b=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(b))return parseFloat(b[1])}function Fq(b){b=Pp(b,!1);return Gq(b)}function Gq(b){if(b=/^\s*(\d+)\s*$/.exec(b))return parseInt(b[1],10)}function Hq(b){b=Pp(b,!1);return Aa(b)}function Iq(b,c){Jq(b,c?"1":"0")}function Kq(b,c){b.appendChild(Lp.createTextNode(c.toPrecision()))}function Lq(b,c){b.appendChild(Lp.createTextNode(c.toString()))}function Jq(b,c){b.appendChild(Lp.createTextNode(c))};function X(b){b=m(b)?b:{};zq.call(this,b);this.g=m(b.surface)?b.surface:!1;this.d=m(b.curve)?b.curve:!1;this.e=m(b.multiCurve)?b.multiCurve:!0;this.f=m(b.multiSurface)?b.multiSurface:!0;this.schemaLocation=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd"}u(X,zq);l=X.prototype;l.Lk=function(b,c){var d=W([],this.wg,b,c,this);if(m(d)){var e=new rn(null);tn(e,d);return e}}; -l.Mk=function(b,c){var d=W([],this.Ag,b,c,this);if(m(d)){var e=new vn(null);xn(e,d);return e}};l.We=function(b,c){uq(this.sg,b,c,this)};l.fg=function(b,c){uq(this.Gg,b,c,this)};l.Ok=function(b,c){return W([null],this.Bg,b,c,this)};l.Qk=function(b,c){return W([null],this.Fg,b,c,this)};l.Pk=function(b,c){return W([null],this.Ud,b,c,this)};l.Kk=function(b,c){return W([null],this.Sc,b,c,this)};l.vi=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&c[c.length-1].push(d)}; -l.ah=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&(c[c.length-1][0]=d)};l.Wf=function(b,c){var d=W([null],this.Hg,b,c,this);if(m(d)&&null!==d[0]){var e=new H(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)db(f,d[h]),g.push(f.length);wl(e,"XYZ",f,g);return e}};l.Nf=function(b,c){var d=W([null],this.tg,b,c,this);if(m(d)){var e=new M(null);qn(e,"XYZ",d);return e}};l.Hk=function(b,c){var d=W([null],this.ug,b,c,this);return Xd(d[1][0],d[1][1],d[2][0],d[2][1])}; -l.Jk=function(b,c){for(var d=Pp(b,!1),e=/^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/,f=[],g;g=e.exec(d);)f.push(parseFloat(g[1])),d=d.substr(g[0].length);if(""===d){d=x(c[0],"srsName");e="enu";null===d||(e=Ce(Ee(d)));if("neu"===e)for(d=0,e=f.length;d<e;d+=3)g=f[d],f[d]=f[d+1],f[d+1]=g;d=f.length;2==d&&f.push(0);return 0===d?void 0:f}}; -l.Ae=function(b,c){var d=Pp(b,!1).replace(/^\s*|\s*$/g,""),e=x(c[0],"srsName"),f=b.parentNode.getAttribute("srsDimension"),g="enu";null===e||(g=Ce(Ee(e)));d=d.split(/\s+/);e=2;fa(b.getAttribute("srsDimension"))?fa(b.getAttribute("dimension"))?null===f||(e=Gq(f)):e=Gq(b.getAttribute("dimension")):e=Gq(b.getAttribute("srsDimension"));for(var h,k,n=[],p=0,q=d.length;p<q;p+=e)f=parseFloat(d[p]),h=parseFloat(d[p+1]),k=3===e?parseFloat(d[p+2]):0,"en"===g.substr(0,2)?n.push(f,h,k):n.push(h,f,k);return n}; -l.Sc=Object({"http://www.opengis.net/gml":{pos:lq(X.prototype.Jk),posList:lq(X.prototype.Ae)}});l.Ud=Object({"http://www.opengis.net/gml":{interior:X.prototype.vi,exterior:X.prototype.ah}}); -l.Le=Object({"http://www.opengis.net/gml":{Point:lq(zq.prototype.Vf),MultiPoint:lq(zq.prototype.Tf),LineString:lq(zq.prototype.Jd),MultiLineString:lq(zq.prototype.Sf),LinearRing:lq(zq.prototype.Rf),Polygon:lq(zq.prototype.Kd),MultiPolygon:lq(zq.prototype.Uf),Surface:lq(X.prototype.Wf),MultiSurface:lq(X.prototype.Mk),Curve:lq(X.prototype.Nf),MultiCurve:lq(X.prototype.Lk),Envelope:lq(X.prototype.Hk)}});l.wg=Object({"http://www.opengis.net/gml":{curveMember:kq(X.prototype.We),curveMembers:kq(X.prototype.We)}}); -l.Ag=Object({"http://www.opengis.net/gml":{surfaceMember:kq(X.prototype.fg),surfaceMembers:kq(X.prototype.fg)}});l.sg=Object({"http://www.opengis.net/gml":{LineString:kq(zq.prototype.Jd),Curve:kq(X.prototype.Nf)}});l.Gg=Object({"http://www.opengis.net/gml":{Polygon:kq(zq.prototype.Kd),Surface:kq(X.prototype.Wf)}});l.Hg=Object({"http://www.opengis.net/gml":{patches:lq(X.prototype.Ok)}});l.tg=Object({"http://www.opengis.net/gml":{segments:lq(X.prototype.Qk)}}); -l.ug=Object({"http://www.opengis.net/gml":{lowerCorner:kq(X.prototype.Ae),upperCorner:kq(X.prototype.Ae)}});l.Bg=Object({"http://www.opengis.net/gml":{PolygonPatch:lq(X.prototype.Pk)}});l.Fg=Object({"http://www.opengis.net/gml":{LineStringSegment:lq(X.prototype.Kk)}});function Mq(b,c,d){d=x(d[d.length-1],"srsName");c=c.K();for(var e=c.length,f=Array(e),g,h=0;h<e;++h){g=c[h];var k=h,n="enu";null!=d&&(n=Ce(Ee(d)));f[k]="en"===n.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0]}Jq(b,f.join(" "))} -l.og=function(b,c,d){var e=x(d[d.length-1],"srsName");null!=e&&b.setAttribute("srsName",e);e=Op(b.namespaceURI,"pos");b.appendChild(e);d=x(d[d.length-1],"srsName");b="enu";null!=d&&(b=Ce(Ee(d)));c=c.K();Jq(e,"en"===b.substr(0,2)?c[0]+" "+c[1]:c[1]+" "+c[0])};var Nq={"http://www.opengis.net/gml":{lowerCorner:V(Jq),upperCorner:V(Jq)}};l=X.prototype; -l.zl=function(b,c,d){var e=x(d[d.length-1],"srsName");m(e)&&b.setAttribute("srsName",e);vq({node:b},Nq,sq,[c[0]+" "+c[1],c[2]+" "+c[3]],d,["lowerCorner","upperCorner"],this)};l.lg=function(b,c,d){var e=x(d[d.length-1],"srsName");null!=e&&b.setAttribute("srsName",e);e=Op(b.namespaceURI,"posList");b.appendChild(e);Mq(e,c,d)};l.Eg=function(b,c){var d=c[c.length-1],e=d.node,f=x(d,"exteriorWritten");m(f)||(d.exteriorWritten=!0);return Op(e.namespaceURI,m(f)?"interior":"exterior")}; -l.Sd=function(b,c,d){var e=x(d[d.length-1],"srsName");"PolygonPatch"!==b.nodeName&&null!=e&&b.setAttribute("srsName",e);"Polygon"===b.nodeName||"PolygonPatch"===b.nodeName?(c=c.dd(),vq({node:b,srsName:e},Oq,this.Eg,c,d,void 0,this)):"Surface"===b.nodeName&&(e=Op(b.namespaceURI,"patches"),b.appendChild(e),b=Op(e.namespaceURI,"PolygonPatch"),e.appendChild(b),this.Sd(b,c,d))}; -l.Od=function(b,c,d){var e=x(d[d.length-1],"srsName");"LineStringSegment"!==b.nodeName&&null!=e&&b.setAttribute("srsName",e);"LineString"===b.nodeName||"LineStringSegment"===b.nodeName?(e=Op(b.namespaceURI,"posList"),b.appendChild(e),Mq(e,c,d)):"Curve"===b.nodeName&&(e=Op(b.namespaceURI,"segments"),b.appendChild(e),b=Op(e.namespaceURI,"LineStringSegment"),e.appendChild(b),this.Od(b,c,d))}; -l.ng=function(b,c,d){var e=d[d.length-1],f=x(e,"srsName"),e=x(e,"surface");null!=f&&b.setAttribute("srsName",f);c=c.gd();vq({node:b,srsName:f,surface:e},Pq,this.b,c,d,void 0,this)};l.Dl=function(b,c,d){var e=x(d[d.length-1],"srsName");null!=e&&b.setAttribute("srsName",e);c=c.xd();vq({node:b,srsName:e},Qq,qq("pointMember"),c,d,void 0,this)}; -l.mg=function(b,c,d){var e=d[d.length-1],f=x(e,"srsName"),e=x(e,"curve");null!=f&&b.setAttribute("srsName",f);c=c.Ec();vq({node:b,srsName:f,curve:e},Rq,this.b,c,d,void 0,this)};l.pg=function(b,c,d){var e=Op(b.namespaceURI,"LinearRing");b.appendChild(e);this.lg(e,c,d)};l.qg=function(b,c,d){var e=this.c(c,d);m(e)&&(b.appendChild(e),this.Sd(e,c,d))};l.Gl=function(b,c,d){var e=Op(b.namespaceURI,"Point");b.appendChild(e);this.og(e,c,d)}; -l.kg=function(b,c,d){var e=this.c(c,d);m(e)&&(b.appendChild(e),this.Od(e,c,d))};l.Rd=function(b,c,d){var e=d[d.length-1],f=Cb(e);f.node=b;var g;ga(c)?m(e.dataProjection)?g=Xe(c,e.featureProjection,e.dataProjection):g=c:g=xp(c,!0,e);vq(f,Sq,this.c,[g],d,void 0,this)}; -l.hg=function(b,c,d){var e=c.X;m(e)&&b.setAttribute("fid",e);var e=d[d.length-1],f=x(e,"featureNS"),g=c.b;m(e.ac)||(e.ac={},e.ac[f]={});var h=c.L();c=[];var k=[],n;for(n in h){var p=h[n];null!==p&&(c.push(n),k.push(p),n==g?n in e.ac[f]||(e.ac[f][n]=V(this.Rd,this)):n in e.ac[f]||(e.ac[f][n]=V(Jq)))}n=Cb(e);n.node=b;vq(n,e.ac,qq(void 0,f),k,d,c)}; -var Pq={"http://www.opengis.net/gml":{surfaceMember:V(X.prototype.qg),polygonMember:V(X.prototype.qg)}},Qq={"http://www.opengis.net/gml":{pointMember:V(X.prototype.Gl)}},Rq={"http://www.opengis.net/gml":{lineStringMember:V(X.prototype.kg),curveMember:V(X.prototype.kg)}},Oq={"http://www.opengis.net/gml":{exterior:V(X.prototype.pg),interior:V(X.prototype.pg)}},Sq={"http://www.opengis.net/gml":{Curve:V(X.prototype.Od),MultiCurve:V(X.prototype.mg),Point:V(X.prototype.og),MultiPoint:V(X.prototype.Dl), -LineString:V(X.prototype.Od),MultiLineString:V(X.prototype.mg),LinearRing:V(X.prototype.lg),Polygon:V(X.prototype.Sd),MultiPolygon:V(X.prototype.ng),Surface:V(X.prototype.Sd),MultiSurface:V(X.prototype.ng),Envelope:V(X.prototype.zl)}},Tq={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};X.prototype.b=function(b,c){return Op("http://www.opengis.net/gml",Tq[c[c.length-1].node.nodeName])}; -X.prototype.c=function(b,c){var d=c[c.length-1],e=x(d,"multiSurface"),f=x(d,"surface"),g=x(d,"curve"),d=x(d,"multiCurve"),h;ga(b)?h="Envelope":(h=b.H(),"MultiPolygon"===h&&!0===e?h="MultiSurface":"Polygon"===h&&!0===f?h="Surface":"LineString"===h&&!0===g?h="Curve":"MultiLineString"===h&&!0===d&&(h="MultiCurve"));return Op("http://www.opengis.net/gml",h)}; -X.prototype.o=function(b,c){c=wp(this,c);var d=Op("http://www.opengis.net/gml","geom"),e={node:d,srsName:this.srsName,curve:this.d,surface:this.g,multiSurface:this.f,multiCurve:this.e};m(c)&&Eb(e,c);this.Rd(d,b,[e]);return d}; -X.prototype.a=function(b,c){c=wp(this,c);var d=Op("http://www.opengis.net/gml","featureMembers");hq(d,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.schemaLocation);var e={srsName:this.srsName,curve:this.d,surface:this.g,multiSurface:this.f,multiCurve:this.e,featureNS:this.featureNS,featureType:this.featureType};m(c)&&Eb(e,c);var e=[e],f=e[e.length-1],g=x(f,"featureType"),h=x(f,"featureNS"),k={};k[h]={};k[h][g]=V(this.hg,this);f=Cb(f);f.node=d;vq(f,k,qq(g,h),b,e);return d};function Uq(b){b=m(b)?b:{};zq.call(this,b);this.schemaLocation=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}u(Uq,zq);l=Uq.prototype; -l.Qf=function(b,c){var d=Pp(b,!1).replace(/^\s*|\s*$/g,""),e=x(c[0],"srsName"),f=b.parentNode.getAttribute("srsDimension"),g="enu";null===e||(g=Ce(Ee(e)));d=d.split(/[\s,]+/);e=2;fa(b.getAttribute("srsDimension"))?fa(b.getAttribute("dimension"))?null===f||(e=Gq(f)):e=Gq(b.getAttribute("dimension")):e=Gq(b.getAttribute("srsDimension"));for(var h,k,n=[],p=0,q=d.length;p<q;p+=e)f=parseFloat(d[p]),h=parseFloat(d[p+1]),k=3===e?parseFloat(d[p+2]):0,"en"===g.substr(0,2)?n.push(f,h,k):n.push(h,f,k);return n}; -l.Gk=function(b,c){var d=W([null],this.rg,b,c,this);return Xd(d[1][0],d[1][1],d[1][3],d[1][4])};l.ti=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&c[c.length-1].push(d)};l.tk=function(b,c){var d=W(void 0,this.Tc,b,c,this);m(d)&&(c[c.length-1][0]=d)};l.Sc=Object({"http://www.opengis.net/gml":{coordinates:lq(Uq.prototype.Qf)}});l.Ud=Object({"http://www.opengis.net/gml":{innerBoundaryIs:Uq.prototype.ti,outerBoundaryIs:Uq.prototype.tk}});l.rg=Object({"http://www.opengis.net/gml":{coordinates:kq(Uq.prototype.Qf)}}); -l.Le=Object({"http://www.opengis.net/gml":{Point:lq(zq.prototype.Vf),MultiPoint:lq(zq.prototype.Tf),LineString:lq(zq.prototype.Jd),MultiLineString:lq(zq.prototype.Sf),LinearRing:lq(zq.prototype.Rf),Polygon:lq(zq.prototype.Kd),MultiPolygon:lq(zq.prototype.Uf),Box:lq(Uq.prototype.Gk)}});function Vq(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.c=b.readExtensions}u(Vq,wq);var Wq=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function Xq(b,c,d){b.push(parseFloat(c.getAttribute("lon")),parseFloat(c.getAttribute("lat")));"ele"in d?(b.push(x(d,"ele")),zb(d,"ele")):b.push(0);"time"in d?(b.push(x(d,"time")),zb(d,"time")):b.push(0);return b} -function Yq(b,c){var d=c[c.length-1],e=b.getAttribute("href");null!==e&&(d.link=e);uq(Zq,b,c)}function $q(b,c){c[c.length-1].extensionsNode_=b}function ar(b,c){var d=c[0],e=W({flatCoordinates:[]},br,b,c);if(m(e)){var f=x(e,"flatCoordinates");zb(e,"flatCoordinates");var g=new M(null);qn(g,"XYZM",f);xp(g,!1,d);d=new R(g);d.G(e);return d}} -function cr(b,c){var d=c[0],e=W({flatCoordinates:[],ends:[]},dr,b,c);if(m(e)){var f=x(e,"flatCoordinates");zb(e,"flatCoordinates");var g=x(e,"ends");zb(e,"ends");var h=new rn(null);sn(h,"XYZM",f,g);xp(h,!1,d);d=new R(h);d.G(e);return d}}function er(b,c){var d=c[0],e=W({},fr,b,c);if(m(e)){var f=Xq([],b,e),f=new jl(f,"XYZM");xp(f,!1,d);d=new R(f);d.G(e);return d}} -var gr={rte:ar,trk:cr,wpt:er},hr=nq(Wq,{rte:kq(ar),trk:kq(cr),wpt:kq(er)}),Zq=nq(Wq,{text:S(Hq,"linkText"),type:S(Hq,"linkType")}),br=nq(Wq,{name:S(Hq),cmt:S(Hq),desc:S(Hq),src:S(Hq),link:Yq,number:S(Fq),extensions:$q,type:S(Hq),rtept:function(b,c){var d=W({},ir,b,c);m(d)&&Xq(x(c[c.length-1],"flatCoordinates"),b,d)}}),ir=nq(Wq,{ele:S(Dq),time:S(Cq)}),dr=nq(Wq,{name:S(Hq),cmt:S(Hq),desc:S(Hq),src:S(Hq),link:Yq,number:S(Fq),type:S(Hq),extensions:$q,trkseg:function(b,c){var d=c[c.length-1];uq(jr,b,c); -x(d,"ends").push(x(d,"flatCoordinates").length)}}),jr=nq(Wq,{trkpt:function(b,c){var d=W({},kr,b,c);m(d)&&Xq(x(c[c.length-1],"flatCoordinates"),b,d)}}),kr=nq(Wq,{ele:S(Dq),time:S(Cq)}),fr=nq(Wq,{ele:S(Dq),time:S(Cq),magvar:S(Dq),geoidheight:S(Dq),name:S(Hq),cmt:S(Hq),desc:S(Hq),src:S(Hq),link:Yq,sym:S(Hq),type:S(Hq),fix:S(Hq),sat:S(Fq),hdop:S(Dq),vdop:S(Dq),pdop:S(Dq),ageofdgpsdata:S(Dq),dgpsid:S(Fq),extensions:$q}); -function lr(b,c){null===c&&(c=[]);for(var d=0,e=c.length;d<e;++d){var f=c[d];if(m(b.c)){var g=f.get("extensionsNode_")||null;b.c(f,g)}f.set("extensionsNode_",void 0)}}Vq.prototype.Pf=function(b,c){if(!Za(Wq,b.namespaceURI))return null;var d=gr[b.localName];if(!m(d))return null;d=d(b,[vp(this,b,c)]);if(!m(d))return null;lr(this,[d]);return d};Vq.prototype.Ob=function(b,c){if(!Za(Wq,b.namespaceURI))return[];if("gpx"==b.localName){var d=W([],hr,b,[vp(this,b,c)]);if(m(d))return lr(this,d),d}return[]}; -Vq.prototype.Lc=function(){return this.defaultDataProjection};Vq.prototype.tc=function(){return this.defaultDataProjection};function mr(b,c,d){b.setAttribute("href",c);c=x(d[d.length-1],"properties");vq({node:b},nr,sq,[x(c,"linkText"),x(c,"linkType")],d,or)} -function pr(b,c,d){var e=d[d.length-1],f=e.node.namespaceURI,g=x(e,"properties");hq(b,null,"lat",c[1]);hq(b,null,"lon",c[0]);switch(x(e,"geometryLayout")){case "XYZM":0!==c[3]&&(g.time=c[3]);case "XYZ":0!==c[2]&&(g.ele=c[2]);break;case "XYM":0!==c[2]&&(g.time=c[2])}c=qr[f];e=tq(g,c);vq({node:b,properties:g},rr,sq,e,d,c)} -var or=["text","type"],nr=oq(Wq,{text:V(Jq),type:V(Jq)}),sr=oq(Wq,"name cmt desc src link number type rtept".split(" ")),tr=oq(Wq,{name:V(Jq),cmt:V(Jq),desc:V(Jq),src:V(Jq),link:V(mr),number:V(Lq),type:V(Jq),rtept:pq(V(pr))}),ur=oq(Wq,"name cmt desc src link number type trkseg".split(" ")),xr=oq(Wq,{name:V(Jq),cmt:V(Jq),desc:V(Jq),src:V(Jq),link:V(mr),number:V(Lq),type:V(Jq),trkseg:pq(V(function(b,c,d){vq({node:b,geometryLayout:c.a,properties:{}},vr,wr,c.K(),d)}))}),wr=qq("trkpt"),vr=oq(Wq,{trkpt:V(pr)}), -qr=oq(Wq,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),rr=oq(Wq,{ele:V(Kq),time:V(function(b,c){var d=new Date(1E3*c),d=d.getUTCFullYear()+"-"+Ma(d.getUTCMonth()+1)+"-"+Ma(d.getUTCDate())+"T"+Ma(d.getUTCHours())+":"+Ma(d.getUTCMinutes())+":"+Ma(d.getUTCSeconds())+"Z";b.appendChild(Lp.createTextNode(d))}),magvar:V(Kq),geoidheight:V(Kq),name:V(Jq),cmt:V(Jq),desc:V(Jq),src:V(Jq),link:V(mr),sym:V(Jq),type:V(Jq),fix:V(Jq),sat:V(Lq), -hdop:V(Kq),vdop:V(Kq),pdop:V(Kq),ageofdgpsdata:V(Kq),dgpsid:V(Lq)}),yr={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function zr(b,c){var d=b.N();if(m(d))return Op(c[c.length-1].node.namespaceURI,yr[d.H()])} -var Ar=oq(Wq,{rte:V(function(b,c,d){var e=d[0],f=c.L();b={node:b,properties:f};c=c.N();m(c)&&(c=xp(c,!0,e),b.geometryLayout=c.a,e=c.K(),f.rtept=e);e=sr[d[d.length-1].node.namespaceURI];f=tq(f,e);vq(b,tr,sq,f,d,e)}),trk:V(function(b,c,d){var e=d[0],f=c.L();b={node:b,properties:f};c=c.N();m(c)&&(c=xp(c,!0,e),e=c.Ec(),f.trkseg=e);e=ur[d[d.length-1].node.namespaceURI];f=tq(f,e);vq(b,xr,sq,f,d,e)}),wpt:V(function(b,c,d){var e=d[0],f=d[d.length-1],g=c.L();f.properties=g;c=c.N();m(c)&&(c=xp(c,!0,e),f.geometryLayout= -c.a,pr(b,c.K(),d))})});Vq.prototype.a=function(b,c){c=wp(this,c);var d=Op("http://www.topografix.com/GPX/1/1","gpx");vq({node:d},Ar,zr,b,[c]);return d};function Br(b){b=Cr(b);return Va(b,function(b){return b.b.substring(b.c,b.a)})}function Dr(b,c,d){this.b=b;this.c=c;this.a=d}function Cr(b){for(var c=RegExp("\r\n|\r|\n","g"),d=0,e,f=[];e=c.exec(b);)d=new Dr(b,d,e.index),f.push(d),d=c.lastIndex;d<b.length&&(d=new Dr(b,d,b.length),f.push(d));return f};function Er(){this.defaultDataProjection=null}u(Er,up);l=Er.prototype;l.H=function(){return"text"};l.Nb=function(b,c){return this.Ic(ia(b)?b:"",wp(this,c))};l.ja=function(b,c){return this.ze(ia(b)?b:"",wp(this,c))};l.Jc=function(b,c){return this.Kc(ia(b)?b:"",wp(this,c))};l.Ba=function(b){return this.Ce(ia(b)?b:"")};l.Pd=function(b,c){return this.Qd(b,wp(this,c))};l.Qb=function(b,c){return this.ig(b,wp(this,c))};l.Qc=function(b,c){return this.Rc(b,wp(this,c))};function Fr(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=m(b.altitudeMode)?b.altitudeMode:"none"}u(Fr,Er);var Gr=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,Hr=/^H.([A-Z]{3}).*?:(.*)/,Ir=/^HFDTE(\d{2})(\d{2})(\d{2})/; -Fr.prototype.Ic=function(b,c){var d=this.a,e=Br(b),f={},g=[],h=2E3,k=0,n=1,p,q;p=0;for(q=e.length;p<q;++p){var r=e[p],s;if("B"==r.charAt(0)){if(s=Gr.exec(r)){var r=parseInt(s[1],10),v=parseInt(s[2],10),y=parseInt(s[3],10),C=parseInt(s[4],10)+parseInt(s[5],10)/6E4;"S"==s[6]&&(C=-C);var F=parseInt(s[7],10)+parseInt(s[8],10)/6E4;"W"==s[9]&&(F=-F);g.push(F,C);"none"!=d&&g.push("gps"==d?parseInt(s[11],10):"barometric"==d?parseInt(s[12],10):0);g.push(Date.UTC(h,k,n,r,v,y)/1E3)}}else if("H"==r.charAt(0))if(s= -Ir.exec(r))n=parseInt(s[1],10),k=parseInt(s[2],10)-1,h=2E3+parseInt(s[3],10);else if(s=Hr.exec(r))f[s[1]]=Aa(s[2]),Ir.exec(r)}if(0===g.length)return null;e=new M(null);qn(e,"none"==d?"XYM":"XYZM",g);d=new R(xp(e,!1,c));d.G(f);return d};Fr.prototype.ze=function(b,c){var d=this.Ic(b,c);return null===d?[]:[d]};Fr.prototype.Ce=function(){return this.defaultDataProjection};function Jr(b){b=m(b)?b:{};this.d=b.font;this.e=b.rotation;this.c=b.scale;this.b=b.text;this.g=b.textAlign;this.o=b.textBaseline;this.a=m(b.fill)?b.fill:null;this.f=m(b.stroke)?b.stroke:null;this.i=m(b.offsetX)?b.offsetX:0;this.n=m(b.offsetY)?b.offsetY:0}l=Jr.prototype;l.kh=function(){return this.d};l.yh=function(){return this.i};l.zh=function(){return this.n};l.dk=function(){return this.a};l.ek=function(){return this.e};l.fk=function(){return this.c};l.gk=function(){return this.f};l.hk=function(){return this.b}; -l.Hh=function(){return this.g};l.Ih=function(){return this.o};l.dl=function(b){this.d=b};l.cl=function(b){this.a=b};l.ik=function(b){this.e=b};l.jk=function(b){this.c=b};l.kl=function(b){this.f=b};l.ll=function(b){this.b=b};l.ml=function(b){this.g=b};l.nl=function(b){this.o=b};function Kr(b){function c(b){return ga(b)?b:ia(b)?(!(b in e)&&"#"+b in e&&(b="#"+b),c(e[b])):d}b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");var d=m(b.defaultStyle)?b.defaultStyle:Lr,e={};this.b=m(b.extractStyles)?b.extractStyles:!0;this.c=e;this.d=function(){var b=this.get("Style");if(m(b))return b;b=this.get("styleUrl");return m(b)?c(b):d}}u(Kr,wq); -var Mr=["http://www.google.com/kml/ext/2.2"],Nr=[null,"http://earth.google.com/kml/2.0","http://earth.google.com/kml/2.1","http://earth.google.com/kml/2.2","http://www.opengis.net/kml/2.2"],Or=[255,255,255,1],Pr=new Rl({color:Or}),Qr=[2,20],Rr=[32,32],Sr=new Sj({anchor:Qr,anchorXUnits:"pixels",anchorYUnits:"pixels",crossOrigin:"anonymous",rotation:0,scale:1,size:Rr,src:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"}),Tr=new Nl({color:Or,width:1}),Ur=new Jr({font:"normal 16px Helvetica", -fill:Pr,stroke:Tr,scale:1}),Lr=[new Tl({fill:Pr,image:Sr,text:Ur,stroke:Tr,zIndex:0})],Vr={fraction:"fraction",pixels:"pixels"};function Wr(b){b=Pp(b,!1);if(b=/^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(b))return b=b[1],[parseInt(b.substr(6,2),16),parseInt(b.substr(4,2),16),parseInt(b.substr(2,2),16),parseInt(b.substr(0,2),16)/255]} -function Xr(b){b=Pp(b,!1);for(var c=[],d=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i,e;e=d.exec(b);)c.push(parseFloat(e[1]),parseFloat(e[2]),e[3]?parseFloat(e[3]):0),b=b.substr(e[0].length);return""!==b?void 0:c}function Yr(b){var c=Pp(b,!1);return null!=b.baseURI?Ph(b.baseURI,Aa(c)).toString():Aa(c)}function Zr(b){b=Dq(b);if(m(b))return Math.sqrt(b)}function $r(b,c){return W(null,as,b,c)} -function bs(b,c){var d=W({k:[],gg:[]},cs,b,c);if(m(d)){var e=d.k,d=d.gg,f,g;f=0;for(g=Math.min(e.length,d.length);f<g;++f)e[4*f+3]=d[f];d=new M(null);qn(d,"XYZM",e);return d}}function ds(b,c){var d=W(null,es,b,c);if(m(d)){var e=new M(null);qn(e,"XYZ",d);return e}}function fs(b,c){var d=W(null,es,b,c);if(m(d)){var e=new H(null);wl(e,"XYZ",d,[d.length]);return e}} -function gs(b,c){var d=W([],hs,b,c);if(!m(d))return null;if(0===d.length)return new jn(d);var e=!0,f=d[0].H(),g,h,k;h=1;for(k=d.length;h<k;++h)if(g=d[h],g.H()!=f){e=!1;break}if(e){if("Point"==f){g=d[0];e=g.a;f=g.k;h=1;for(k=d.length;h<k;++h)g=d[h],db(f,g.k);d=new un(null);Qk(d,e,f);d.l();return d}return"LineString"==f?(g=new rn(null),tn(g,d),g):"Polygon"==f?(g=new vn(null),xn(g,d),g):"GeometryCollection"==f?new jn(d):null}return new jn(d)} -function is(b,c){var d=W(null,es,b,c);if(null!=d){var e=new jl(null);kl(e,"XYZ",d);return e}}function js(b,c){var d=W([null],ks,b,c);if(null!=d&&null!==d[0]){var e=new H(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)db(f,d[h]),g.push(f.length);wl(e,"XYZ",f,g);return e}} -function ls(b,c){var d=W({},ms,b,c);if(!m(d))return null;var e=x(d,"fillStyle",Pr),f=x(d,"fill");m(f)&&!f&&(e=null);var f=x(d,"imageStyle",Sr),g=x(d,"textStyle",Ur),h=x(d,"strokeStyle",Tr),d=x(d,"outline");m(d)&&!d&&(h=null);return[new Tl({fill:e,image:f,stroke:h,text:g,zIndex:void 0})]} -var ns=nq(Nr,{value:lq(Hq)}),ps=nq(Nr,{Data:function(b,c){var d=b.getAttribute("name");if(null!==d){var e=W(void 0,ns,b,c);m(e)&&(c[c.length-1][d]=e)}},SchemaData:function(b,c){uq(os,b,c)}}),as=nq(Nr,{coordinates:lq(Xr)}),ks=nq(Nr,{innerBoundaryIs:function(b,c){var d=W(void 0,qs,b,c);m(d)&&c[c.length-1].push(d)},outerBoundaryIs:function(b,c){var d=W(void 0,rs,b,c);m(d)&&(c[c.length-1][0]=d)}}),cs=nq(Nr,{when:function(b,c){var d=c[c.length-1].gg,e=Pp(b,!1);if(e=/^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/.exec(e)){var f= -Date.UTC(parseInt(e[1],10),m(e[3])?parseInt(e[3],10)-1:0,m(e[5])?parseInt(e[5],10):1,m(e[7])?parseInt(e[7],10):0,m(e[8])?parseInt(e[8],10):0,m(e[9])?parseInt(e[9],10):0);if(m(e[10])&&"Z"!=e[10]){var g="-"==e[11]?-1:1,f=f+60*g*parseInt(e[12],10);m(e[13])&&(f+=3600*g*parseInt(e[13],10))}d.push(f)}else d.push(0)}},nq(Mr,{coord:function(b,c){var d=c[c.length-1].k,e=Pp(b,!1);(e=/^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i.exec(e))? -d.push(parseFloat(e[1]),parseFloat(e[2]),parseFloat(e[3]),0):d.push(0,0,0,0)}})),es=nq(Nr,{coordinates:lq(Xr)}),ss=nq(Nr,{href:S(Yr)},nq(Mr,{x:S(Dq),y:S(Dq),w:S(Dq),h:S(Dq)})),ts=nq(Nr,{Icon:S(function(b,c){var d=W({},ss,b,c);return m(d)?d:null}),heading:S(Dq),hotSpot:S(function(b){var c=b.getAttribute("xunits"),d=b.getAttribute("yunits");return{x:parseFloat(b.getAttribute("x")),Je:Vr[c],y:parseFloat(b.getAttribute("y")),Ke:Vr[d]}}),scale:S(Zr)}),qs=nq(Nr,{LinearRing:lq($r)}),us=nq(Nr,{color:S(Wr), -scale:S(Zr)}),vs=nq(Nr,{color:S(Wr),width:S(Dq)}),hs=nq(Nr,{LineString:kq(ds),LinearRing:kq(fs),MultiGeometry:kq(gs),Point:kq(is),Polygon:kq(js)}),ws=nq(Mr,{Track:kq(bs)}),rs=nq(Nr,{LinearRing:lq($r)}),xs=nq(Nr,{Style:S(ls),key:S(Hq),styleUrl:S(function(b){var c=Aa(Pp(b,!1));return null!=b.baseURI?Ph(b.baseURI,c).toString():c})}),zs=nq(Nr,{ExtendedData:function(b,c){uq(ps,b,c)},MultiGeometry:S(gs,"geometry"),LineString:S(ds,"geometry"),LinearRing:S(fs,"geometry"),Point:S(is,"geometry"),Polygon:S(js, -"geometry"),Style:S(ls),StyleMap:function(b,c){var d=W(void 0,ys,b,c);if(m(d)){var e=c[c.length-1];ga(d)?e.Style=d:ia(d)&&(e.styleUrl=d)}},address:S(Hq),description:S(Hq),name:S(Hq),open:S(Aq),phoneNumber:S(Hq),styleUrl:S(Yr),visibility:S(Aq)},nq(Mr,{MultiTrack:S(function(b,c){var d=W([],ws,b,c);if(m(d)){var e=new rn(null);tn(e,d);return e}},"geometry"),Track:S(bs,"geometry")})),As=nq(Nr,{color:S(Wr),fill:S(Aq),outline:S(Aq)}),os=nq(Nr,{SimpleData:function(b,c){var d=b.getAttribute("name");if(null!== -d){var e=Hq(b);c[c.length-1][d]=e}}}),ms=nq(Nr,{IconStyle:function(b,c){var d=W({},ts,b,c);if(m(d)){var e=c[c.length-1],f=x(d,"Icon",{}),g;g=x(f,"href");g=m(g)?g:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";var h,k,n,p=x(d,"hotSpot");m(p)?(h=[p.x,p.y],k=p.Je,n=p.Ke):"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"===g?(h=Qr,n=k="pixels"):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(g)&&(h=[.5,0],n=k="fraction");var q,p=x(f,"x"),r=x(f,"y");m(p)&&m(r)&&(q=[p,r]); -var s,p=x(f,"w"),f=x(f,"h");m(p)&&m(f)&&(s=[p,f]);var v,f=x(d,"heading");m(f)&&(v=bc(f));d=x(d,"scale");"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"==g&&(s=Rr);h=new Sj({anchor:h,anchorOrigin:"bottom-left",anchorXUnits:k,anchorYUnits:n,crossOrigin:"anonymous",offset:q,offsetOrigin:"bottom-left",rotation:v,scale:d,size:s,src:g});e.imageStyle=h}},LabelStyle:function(b,c){var d=W({},us,b,c);m(d)&&(c[c.length-1].textStyle=new Jr({fill:new Rl({color:x(d,"color",Or)}),scale:x(d,"scale")}))}, -LineStyle:function(b,c){var d=W({},vs,b,c);m(d)&&(c[c.length-1].strokeStyle=new Nl({color:x(d,"color",Or),width:x(d,"width",1)}))},PolyStyle:function(b,c){var d=W({},As,b,c);if(m(d)){var e=c[c.length-1];e.fillStyle=new Rl({color:x(d,"color",Or)});var f=x(d,"fill");m(f)&&(e.fill=f);d=x(d,"outline");m(d)&&(e.outline=d)}}}),ys=nq(Nr,{Pair:function(b,c){var d=W({},xs,b,c);if(m(d)){var e=x(d,"key");m(e)&&"normal"==e&&(e=x(d,"styleUrl"),m(e)&&(c[c.length-1]=e),d=x(d,"Style"),m(d)&&(c[c.length-1]=d))}}}); -l=Kr.prototype;l.Of=function(b,c){Tp(b);var d=nq(Nr,{Folder:jq(this.Of,this),Placemark:kq(this.Be,this),Style:sa(this.Sk,this),StyleMap:sa(this.Rk,this)}),d=W([],d,b,c,this);if(m(d))return d};l.Be=function(b,c){var d=W({geometry:null},zs,b,c);if(m(d)){var e=new R,f=b.getAttribute("id");null===f||e.d(f);f=c[0];null!=d.geometry&&xp(d.geometry,!1,f);e.G(d);this.b&&e.i(this.d);return e}}; -l.Sk=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=ls(b,c);m(e)&&(d=null!=b.baseURI?Ph(b.baseURI,"#"+d).toString():"#"+d,this.c[d]=e)}};l.Rk=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=W(void 0,ys,b,c);m(e)&&(d=null!=b.baseURI?Ph(b.baseURI,"#"+d).toString():"#"+d,this.c[d]=e)}};l.Pf=function(b,c){if(!Za(Nr,b.namespaceURI))return null;var d=this.Be(b,[vp(this,b,c)]);return m(d)?d:null}; -l.Ob=function(b,c){if(!Za(Nr,b.namespaceURI))return[];var d;d=Tp(b);if("Document"==d||"Folder"==d)return d=this.Of(b,[vp(this,b,c)]),m(d)?d:[];if("Placemark"==d)return d=this.Be(b,[vp(this,b,c)]),m(d)?[d]:[];if("kml"==d){d=[];var e;for(e=b.firstElementChild;null!==e;e=e.nextElementSibling){var f=this.Ob(e,c);m(f)&&db(d,f)}return d}return[]};l.Nk=function(b){if(Wp(b))return Bs(this,b);if(Zp(b))return Cs(this,b);if(ia(b))return b=iq(b),Bs(this,b)}; -function Bs(b,c){var d;for(d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType){var e=Cs(b,d);if(m(e))return e}}function Cs(b,c){var d;for(d=c.firstElementChild;null!==d;d=d.nextElementSibling)if(Za(Nr,d.namespaceURI)&&"name"==d.localName)return Hq(d);for(d=c.firstElementChild;null!==d;d=d.nextElementSibling){var e=Tp(d);if(Za(Nr,d.namespaceURI)&&("Document"==e||"Folder"==e||"Placemark"==e||"kml"==e)&&(e=Cs(b,d),m(e)))return e}}l.Lc=function(){return this.defaultDataProjection};l.tc=function(){return this.defaultDataProjection}; -function Ds(b,c){var d=ug(c),d=[255*(4==d.length?d[3]:1),d[2],d[1],d[0]],e;for(e=0;4>e;++e){var f=parseInt(d[e],10).toString(16);d[e]=1==f.length?"0"+f:f}Jq(b,d.join(""))}function Es(b,c,d){vq({node:b},Fs,Gs,[c],d)} -function Hs(b,c,d){var e={node:b};null!=c.X&&b.setAttribute("id",c.X);b=c.L();var f=c.a;m(f)&&(f=f.call(c,0),null!==f&&0<f.length&&(b.Style=f[0],f=f[0].c,null!==f&&(b.name=f.b)));f=Is[d[d.length-1].node.namespaceURI];b=tq(b,f);vq(e,Js,sq,b,d,f);b=d[0];c=c.N();null!=c&&(c=xp(c,!0,b));vq(e,Js,Ks,[c],d)}function Ls(b,c,d){var e=c.k;b={node:b};b.layout=c.a;b.stride=c.s;vq(b,Ms,Ns,[e],d)}function Os(b,c,d){c=c.dd();var e=c.shift();b={node:b};vq(b,Ps,Qs,c,d);vq(b,Ps,Rs,[e],d)} -function Ss(b,c){Kq(b,c*c)} -var Ts=oq(Nr,["Document","Placemark"]),Ws=oq(Nr,{Document:V(function(b,c,d){vq({node:b},Us,Vs,c,d)}),Placemark:V(Hs)}),Us=oq(Nr,{Placemark:V(Hs)}),Xs={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry"},Ys=oq(Nr,["href"],oq(Mr,["x","y","w","h"])),Zs=oq(Nr,{href:V(Jq)},oq(Mr,{x:V(Kq),y:V(Kq),w:V(Kq),h:V(Kq)})),$s=oq(Nr,["scale","heading","Icon","hotSpot"]),bt=oq(Nr,{Icon:V(function(b, -c,d){b={node:b};var e=Ys[d[d.length-1].node.namespaceURI],f=tq(c,e);vq(b,Zs,sq,f,d,e);e=Ys[Mr[0]];f=tq(c,e);vq(b,Zs,at,f,d,e)}),heading:V(Kq),hotSpot:V(function(b,c){b.setAttribute("x",c.x);b.setAttribute("y",c.y);b.setAttribute("xunits",c.Je);b.setAttribute("yunits",c.Ke)}),scale:V(Ss)}),ct=oq(Nr,["color","scale"]),dt=oq(Nr,{color:V(Ds),scale:V(Ss)}),et=oq(Nr,["color","width"]),ft=oq(Nr,{color:V(Ds),width:V(Kq)}),Fs=oq(Nr,{LinearRing:V(Ls)}),gt=oq(Nr,{LineString:V(Ls),Point:V(Ls),Polygon:V(Os)}), -Is=oq(Nr,"name open visibility address phoneNumber description styleUrl Style".split(" ")),Js=oq(Nr,{MultiGeometry:V(function(b,c,d){b={node:b};var e=c.H(),f,g;"MultiPoint"==e?(f=c.xd(),g=ht):"MultiLineString"==e?(f=c.Ec(),g=it):"MultiPolygon"==e&&(f=c.gd(),g=jt);vq(b,gt,g,f,d)}),LineString:V(Ls),LinearRing:V(Ls),Point:V(Ls),Polygon:V(Os),Style:V(function(b,c,d){b={node:b};var e={},f=c.e,g=c.b,h=c.f;c=c.c;null!==h&&(e.IconStyle=h);null!==c&&(e.LabelStyle=c);null!==g&&(e.LineStyle=g);null!==f&&(e.PolyStyle= -f);c=kt[d[d.length-1].node.namespaceURI];e=tq(e,c);vq(b,lt,sq,e,d,c)}),address:V(Jq),description:V(Jq),name:V(Jq),open:V(Iq),phoneNumber:V(Jq),styleUrl:V(Jq),visibility:V(Iq)}),Ms=oq(Nr,{coordinates:V(function(b,c,d){d=d[d.length-1];var e=x(d,"layout");d=x(d,"stride");var f;"XY"==e||"XYM"==e?f=2:("XYZ"==e||"XYZM"==e)&&(f=3);var g,h=c.length,k="";if(0<h){k+=c[0];for(e=1;e<f;++e)k+=","+c[e];for(g=d;g<h;g+=d)for(k+=" "+c[g],e=1;e<f;++e)k+=","+c[g+e]}Jq(b,k)})}),Ps=oq(Nr,{outerBoundaryIs:V(Es),innerBoundaryIs:V(Es)}), -mt=oq(Nr,{color:V(Ds)}),kt=oq(Nr,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),lt=oq(Nr,{IconStyle:V(function(b,c,d){b={node:b};var e={},f=c.ab(),g=c.cd(),h={href:c.a.f};if(null!==f){h.w=f[0];h.h=f[1];var k=c.tb(),n=c.zb();null!==n&&null!==g&&0!==n[0]&&n[1]!==f[1]&&(h.x=n[0],h.y=g[1]-(n[1]+f[1]));null!==k&&0!==k[0]&&k[1]!==f[1]&&(e.hotSpot={x:k[0],Je:"pixels",y:f[1]-k[1],Ke:"pixels"})}e.Icon=h;f=c.n;1!==f&&(e.scale=f);c=c.i;0!==c&&(e.heading=c);c=$s[d[d.length-1].node.namespaceURI];e=tq(e,c); -vq(b,bt,sq,e,d,c)}),LabelStyle:V(function(b,c,d){b={node:b};var e={},f=c.a;null!==f&&(e.color=f.a);c=c.c;m(c)&&1!==c&&(e.scale=c);c=ct[d[d.length-1].node.namespaceURI];e=tq(e,c);vq(b,dt,sq,e,d,c)}),LineStyle:V(function(b,c,d){b={node:b};var e=et[d[d.length-1].node.namespaceURI];c=tq({color:c.a,width:c.c},e);vq(b,ft,sq,c,d,e)}),PolyStyle:V(function(b,c,d){vq({node:b},mt,nt,[c.a],d)})});function at(b,c,d){return Op(Mr[0],"gx:"+d)} -function Vs(b,c){return Op(c[c.length-1].node.namespaceURI,"Placemark")}function Ks(b,c){if(null!=b)return Op(c[c.length-1].node.namespaceURI,Xs[b.H()])}var nt=qq("color"),Ns=qq("coordinates"),Qs=qq("innerBoundaryIs"),ht=qq("Point"),it=qq("LineString"),Gs=qq("LinearRing"),jt=qq("Polygon"),Rs=qq("outerBoundaryIs"); -Kr.prototype.a=function(b,c){c=wp(this,c);var d=Op(Nr[4],"kml");hq(d,"http://www.w3.org/2000/xmlns/","xmlns:gx",Mr[0]);hq(d,"http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");hq(d,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation","http://www.opengis.net/kml/2.2 https://developers.google.com/kml/schema/kml22gx.xsd");var e={node:d},f={};1<b.length?f.Document=b:1==b.length&&(f.Placemark=b[0]);var g=Ts[d.namespaceURI],f=tq(f,g);vq(e,Ws,sq,f,[c],g); -return d};function ot(){this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326")}u(ot,wq);function pt(b,c){var d=b.getAttribute("k"),e=b.getAttribute("v");c[c.length-1].Oc[d]=e} -var qt=[null],rt=nq(qt,{nd:function(b,c){c[c.length-1].pc.push(b.getAttribute("ref"))},tag:pt}),tt=nq(qt,{node:function(b,c){var d=c[0],e=c[c.length-1],f=b.getAttribute("id"),g=[parseFloat(b.getAttribute("lon")),parseFloat(b.getAttribute("lat"))];e.pf[f]=g;var h=W({Oc:{}},st,b,c);xb(h.Oc)||(g=new jl(g),xp(g,!1,d),d=new R(g),d.d(f),d.G(h.Oc),e.features.push(d))},way:function(b,c){for(var d=c[0],e=b.getAttribute("id"),f=W({pc:[],Oc:{}},rt,b,c),g=c[c.length-1],h=[],k=0,n=f.pc.length;k<n;k++)db(h,x(g.pf, -f.pc[k]));f.pc[0]==f.pc[f.pc.length-1]?(k=new H(null),wl(k,"XY",h,[h.length])):(k=new M(null),qn(k,"XY",h));xp(k,!1,d);d=new R(k);d.d(e);d.G(f.Oc);g.features.push(d)}}),st=nq(qt,{tag:pt});ot.prototype.Ob=function(b,c){var d=vp(this,b,c);return"osm"==b.localName&&(d=W({pf:{},features:[]},tt,b,[d]),m(d.features))?d.features:[]};ot.prototype.Lc=function(){return this.defaultDataProjection};ot.prototype.tc=function(){return this.defaultDataProjection};function ut(b){return b.getAttributeNS("http://www.w3.org/1999/xlink","href")};function vt(){}vt.prototype.a=function(b){return Wp(b)?wt(this,b):Zp(b)?xt(this,b):ia(b)?(b=iq(b),wt(this,b)):null};function yt(b,c,d,e){var f;m(e)?f=m(void 0)?void 0:0:(e=[],f=0);var g,h;for(g=0;g<c;)for(h=b[g++],e[f++]=b[g++],e[f++]=h,h=2;h<d;++h)e[f++]=b[g++];e.length=f};function zt(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee("EPSG:4326");this.a=m(b.factor)?b.factor:1E5}u(zt,Er);function At(b,c,d){d=m(d)?d:1E5;var e,f=Array(c);for(e=0;e<c;++e)f[e]=0;var g,h;g=0;for(h=b.length;g<h;)for(e=0;e<c;++e,++g){var k=b[g],n=k-f[e];f[e]=k;b[g]=n}return Bt(b,d)}function Ct(b,c,d){var e=m(d)?d:1E5,f=Array(c);for(d=0;d<c;++d)f[d]=0;b=Dt(b,e);var g,e=0;for(g=b.length;e<g;)for(d=0;d<c;++d,++e)f[d]+=b[e],b[e]=f[d];return b} -function Bt(b,c){var d=m(c)?c:1E5,e,f;e=0;for(f=b.length;e<f;++e)b[e]=Math.round(b[e]*d);d=0;for(e=b.length;d<e;++d)f=b[d],b[d]=0>f?~(f<<1):f<<1;d="";e=0;for(f=b.length;e<f;++e){for(var g=b[e],h=void 0,k="";32<=g;)h=(32|g&31)+63,k+=String.fromCharCode(h),g>>=5;h=g+63;k+=String.fromCharCode(h);d+=k}return d} -function Dt(b,c){var d=m(c)?c:1E5,e=[],f=0,g=0,h,k;h=0;for(k=b.length;h<k;++h){var n=b.charCodeAt(h)-63,f=f|(n&31)<<g;32>n?(e.push(f),g=f=0):g+=5}f=0;for(g=e.length;f<g;++f)h=e[f],e[f]=h&1?~(h>>1):h>>1;f=0;for(g=e.length;f<g;++f)e[f]/=d;return e}l=zt.prototype;l.Ic=function(b,c){var d=this.Kc(b,c);return new R(d)};l.ze=function(b,c){return[this.Ic(b,c)]};l.Kc=function(b,c){var d=Ct(b,2,this.a);yt(d,d.length,2,d);d=dl(d,0,d.length,2);return xp(new M(d),!1,wp(this,c))};l.Ce=function(){return this.defaultDataProjection}; -l.Qd=function(b,c){var d=b.N();return null!=d?this.Rc(d,c):""};l.ig=function(b,c){return this.Qd(b[0],c)};l.Rc=function(b,c){b=xp(b,!0,wp(this,c));var d=b.k,e=b.s;yt(d,d.length,e,d);return At(d,e,this.a)};function Et(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ee(null!=b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326")}u(Et,Ap);function Ft(b,c){var d=[],e,f,g,h;g=0;for(h=b.length;g<h;++g)e=b[g],0<g&&d.pop(),0<=e?f=c[e]:f=c[~e].slice().reverse(),d.push.apply(d,f);e=0;for(f=d.length;e<f;++e)d[e]=d[e].slice();return d}function Gt(b,c,d,e,f){b=b.geometries;var g=[],h,k;h=0;for(k=b.length;h<k;++h)g[h]=Ht(b[h],c,d,e,f);return g} -function Ht(b,c,d,e,f){var g=b.type,h=It[g];c="Point"===g||"MultiPoint"===g?h(b,d,e):h(b,c);d=new R;d.Ma(xp(c,!1,f));m(b.id)&&d.d(b.id);m(b.properties)&&d.G(b.properties);return d} -Et.prototype.b=function(b,c){if("Topology"==b.type){var d,e=null,f=null;m(b.transform)&&(d=b.transform,e=d.scale,f=d.translate);var g=b.arcs;if(m(d)){d=e;var h=f,k,n;k=0;for(n=g.length;k<n;++k)for(var p=g[k],q=d,r=h,s=0,v=0,y=void 0,C=void 0,F=void 0,C=0,F=p.length;C<F;++C)y=p[C],s+=y[0],v+=y[1],y[0]=s,y[1]=v,Jt(y,q,r)}d=[];h=rb(b.objects);k=0;for(n=h.length;k<n;++k)"GeometryCollection"===h[k].type?(p=h[k],d.push.apply(d,Gt(p,g,e,f,c))):(p=h[k],d.push(Ht(p,g,e,f,c)));return d}return[]}; -function Jt(b,c,d){b[0]=b[0]*c[0]+d[0];b[1]=b[1]*c[1]+d[1]}Et.prototype.Ba=function(){return this.defaultDataProjection}; -var It={Point:function(b,c,d){b=b.coordinates;null===c||null===d||Jt(b,c,d);return new jl(b)},LineString:function(b,c){var d=Ft(b.arcs,c);return new M(d)},Polygon:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=Ft(b.arcs[e],c);return new H(d)},MultiPoint:function(b,c,d){b=b.coordinates;var e,f;if(null!==c&&null!==d)for(e=0,f=b.length;e<f;++e)Jt(b[e],c,d);return new un(b)},MultiLineString:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=Ft(b.arcs[e],c);return new rn(d)}, -MultiPolygon:function(b,c){var d=[],e,f,g,h,k,n;k=0;for(n=b.arcs.length;k<n;++k){e=b.arcs[k];f=[];g=0;for(h=e.length;g<h;++g)f[g]=Ft(e[g],c);d[k]=f}return new vn(d)}};function Kt(b){b=m(b)?b:{};this.e=b.featureType;this.b=b.featureNS;this.c=m(b.gmlFormat)?b.gmlFormat:new X;this.d=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";this.defaultDataProjection=null}u(Kt,wq);Kt.prototype.Ob=function(b,c){var d={featureType:this.e,featureNS:this.b};Eb(d,vp(this,b,m(c)?c:{}));d=W([],this.c.Td,b,[d],this.c);m(d)||(d=[]);return d}; -Kt.prototype.g=function(b){if(Wp(b))return Lt(b);if(Zp(b))return W({},Mt,b,[]);if(ia(b))return b=iq(b),Lt(b)};Kt.prototype.f=function(b){if(Wp(b))return Nt(this,b);if(Zp(b))return Ot(this,b);if(ia(b))return b=iq(b),Nt(this,b)};function Nt(b,c){for(var d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType)return Ot(b,d)}var Pt={"http://www.opengis.net/gml":{boundedBy:S(zq.prototype.Id,"bounds")}}; -function Ot(b,c){var d={},e=Gq(c.getAttribute("numberOfFeatures"));d.numberOfFeatures=e;return W(d,Pt,c,[],b.c)} -var Qt={"http://www.opengis.net/wfs":{totalInserted:S(Fq),totalUpdated:S(Fq),totalDeleted:S(Fq)}},Rt={"http://www.opengis.net/ogc":{FeatureId:kq(function(b){return b.getAttribute("fid")})}},St={"http://www.opengis.net/wfs":{Feature:function(b,c){uq(Rt,b,c)}}},Mt={"http://www.opengis.net/wfs":{TransactionSummary:S(function(b,c){return W({},Qt,b,c)},"transactionSummary"),InsertResults:S(function(b,c){return W([],St,b,c)},"insertIds")}}; -function Lt(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return W({},Mt,b,[])}var Tt={"http://www.opengis.net/wfs":{PropertyName:V(Jq)}};function Ut(b,c){var d=Op("http://www.opengis.net/ogc","Filter"),e=Op("http://www.opengis.net/ogc","FeatureId");d.appendChild(e);e.setAttribute("fid",c);b.appendChild(d)} -var Vt={"http://www.opengis.net/wfs":{Insert:V(function(b,c,d){var e=d[d.length-1],e=Op(x(e,"featureNS"),x(e,"featureType"));b.appendChild(e);X.prototype.hg(e,c,d)}),Update:V(function(b,c,d){var e=d[d.length-1],f=x(e,"featureType"),g=x(e,"featurePrefix"),g=m(g)?g:"feature",h=x(e,"featureNS");b.setAttribute("typeName",g+":"+f);hq(b,"http://www.w3.org/2000/xmlns/","xmlns:"+g,h);f=c.X;if(m(f)){for(var g=c.I(),h=[],k=0,n=g.length;k<n;k++){var p=c.get(g[k]);m(p)&&h.push({name:g[k],value:p})}vq({node:b, -srsName:x(e,"srsName")},Vt,qq("Property"),h,d);Ut(b,f)}}),Delete:V(function(b,c,d){var e=d[d.length-1];d=x(e,"featureType");var f=x(e,"featurePrefix"),f=m(f)?f:"feature",e=x(e,"featureNS");b.setAttribute("typeName",f+":"+d);hq(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,e);c=c.X;m(c)&&Ut(b,c)}),Property:V(function(b,c,d){var e=Op("http://www.opengis.net/wfs","Name");b.appendChild(e);Jq(e,c.name);null!=c.value&&(e=Op("http://www.opengis.net/wfs","Value"),b.appendChild(e),c.value instanceof Mk?X.prototype.Rd(e, -c.value,d):Jq(e,c.value))}),Native:V(function(b,c){m(c.yl)&&b.setAttribute("vendorId",c.yl);m(c.al)&&b.setAttribute("safeToIgnore",c.al);m(c.value)&&Jq(b,c.value)})}},Wt={"http://www.opengis.net/wfs":{Query:V(function(b,c,d){var e=d[d.length-1],f=x(e,"featurePrefix"),g=x(e,"featureNS"),h=x(e,"propertyNames"),k=x(e,"srsName");b.setAttribute("typeName",(m(f)?f+":":"")+c);m(k)&&b.setAttribute("srsName",k);m(g)&&hq(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,g);c=Cb(e);c.node=b;vq(c,Tt,qq("PropertyName"), -h,d);e=x(e,"bbox");m(e)&&(h=Op("http://www.opengis.net/ogc","Filter"),c=x(d[d.length-1],"geometryName"),f=Op("http://www.opengis.net/ogc","BBOX"),h.appendChild(f),g=Op("http://www.opengis.net/ogc","PropertyName"),Jq(g,c),f.appendChild(g),X.prototype.Rd(f,e,d),b.appendChild(h))})}}; -Kt.prototype.n=function(b){var c=Op("http://www.opengis.net/wfs","GetFeature");c.setAttribute("service","WFS");c.setAttribute("version","1.1.0");m(b)&&(m(b.handle)&&c.setAttribute("handle",b.handle),m(b.outputFormat)&&c.setAttribute("outputFormat",b.outputFormat),m(b.maxFeatures)&&c.setAttribute("maxFeatures",b.maxFeatures),m(b.resultType)&&c.setAttribute("resultType",b.resultType),m(b.rl)&&c.setAttribute("startIndex",b.rl),m(b.count)&&c.setAttribute("count",b.count));hq(c,"http://www.w3.org/2001/XMLSchema-instance", -"xsi:schemaLocation",this.d);var d=b.featureTypes;b=[{node:c,srsName:b.srsName,featureNS:m(b.featureNS)?b.featureNS:this.b,featurePrefix:b.featurePrefix,geometryName:b.geometryName,bbox:b.bbox,Mf:m(b.Mf)?b.Mf:[]}];var e=Cb(b[b.length-1]);e.node=c;vq(e,Wt,qq("Query"),d,b);return c}; -Kt.prototype.j=function(b,c,d,e){var f=[],g=Op("http://www.opengis.net/wfs","Transaction");g.setAttribute("service","WFS");g.setAttribute("version","1.1.0");var h,k;m(e)&&(h=m(e.gmlOptions)?e.gmlOptions:{},m(e.handle)&&g.setAttribute("handle",e.handle));hq(g,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.d);null!=b&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Eb(k,h),vq(k,Vt,qq("Insert"),b,f));null!=c&&(k={node:g,featureNS:e.featureNS, -featureType:e.featureType,featurePrefix:e.featurePrefix},Eb(k,h),vq(k,Vt,qq("Update"),c,f));null!=d&&vq({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Vt,qq("Delete"),d,f);m(e.nativeElements)&&vq({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Vt,qq("Native"),e.nativeElements,f);return g};Kt.prototype.Lc=function(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return this.tc(b);return null}; -Kt.prototype.tc=function(b){b=b.firstElementChild.firstElementChild;if(null!=b)for(b=b.firstElementChild;null!==b;b=b.nextElementSibling)if(0!==b.childNodes.length&&(1!==b.childNodes.length||3!==b.firstChild.nodeType)){var c=[{}];this.c.Id(b,c);return Ee(c.pop().srsName)}return null};function Xt(b){b=m(b)?b:{};this.defaultDataProjection=null;this.a=m(b.splitCollection)?b.splitCollection:!1}u(Xt,Er);function Yt(b){b=b.K();return 0==b.length?"":b[0]+" "+b[1]}function Zt(b){b=b.K();for(var c=[],d=0,e=b.length;d<e;++d)c.push(b[d][0]+" "+b[d][1]);return c.join(",")}function $t(b){var c=[];b=b.dd();for(var d=0,e=b.length;d<e;++d)c.push("("+Zt(b[d])+")");return c.join(",")}function au(b){var c=b.H();b=(0,bu[c])(b);c=c.toUpperCase();return 0===b.length?c+" EMPTY":c+"("+b+")"} -var bu={Point:Yt,LineString:Zt,Polygon:$t,MultiPoint:function(b){var c=[];b=b.xd();for(var d=0,e=b.length;d<e;++d)c.push("("+Yt(b[d])+")");return c.join(",")},MultiLineString:function(b){var c=[];b=b.Ec();for(var d=0,e=b.length;d<e;++d)c.push("("+Zt(b[d])+")");return c.join(",")},MultiPolygon:function(b){var c=[];b=b.gd();for(var d=0,e=b.length;d<e;++d)c.push("("+$t(b[d])+")");return c.join(",")},GeometryCollection:function(b){var c=[];b=b.af();for(var d=0,e=b.length;d<e;++d)c.push(au(b[d]));return c.join(",")}}; -l=Xt.prototype;l.Ic=function(b,c){var d=this.Kc(b,c);if(m(d)){var e=new R;e.Ma(d);return e}return null};l.ze=function(b,c){var d=[],e=this.Kc(b,c);this.a&&"GeometryCollection"==e.H()?d=e.d:d=[e];for(var f=[],g=0,h=d.length;g<h;++g)e=new R,e.Ma(d[g]),f.push(e);return f};l.Kc=function(b,c){var d;d=new cu(new du(b));d.a=eu(d.c);d=fu(d);return m(d)?xp(d,!1,c):null};l.Ce=function(){return null};l.Qd=function(b,c){var d=b.N();return m(d)?this.Rc(d,c):""}; -l.ig=function(b,c){if(1==b.length)return this.Qd(b[0],c);for(var d=[],e=0,f=b.length;e<f;++e)d.push(b[e].N());d=new jn(d);return this.Rc(d,c)};l.Rc=function(b,c){return au(xp(b,!0,c))};function du(b){this.c=b;this.a=-1}function gu(b,c){var d=m(c)?c:!1;return"0"<=b&&"9">=b||"."==b&&!d} -function eu(b){var c=b.c.charAt(++b.a),d={position:b.a,value:c};if("("==c)d.type=2;else if(","==c)d.type=5;else if(")"==c)d.type=3;else if(gu(c)||"-"==c){d.type=4;var e,c=b.a,f=!1;do"."==e&&(f=!0),e=b.c.charAt(++b.a);while(gu(e,f));b=parseFloat(b.c.substring(c,b.a--));d.value=b}else if("a"<=c&&"z">=c||"A"<=c&&"Z">=c){d.type=1;c=b.a;do e=b.c.charAt(++b.a);while("a"<=e&&"z">=e||"A"<=e&&"Z">=e);b=b.c.substring(c,b.a--).toUpperCase();d.value=b}else{if(" "==c||"\t"==c||"\r"==c||"\n"==c)return eu(b);if(""=== -c)d.type=6;else throw Error("Unexpected character: "+c);}return d}function cu(b){this.c=b}l=cu.prototype;l.match=function(b){if(b=this.a.type==b)this.a=eu(this.c);return b}; -function fu(b){var c=b.a;if(b.match(1)){var d=c.value;if("GEOMETRYCOLLECTION"==d){a:{if(b.match(2)){c=[];do c.push(fu(b));while(b.match(5));if(b.match(3)){b=c;break a}}else if(hu(b)){b=[];break a}throw Error(iu(b));}return new jn(b)}var e=ju[d],c=ku[d];if(!m(e)||!m(c))throw Error("Invalid geometry type: "+d);b=e.call(b);return new c(b)}throw Error(iu(b));}l.we=function(){if(this.match(2)){var b=lu(this);if(this.match(3))return b}else if(hu(this))return null;throw Error(iu(this));}; -l.ve=function(){if(this.match(2)){var b=mu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};l.xe=function(){if(this.match(2)){var b=nu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};l.wk=function(){if(this.match(2)){var b;if(2==this.a.type)for(b=[this.we()];this.match(5);)b.push(this.we());else b=mu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));}; -l.vk=function(){if(this.match(2)){var b=nu(this);if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};l.xk=function(){if(this.match(2)){for(var b=[this.xe()];this.match(5);)b.push(this.xe());if(this.match(3))return b}else if(hu(this))return[];throw Error(iu(this));};function lu(b){for(var c=[],d=0;2>d;++d){var e=b.a;if(b.match(4))c.push(e.value);else break}if(2==c.length)return c;throw Error(iu(b));}function mu(b){for(var c=[lu(b)];b.match(5);)c.push(lu(b));return c} -function nu(b){for(var c=[b.ve()];b.match(5);)c.push(b.ve());return c}function hu(b){var c=1==b.a.type&&"EMPTY"==b.a.value;c&&(b.a=eu(b.c));return c}function iu(b){return"Unexpected `"+b.a.value+"` at position "+b.a.position+" in `"+b.c.c+"`"}var ku={POINT:jl,LINESTRING:M,POLYGON:H,MULTIPOINT:un,MULTILINESTRING:rn,MULTIPOLYGON:vn},ju={POINT:cu.prototype.we,LINESTRING:cu.prototype.ve,POLYGON:cu.prototype.xe,MULTIPOINT:cu.prototype.wk,MULTILINESTRING:cu.prototype.vk,MULTIPOLYGON:cu.prototype.xk};function ou(){this.version=void 0}u(ou,vt);function wt(b,c){for(var d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType)return xt(b,d);return null}function xt(b,c){b.version=Aa(c.getAttribute("version"));var d=W({version:b.version},pu,c,[]);return m(d)?d:null}function qu(b,c){return W({},ru,b,c)}function su(b,c){return W({},tu,b,c)}function uu(b,c){var d=qu(b,c);if(m(d)){var e=[Gq(b.getAttribute("width")),Gq(b.getAttribute("height"))];d.size=e;return d}}function vu(b,c){return W([],wu,b,c)} -var xu=[null,"http://www.opengis.net/wms"],pu=nq(xu,{Service:S(function(b,c){return W({},yu,b,c)}),Capability:S(function(b,c){return W({},zu,b,c)})}),zu=nq(xu,{Request:S(function(b,c){return W({},Au,b,c)}),Exception:S(function(b,c){return W([],Bu,b,c)}),Layer:S(function(b,c){return W({},Cu,b,c)})}),yu=nq(xu,{Name:S(Hq),Title:S(Hq),Abstract:S(Hq),KeywordList:S(vu),OnlineResource:S(ut),ContactInformation:S(function(b,c){return W({},Du,b,c)}),Fees:S(Hq),AccessConstraints:S(Hq),LayerLimit:S(Fq),MaxWidth:S(Fq), -MaxHeight:S(Fq)}),Du=nq(xu,{ContactPersonPrimary:S(function(b,c){return W({},Eu,b,c)}),ContactPosition:S(Hq),ContactAddress:S(function(b,c){return W({},Fu,b,c)}),ContactVoiceTelephone:S(Hq),ContactFacsimileTelephone:S(Hq),ContactElectronicMailAddress:S(Hq)}),Eu=nq(xu,{ContactPerson:S(Hq),ContactOrganization:S(Hq)}),Fu=nq(xu,{AddressType:S(Hq),Address:S(Hq),City:S(Hq),StateOrProvince:S(Hq),PostCode:S(Hq),Country:S(Hq)}),Bu=nq(xu,{Format:kq(Hq)}),Cu=nq(xu,{Name:S(Hq),Title:S(Hq),Abstract:S(Hq),KeywordList:S(vu), -CRS:mq(Hq),EX_GeographicBoundingBox:S(function(b,c){var d=W({},Gu,b,c);if(m(d)){var e=x(d,"westBoundLongitude"),f=x(d,"southBoundLatitude"),g=x(d,"eastBoundLongitude"),d=x(d,"northBoundLatitude");return m(e)&&m(f)&&m(g)&&m(d)?[e,f,g,d]:void 0}}),BoundingBox:mq(function(b){var c=[Eq(b.getAttribute("minx")),Eq(b.getAttribute("miny")),Eq(b.getAttribute("maxx")),Eq(b.getAttribute("maxy"))],d=[Eq(b.getAttribute("resx")),Eq(b.getAttribute("resy"))];return{crs:b.getAttribute("CRS"),extent:c,res:d}}),Dimension:mq(function(b){return{name:b.getAttribute("name"), -units:b.getAttribute("units"),unitSymbol:b.getAttribute("unitSymbol"),"default":b.getAttribute("default"),multipleValues:Bq(b.getAttribute("multipleValues")),nearestValue:Bq(b.getAttribute("nearestValue")),current:Bq(b.getAttribute("current")),values:Hq(b)}}),Attribution:S(function(b,c){return W({},Hu,b,c)}),AuthorityURL:mq(function(b,c){var d=qu(b,c);if(m(d)){var e=b.getAttribute("name");d.name=e;return d}}),Identifier:mq(Hq),MetadataURL:mq(function(b,c){var d=qu(b,c);if(m(d)){var e=b.getAttribute("type"); -d.type=e;return d}}),DataURL:mq(qu),FeatureListURL:mq(qu),Style:mq(function(b,c){return W({},Iu,b,c)}),MinScaleDenominator:S(Dq),MaxScaleDenominator:S(Dq),Layer:mq(function(b,c){var d=c[c.length-1],e=W({},Cu,b,c);if(m(e)){var f=Bq(b.getAttribute("queryable"));m(f)||(f=x(d,"queryable"));e.queryable=m(f)?f:!1;f=Gq(b.getAttribute("cascaded"));m(f)||(f=x(d,"cascaded"));e.cascaded=f;f=Bq(b.getAttribute("opaque"));m(f)||(f=x(d,"opaque"));e.opaque=m(f)?f:!1;f=Bq(b.getAttribute("noSubsets"));m(f)||(f=x(d, -"noSubsets"));e.noSubsets=m(f)?f:!1;f=Eq(b.getAttribute("fixedWidth"));m(f)||(f=x(d,"fixedWidth"));e.fixedWidth=f;f=Eq(b.getAttribute("fixedHeight"));m(f)||(f=x(d,"fixedHeight"));e.fixedHeight=f;Ta(["Style","CRS","AuthorityURL"],function(b){var c=x(d,b);if(m(c)){var f=Bb(e,b),f=f.concat(c);e[b]=f}});Ta("EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" "),function(b){m(x(e,b))||(e[b]=x(d,b))});return e}})}),Hu=nq(xu,{Title:S(Hq),OnlineResource:S(ut), -LogoURL:S(uu)}),Gu=nq(xu,{westBoundLongitude:S(Dq),eastBoundLongitude:S(Dq),southBoundLatitude:S(Dq),northBoundLatitude:S(Dq)}),Au=nq(xu,{GetCapabilities:S(su),GetMap:S(su),GetFeatureInfo:S(su)}),tu=nq(xu,{Format:mq(Hq),DCPType:mq(function(b,c){return W({},Ju,b,c)})}),Ju=nq(xu,{HTTP:S(function(b,c){return W({},Ku,b,c)})}),Ku=nq(xu,{Get:S(qu),Post:S(qu)}),Iu=nq(xu,{Name:S(Hq),Title:S(Hq),Abstract:S(Hq),LegendURL:mq(uu),StyleSheetURL:S(qu),StyleURL:S(qu)}),ru=nq(xu,{Format:S(Hq),OnlineResource:S(ut)}), -wu=nq(xu,{Keyword:kq(Hq)});function Lu(){this.b="http://mapserver.gis.umn.edu/mapserver";this.c=new Uq;this.defaultDataProjection=null}u(Lu,wq); -function Mu(b,c,d){c.namespaceURI=b.b;var e=Tp(c),f=[];if(0===c.childNodes.length)return f;"msGMLOutput"==e&&Ta(c.childNodes,function(b){if(1===b.nodeType){var c=d[0],e=b.localName,n=new RegExp(Ja("_layer"),""),e=e.replace(n,"")+"_feature";c.featureType=e;c.featureNS=this.b;n={};n[e]=kq(this.c.ye,this.c);c=nq([x(c,"featureNS"),null],n);b.namespaceURI=this.b;b=W([],c,b,d,this.c);m(b)&&db(f,b)}},b);"FeatureCollection"==e&&(b=W([],b.c.Td,c,[{}],b.c),m(b)&&(f=b));return f} -Lu.prototype.Ob=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};m(c)&&Eb(d,vp(this,b,c));return Mu(this,b,[d])};var Nu=new xe(6378137);function Z(b){td.call(this);b=m(b)?b:{};this.a=null;this.e=We;this.d=void 0;z(this,xd("projection"),this.Ui,!1,this);z(this,xd("tracking"),this.Vi,!1,this);m(b.projection)&&this.n(Ee(b.projection));m(b.trackingOptions)&&this.j(b.trackingOptions);this.b(m(b.tracking)?b.tracking:!1)}u(Z,td);l=Z.prototype;l.M=function(){this.b(!1);Z.S.M.call(this)};l.Ui=function(){var b=this.g();null!=b&&(this.e=De(Ee("EPSG:4326"),b),null===this.a||this.set("position",this.e(this.a)))}; -l.Vi=function(){if(fg){var b=this.i();b&&!m(this.d)?this.d=ba.navigator.geolocation.watchPosition(sa(this.Ek,this),sa(this.Fk,this),this.f()):!b&&m(this.d)&&(ba.navigator.geolocation.clearWatch(this.d),this.d=void 0)}}; -l.Ek=function(b){b=b.coords;this.set("accuracy",b.accuracy);this.set("altitude",null===b.altitude?void 0:b.altitude);this.set("altitudeAccuracy",null===b.altitudeAccuracy?void 0:b.altitudeAccuracy);this.set("heading",null===b.heading?void 0:bc(b.heading));null===this.a?this.a=[b.longitude,b.latitude]:(this.a[0]=b.longitude,this.a[1]=b.latitude);var c=this.e(this.a);this.set("position",c);this.set("speed",null===b.speed?void 0:b.speed);b=zl(Nu,this.a,b.accuracy);b.ma(this.e);this.set("accuracyGeometry", -b);this.l()};l.Fk=function(b){b.type="error";this.b(!1);this.dispatchEvent(b)};l.$e=function(){return this.get("accuracy")};Z.prototype.getAccuracy=Z.prototype.$e;Z.prototype.p=function(){return this.get("accuracyGeometry")||null};Z.prototype.getAccuracyGeometry=Z.prototype.p;Z.prototype.q=function(){return this.get("altitude")};Z.prototype.getAltitude=Z.prototype.q;Z.prototype.r=function(){return this.get("altitudeAccuracy")};Z.prototype.getAltitudeAccuracy=Z.prototype.r;Z.prototype.F=function(){return this.get("heading")}; -Z.prototype.getHeading=Z.prototype.F;Z.prototype.J=function(){return this.get("position")};Z.prototype.getPosition=Z.prototype.J;Z.prototype.g=function(){return this.get("projection")};Z.prototype.getProjection=Z.prototype.g;Z.prototype.t=function(){return this.get("speed")};Z.prototype.getSpeed=Z.prototype.t;Z.prototype.i=function(){return this.get("tracking")};Z.prototype.getTracking=Z.prototype.i;Z.prototype.f=function(){return this.get("trackingOptions")};Z.prototype.getTrackingOptions=Z.prototype.f; -Z.prototype.n=function(b){this.set("projection",b)};Z.prototype.setProjection=Z.prototype.n;Z.prototype.b=function(b){this.set("tracking",b)};Z.prototype.setTracking=Z.prototype.b;Z.prototype.j=function(b){this.set("trackingOptions",b)};Z.prototype.setTrackingOptions=Z.prototype.j;function Ou(b,c,d){for(var e=[],f=b(0),g=b(1),h=c(f),k=c(g),n=[g,f],p=[k,h],q=[1,0],r={},s=1E5,v,y,C,F,G;0<--s&&0<q.length;)C=q.pop(),f=n.pop(),h=p.pop(),g=C.toString(),g in r||(e.push(h[0],h[1]),r[g]=!0),F=q.pop(),g=n.pop(),k=p.pop(),G=(C+F)/2,v=b(G),y=c(v),Uk(y[0],y[1],h[0],h[1],k[0],k[1])<d?(e.push(k[0],k[1]),g=F.toString(),r[g]=!0):(q.push(F,G,G,C),p.push(k,y,y,h),n.push(g,v,v,f));return e}function Pu(b,c,d,e,f){var g=Ee("EPSG:4326");return Ou(function(e){return[b,c+(d-c)*e]},Ve(g,e),f)} -function Qu(b,c,d,e,f){var g=Ee("EPSG:4326");return Ou(function(e){return[c+(d-c)*e,b]},Ve(g,e),f)};function Ru(b){b=m(b)?b:{};this.o=this.g=null;this.d=this.b=Infinity;this.f=this.e=-Infinity;this.r=m(b.targetSize)?b.targetSize:100;this.p=m(b.maxLines)?b.maxLines:100;this.a=[];this.c=[];this.q=m(b.strokeStyle)?b.strokeStyle:Su;this.j=this.i=void 0;this.n=null;this.setMap(m(b.map)?b.map:null)}var Su=new Nl({color:"rgba(0,0,0,0.2)"}),Tu=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001]; -function Uu(b,c,d,e,f){var g=f;c=Pu(c,b.e,b.b,b.o,d);g=m(b.a[g])?b.a[g]:new M(null);qn(g,"XY",c);qe(g.D(),e)&&(b.a[f++]=g);return f}function Vu(b,c,d,e,f){var g=f;c=Qu(c,b.f,b.d,b.o,d);g=m(b.c[g])?b.c[g]:new M(null);qn(g,"XY",c);qe(g.D(),e)&&(b.c[f++]=g);return f}l=Ru.prototype;l.Wi=function(){return this.g};l.wh=function(){return this.a};l.Bh=function(){return this.c}; -l.jf=function(b){var c=b.vectorContext,d=b.frameState;b=d.extent;var e=d.viewState,f=e.center,g=e.projection,e=e.resolution,d=d.pixelRatio,d=e*e/(4*d*d);if(null===this.o||!Ue(this.o,g)){var h=g.D(),k=g.d,n=k[2],p=k[1],q=k[0];this.b=k[3];this.d=n;this.e=p;this.f=q;k=Ee("EPSG:4326");this.i=Ve(k,g);this.j=Ve(g,k);this.n=this.j(ke(h));this.o=g}for(var g=this.n[0],h=this.n[1],k=-1,r,p=Math.pow(this.r*e,2),q=[],s=[],e=0,n=Tu.length;e<n;++e){r=Tu[e]/2;q[0]=g-r;q[1]=h-r;s[0]=g+r;s[1]=h+r;this.i(q,q);this.i(s, -s);r=Math.pow(s[0]-q[0],2)+Math.pow(s[1]-q[1],2);if(r<=p)break;k=Tu[e]}e=k;if(-1==e)this.a.length=this.c.length=0;else{g=this.j(f);f=g[0];g=g[1];h=this.p;f=Math.floor(f/e)*e;p=Yb(f,this.f,this.d);n=Uu(this,p,d,b,0);for(k=0;p!=this.f&&k++<h;)p=Math.max(p-e,this.f),n=Uu(this,p,d,b,n);p=Yb(f,this.f,this.d);for(k=0;p!=this.d&&k++<h;)p=Math.min(p+e,this.d),n=Uu(this,p,d,b,n);this.a.length=n;g=Math.floor(g/e)*e;f=Yb(g,this.e,this.b);n=Vu(this,f,d,b,0);for(k=0;f!=this.e&&k++<h;)f=Math.max(f-e,this.e),n= -Vu(this,f,d,b,n);f=Yb(g,this.e,this.b);for(k=0;f!=this.b&&k++<h;)f=Math.min(f+e,this.b),n=Vu(this,f,d,b,n);this.c.length=n}c.wa(null,this.q);b=0;for(d=this.a.length;b<d;++b)f=this.a[b],c.Cb(f,null);b=0;for(d=this.c.length;b<d;++b)f=this.c[b],c.Cb(f,null)};l.setMap=function(b){null!==this.g&&(this.g.v("postcompose",this.jf,this),this.g.render());null!==b&&(b.u("postcompose",this.jf,this),b.render());this.g=b};function Wu(b,c,d,e,f,g,h){tj.call(this,b,c,d,0,e);this.o=f;this.a=new Image;null!==g&&(this.a.crossOrigin=g);this.d={};this.b=null;this.state=0;this.g=h}u(Wu,tj);Wu.prototype.c=function(b){if(m(b)){var c=ma(b);if(c in this.d)return this.d[c];b=xb(this.d)?this.a:this.a.cloneNode(!1);return this.d[c]=b}return this.a};Wu.prototype.i=function(){this.state=3;Ta(this.b,Yc);this.b=null;this.dispatchEvent("change")}; -Wu.prototype.n=function(){m(this.resolution)||(this.resolution=oe(this.extent)/this.a.height);this.state=2;Ta(this.b,Yc);this.b=null;this.dispatchEvent("change")};Wu.prototype.load=function(){0==this.state&&(this.state=1,this.b=[Wc(this.a,"error",this.i,!1,this),Wc(this.a,"load",this.n,!1,this)],this.g(this,this.o))};function Xu(b,c,d,e,f){tj.call(this,b,c,d,2,e);this.a=f}u(Xu,tj);Xu.prototype.c=function(){return this.a};function Yu(b,c,d,e,f){uj.call(this,b,c);this.g=d;this.c=new Image;null!==e&&(this.c.crossOrigin=e);this.d={};this.b=null;this.o=f}u(Yu,uj);l=Yu.prototype;l.Na=function(b){if(m(b)){var c=ma(b);if(c in this.d)return this.d[c];b=xb(this.d)?this.c:this.c.cloneNode(!1);return this.d[c]=b}return this.c};l.mb=function(){return this.g};l.Xi=function(){this.state=3;Ta(this.b,Yc);this.b=null;vj(this)};l.Yi=function(){this.state=this.c.naturalWidth&&this.c.naturalHeight?2:4;Ta(this.b,Yc);this.b=null;vj(this)}; -l.load=function(){0==this.state&&(this.state=1,this.b=[Wc(this.c,"error",this.Xi,!1,this),Wc(this.c,"load",this.Yi,!1,this)],this.o(this,this.g))};function Zu(b,c,d){return function(e,f,g){return d(b,c,e,f,g)}}function $u(){};function av(b,c){jd.call(this);this.a=new ap(this);var d=b;c&&(d=zf(b));this.a.La(d,"dragenter",this.nk);d!=b&&this.a.La(d,"dragover",this.ok);this.a.La(b,"dragover",this.pk);this.a.La(b,"drop",this.qk)}u(av,jd);l=av.prototype;l.Dc=!1;l.M=function(){av.S.M.call(this);this.a.hc()};l.nk=function(b){var c=b.a.dataTransfer;(this.Dc=!(!c||!(c.types&&(Za(c.types,"Files")||Za(c.types,"public.file-url"))||c.files&&0<c.files.length)))&&b.preventDefault()}; -l.ok=function(b){this.Dc&&(b.preventDefault(),b.a.dataTransfer.dropEffect="none")};l.pk=function(b){this.Dc&&(b.preventDefault(),b.lb(),b=b.a.dataTransfer,b.effectAllowed="all",b.dropEffect="copy")};l.qk=function(b){this.Dc&&(b.preventDefault(),b.lb(),b=new zc(b.a),b.type="drop",this.dispatchEvent(b))};function bv(b){b.prototype.then=b.prototype.then;b.prototype.$goog_Thenable=!0}function cv(b){if(!b)return!1;try{return!!b.$goog_Thenable}catch(c){return!1}};function dv(b,c){ev||fv();gv||(ev(),gv=!0);hv.push(new iv(b,c))}var ev;function fv(){if(ba.Promise&&ba.Promise.resolve){var b=ba.Promise.resolve();ev=function(){b.then(jv)}}else ev=function(){$h(jv)}}var gv=!1,hv=[];function jv(){for(;hv.length;){var b=hv;hv=[];for(var c=0;c<b.length;c++){var d=b[c];try{d.a.call(d.c)}catch(e){Zh(e)}}}gv=!1}function iv(b,c){this.a=b;this.c=c};function kv(b,c){this.c=lv;this.f=void 0;this.a=this.b=null;this.d=this.e=!1;try{var d=this;b.call(c,function(b){mv(d,nv,b)},function(b){mv(d,ov,b)})}catch(e){mv(this,ov,e)}}var lv=0,nv=2,ov=3;kv.prototype.then=function(b,c,d){return pv(this,ka(b)?b:null,ka(c)?c:null,d)};bv(kv);kv.prototype.cancel=function(b){this.c==lv&&dv(function(){var c=new qv(b);rv(this,c)},this)}; -function rv(b,c){if(b.c==lv)if(b.b){var d=b.b;if(d.a){for(var e=0,f=-1,g=0,h;h=d.a[g];g++)if(h=h.wc)if(e++,h==b&&(f=g),0<=f&&1<e)break;0<=f&&(d.c==lv&&1==e?rv(d,c):(e=d.a.splice(f,1)[0],sv(d,e,ov,c)))}}else mv(b,ov,c)}function tv(b,c){b.a&&b.a.length||b.c!=nv&&b.c!=ov||uv(b);b.a||(b.a=[]);b.a.push(c)} -function pv(b,c,d,e){var f={wc:null,Gf:null,If:null};f.wc=new kv(function(b,h){f.Gf=c?function(d){try{var f=c.call(e,d);b(f)}catch(p){h(p)}}:b;f.If=d?function(c){try{var f=d.call(e,c);!m(f)&&c instanceof qv?h(c):b(f)}catch(p){h(p)}}:h});f.wc.b=b;tv(b,f);return f.wc}kv.prototype.g=function(b){this.c=lv;mv(this,nv,b)};kv.prototype.o=function(b){this.c=lv;mv(this,ov,b)}; -function mv(b,c,d){if(b.c==lv){if(b==d)c=ov,d=new TypeError("Promise cannot resolve to itself");else{if(cv(d)){b.c=1;d.then(b.g,b.o,b);return}if(la(d))try{var e=d.then;if(ka(e)){vv(b,d,e);return}}catch(f){c=ov,d=f}}b.f=d;b.c=c;uv(b);c!=ov||d instanceof qv||wv(b,d)}}function vv(b,c,d){function e(c){g||(g=!0,b.o(c))}function f(c){g||(g=!0,b.g(c))}b.c=1;var g=!1;try{d.call(c,f,e)}catch(h){e(h)}}function uv(b){b.e||(b.e=!0,dv(b.i,b))} -kv.prototype.i=function(){for(;this.a&&this.a.length;){var b=this.a;this.a=[];for(var c=0;c<b.length;c++)sv(this,b[c],this.c,this.f)}this.e=!1};function sv(b,c,d,e){if(d==nv)c.Gf(e);else{if(c.wc)for(;b&&b.d;b=b.b)b.d=!1;c.If(e)}}function wv(b,c){b.d=!0;dv(function(){b.d&&xv.call(null,c)})}var xv=Zh;function qv(b){xa.call(this,b)}u(qv,xa);qv.prototype.name="cancel";/* +l.Ha=function(b,c){if(null===b)this.b=null;else{var d=b.a;this.b={fillStyle:yg(null===d?El:d)}}if(null===c)this.a=null;else{var d=c.a,e=c.f,f=c.c,g=c.g,h=c.b,k=c.i;this.a={lineCap:m(e)?e:"round",lineDash:null!=f?f:Fl,lineJoin:m(g)?g:"round",lineWidth:this.fa*(m(h)?h:1),miterLimit:m(k)?k:10,strokeStyle:yg(null===d?Gl:d)}}}; +l.hb=function(b){if(null===b)this.i=null;else{var c=b.zb(),d=b.Tb(1),e=b.Eb(),f=b.fb();this.K=c[0];this.U=c[1];this.T=f[1];this.i=d;this.A=b.u;this.xa=e[0];this.Na=e[1];this.Xa=b.G;this.rb=b.B;this.j=b.v;this.sb=b.fa;this.$=f[0]}}; +l.Ia=function(b){if(null===b)this.f="";else{var c=b.a;null===c?this.l=null:(c=c.a,this.l={fillStyle:yg(null===c?El:c)});var d=b.i;if(null===d)this.v=null;else{var c=d.a,e=d.f,f=d.c,g=d.g,h=d.b,d=d.i;this.v={lineCap:m(e)?e:"round",lineDash:null!=f?f:Fl,lineJoin:m(g)?g:"round",lineWidth:m(h)?h:1,miterLimit:m(d)?d:10,strokeStyle:yg(null===c?Gl:c)}}var c=b.f,e=b.B,f=b.v,g=b.g,h=b.b,d=b.c,k=b.j;b=b.l;this.ba={font:m(c)?c:"10px sans-serif",textAlign:m(k)?k:"center",textBaseline:m(b)?b:"middle"};this.f= +m(d)?d:"";this.tb=m(e)?this.fa*e:0;this.ub=m(f)?this.fa*f:0;this.da=m(g)?g:0;this.B=this.fa*(m(h)?h:1)}};var tm={Point:lm.prototype.xb,LineString:lm.prototype.Hb,Polygon:lm.prototype.Zb,MultiPoint:lm.prototype.wb,MultiLineString:lm.prototype.Ec,MultiPolygon:lm.prototype.Fc,GeometryCollection:lm.prototype.Pd,Circle:lm.prototype.Dc};function zm(b){rk.call(this,b);this.K=Ad()}w(zm,rk); +zm.prototype.A=function(b,c,d){Am(this,"precompose",d,b,void 0);var e=this.ud();if(null!==e){var f=c.extent,g=m(f);if(g){var h=b.pixelRatio,k=de(f),n=ce(f),p=be(f),f=ae(f);qk(b.coordinateToPixelMatrix,k,k);qk(b.coordinateToPixelMatrix,n,n);qk(b.coordinateToPixelMatrix,p,p);qk(b.coordinateToPixelMatrix,f,f);d.save();d.beginPath();d.moveTo(k[0]*h,k[1]*h);d.lineTo(n[0]*h,n[1]*h);d.lineTo(p[0]*h,p[1]*h);d.lineTo(f[0]*h,f[1]*h);d.clip()}h=this.kf();k=d.globalAlpha;d.globalAlpha=c.opacity;0===b.viewState.rotation? +(c=h[13],n=e.width*h[0],p=e.height*h[5],d.drawImage(e,0,0,+e.width,+e.height,Math.round(h[12]),Math.round(c),Math.round(n),Math.round(p))):(d.setTransform(h[0],h[1],h[4],h[5],h[12],h[13]),d.drawImage(e,0,0),d.setTransform(1,0,0,1,0,0));d.globalAlpha=k;g&&d.restore()}Am(this,"postcompose",d,b,void 0)};function Am(b,c,d,e,f){var g=b.b;cd(g,c)&&(b=m(f)?f:Bm(b,e,0),b=new lm(d,e.pixelRatio,e.extent,b,e.viewState.rotation),C(g,new kk(c,g,b,e,d,null)),ym(b))} +function Bm(b,c,d){var e=c.viewState,f=c.pixelRatio;return ok(b.K,f*c.size[0]/2,f*c.size[1]/2,f/e.resolution,-f/e.resolution,-e.rotation,-e.center[0]+d,-e.center[1])}function Cm(b,c){var d=[0,0];qk(c,b,d);return d} +var Dm=function(){var b=null,c=null;return function(d){if(null===b){b=Vi(1,1);c=b.createImageData(1,1);var e=c.data;e[0]=42;e[1]=84;e[2]=126;e[3]=255}var e=b.canvas,f=d[0]<=e.width&&d[1]<=e.height;f||(e.width=d[0],e.height=d[1],e=d[0]-1,d=d[1]-1,b.putImageData(c,e,d),d=b.getImageData(e,d,1,1),f=ib(c.data,d.data));return f}}();var Em=["Polygon","LineString","Image","Text"];function Fm(b,c,d){this.Ma=b;this.$=c;this.f=null;this.g=0;this.resolution=d;this.U=this.K=null;this.b=[];this.coordinates=[];this.ea=Ad();this.a=[];this.ba=[];this.qa=Ad()}w(Fm,jk); +function Gm(b,c,d,e,f,g){var h=b.coordinates.length,k=b.df(),n=[c[d],c[d+1]],p=[NaN,NaN],q=!0,r,t,v;for(r=d+f;r<e;r+=f)p[0]=c[r],p[1]=c[r+1],v=Vd(k,p),v!==t?(q&&(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1]),b.coordinates[h++]=p[0],b.coordinates[h++]=p[1],q=!1):1===v?(b.coordinates[h++]=p[0],b.coordinates[h++]=p[1],q=!1):q=!0,n[0]=p[0],n[1]=p[1],t=v;r===d+f&&(b.coordinates[h++]=n[0],b.coordinates[h++]=n[1]);g&&(b.coordinates[h++]=c[d],b.coordinates[h++]=c[d+1]);return h} +function Hm(b,c){b.K=[0,c,0];b.b.push(b.K);b.U=[0,c,0];b.a.push(b.U)} +function Im(b,c,d,e,f,g,h,k,n){var p;pk(e,b.ea)?p=b.ba:(p=Ye(b.coordinates,0,b.coordinates.length,2,e,b.ba),Dd(b.ea,e));e=0;var q=h.length,r=0,t;for(b=b.qa;e<q;){var v=h[e],B,z,E,A;switch(v[0]){case 0:r=v[1];t=ma(r).toString();m(g[t])?e=v[2]:m(n)&&!ie(n,r.Z().R())?e=v[2]:++e;break;case 1:c.beginPath();++e;break;case 2:r=v[1];t=p[r];var y=p[r+1],J=p[r+2]-t,r=p[r+3]-y;c.arc(t,y,Math.sqrt(J*J+r*r),0,2*Math.PI,!0);++e;break;case 3:c.closePath();++e;break;case 4:r=v[1];t=v[2];B=v[3];E=v[4]*d;var L=v[5]* +d,H=v[6];z=v[7];var S=v[8],ta=v[9],y=v[11],J=v[12],Pa=v[13],R=v[14];for(v[10]&&(y+=f);r<t;r+=2){v=p[r]-E;A=p[r+1]-L;Pa&&(v=v+.5|0,A=A+.5|0);if(1!=J||0!==y){var Aa=v+E,fb=A+L;ok(b,Aa,fb,J,J,y,-Aa,-fb);c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13])}Aa=c.globalAlpha;1!=z&&(c.globalAlpha=Aa*z);c.drawImage(B,S,ta,R,H,v,A,R*d,H*d);1!=z&&(c.globalAlpha=Aa);1==J&&0===y||c.setTransform(1,0,0,1,0,0)}++e;break;case 5:r=v[1];t=v[2];E=v[3];L=v[4]*d;H=v[5]*d;y=v[6];J=v[7]*d;B=v[8];for(z=v[9];r<t;r+=2){v=p[r]+ +L;A=p[r+1]+H;if(1!=J||0!==y)ok(b,v,A,J,J,y,-v,-A),c.setTransform(b[0],b[1],b[4],b[5],b[12],b[13]);z&&c.strokeText(E,v,A);B&&c.fillText(E,v,A);1==J&&0===y||c.setTransform(1,0,0,1,0,0)}++e;break;case 6:if(m(k)&&(r=v[1],r=k(r)))return r;++e;break;case 7:c.fill();++e;break;case 8:r=v[1];t=v[2];c.moveTo(p[r],p[r+1]);for(r+=2;r<t;r+=2)c.lineTo(p[r],p[r+1]);++e;break;case 9:c.fillStyle=v[1];++e;break;case 10:r=m(v[7])?v[7]:!0;t=v[2];c.strokeStyle=v[1];c.lineWidth=r?t*d:t;c.lineCap=v[3];c.lineJoin=v[4];c.miterLimit= +v[5];dj&&c.setLineDash(v[6]);++e;break;case 11:c.font=v[1];c.textAlign=v[2];c.textBaseline=v[3];++e;break;case 12:c.stroke();++e;break;default:++e}}}function Jm(b){var c=b.a;c.reverse();var d,e=c.length,f,g,h=-1;for(d=0;d<e;++d)if(f=c[d],g=f[0],6==g)h=d;else if(0==g){f[2]=d;f=b.a;for(g=d;h<g;){var k=f[h];f[h]=f[g];f[g]=k;++h;--g}h=-1}}function Km(b,c){b.K[2]=b.b.length;b.K=null;b.U[2]=b.a.length;b.U=null;var d=[6,c];b.b.push(d);b.a.push(d)}Fm.prototype.qe=ca;Fm.prototype.df=function(){return this.$}; +function Lm(b,c,d){Fm.call(this,b,c,d);this.l=this.T=null;this.C=this.da=this.fa=this.G=this.u=this.A=this.v=this.B=this.j=this.i=this.c=void 0}w(Lm,Fm); +Lm.prototype.xb=function(b,c){if(null!==this.l){Hm(this,c);var d=b.o,e=this.coordinates.length,d=Gm(this,d,0,d.length,b.H,!1);this.b.push([4,e,d,this.l,this.c,this.i,this.j,this.B,this.v,this.A,this.u,this.G,this.fa,this.da,this.C]);this.a.push([4,e,d,this.T,this.c,this.i,this.j,this.B,this.v,this.A,this.u,this.G,this.fa,this.da,this.C]);Km(this,c)}}; +Lm.prototype.wb=function(b,c){if(null!==this.l){Hm(this,c);var d=b.o,e=this.coordinates.length,d=Gm(this,d,0,d.length,b.H,!1);this.b.push([4,e,d,this.l,this.c,this.i,this.j,this.B,this.v,this.A,this.u,this.G,this.fa,this.da,this.C]);this.a.push([4,e,d,this.T,this.c,this.i,this.j,this.B,this.v,this.A,this.u,this.G,this.fa,this.da,this.C]);Km(this,c)}};Lm.prototype.qe=function(){Jm(this);this.i=this.c=void 0;this.l=this.T=null;this.C=this.da=this.G=this.u=this.A=this.v=this.B=this.fa=this.j=void 0}; +Lm.prototype.hb=function(b){var c=b.zb(),d=b.fb(),e=b.ue(1),f=b.Tb(1),g=b.Eb();this.c=c[0];this.i=c[1];this.T=e;this.l=f;this.j=d[1];this.B=b.u;this.v=g[0];this.A=g[1];this.u=b.G;this.G=b.B;this.fa=b.v;this.da=b.fa;this.C=d[0]};function Mm(b,c,d){Fm.call(this,b,c,d);this.c={hd:void 0,cd:void 0,dd:null,ed:void 0,fd:void 0,gd:void 0,qf:0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}w(Mm,Fm); +function Nm(b,c,d,e,f){var g=b.coordinates.length;c=Gm(b,c,d,e,f,!1);g=[8,g,c];b.b.push(g);b.a.push(g);return e}l=Mm.prototype;l.df=function(){null===this.f&&(this.f=Qd(this.$),0<this.g&&Pd(this.f,this.resolution*(this.g+1)/2,this.f));return this.f}; +function Om(b){var c=b.c,d=c.strokeStyle,e=c.lineCap,f=c.lineDash,g=c.lineJoin,h=c.lineWidth,k=c.miterLimit;c.hd==d&&c.cd==e&&ib(c.dd,f)&&c.ed==g&&c.fd==h&&c.gd==k||(c.qf!=b.coordinates.length&&(b.b.push([12]),c.qf=b.coordinates.length),b.b.push([10,d,h,e,g,k,f],[1]),c.hd=d,c.cd=e,c.dd=f,c.ed=g,c.fd=h,c.gd=k)} +l.Hb=function(b,c){var d=this.c,e=d.lineWidth;m(d.strokeStyle)&&m(e)&&(Om(this),Hm(this,c),this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]),d=b.o,Nm(this,d,0,d.length,b.H),this.a.push([12]),Km(this,c))}; +l.Ec=function(b,c){var d=this.c,e=d.lineWidth;if(m(d.strokeStyle)&&m(e)){Om(this);Hm(this,c);this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash],[1]);var d=b.c,e=b.o,f=b.H,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Nm(this,e,g,d[h],f);this.a.push([12]);Km(this,c)}};l.qe=function(){this.c.qf!=this.coordinates.length&&this.b.push([12]);Jm(this);this.c=null}; +l.Ha=function(b,c){var d=c.a;this.c.strokeStyle=yg(null===d?Gl:d);d=c.f;this.c.lineCap=m(d)?d:"round";d=c.c;this.c.lineDash=null===d?Fl:d;d=c.g;this.c.lineJoin=m(d)?d:"round";d=c.b;this.c.lineWidth=m(d)?d:1;d=c.i;this.c.miterLimit=m(d)?d:10;this.c.lineWidth>this.g&&(this.g=this.c.lineWidth,this.f=null)}; +function Pm(b,c,d){Fm.call(this,b,c,d);this.c={eg:void 0,hd:void 0,cd:void 0,dd:null,ed:void 0,fd:void 0,gd:void 0,fillStyle:void 0,strokeStyle:void 0,lineCap:void 0,lineDash:null,lineJoin:void 0,lineWidth:void 0,miterLimit:void 0}}w(Pm,Fm); +function Qm(b,c,d,e,f){var g=b.c,h=[1];b.b.push(h);b.a.push(h);var k,h=0;for(k=e.length;h<k;++h){var n=e[h],p=b.coordinates.length;d=Gm(b,c,d,n,f,!0);d=[8,p,d];p=[3];b.b.push(d,p);b.a.push(d,p);d=n}c=[7];b.a.push(c);m(g.fillStyle)&&b.b.push(c);m(g.strokeStyle)&&(g=[12],b.b.push(g),b.a.push(g));return d}l=Pm.prototype; +l.Dc=function(b,c){var d=this.c,e=d.strokeStyle;if(m(d.fillStyle)||m(e)){Rm(this);Hm(this,c);this.a.push([9,yg(El)]);m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var f=b.o,e=this.coordinates.length;Gm(this,f,0,f.length,b.H,!1);f=[1];e=[2,e];this.b.push(f,e);this.a.push(f,e);e=[7];this.a.push(e);m(d.fillStyle)&&this.b.push(e);m(d.strokeStyle)&&(d=[12],this.b.push(d),this.a.push(d));Km(this,c)}}; +l.Zb=function(b,c){var d=this.c,e=d.strokeStyle;if(m(d.fillStyle)||m(e))Rm(this),Hm(this,c),this.a.push([9,yg(El)]),m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]),d=b.c,e=If(b),Qm(this,e,0,d,b.H),Km(this,c)}; +l.Fc=function(b,c){var d=this.c,e=d.strokeStyle;if(m(d.fillStyle)||m(e)){Rm(this);Hm(this,c);this.a.push([9,yg(El)]);m(d.strokeStyle)&&this.a.push([10,d.strokeStyle,d.lineWidth,d.lineCap,d.lineJoin,d.miterLimit,d.lineDash]);var d=b.c,e=wm(b),f=b.H,g=0,h,k;h=0;for(k=d.length;h<k;++h)g=Qm(this,e,g,d[h],f);Km(this,c)}};l.qe=function(){Jm(this);this.c=null;var b=this.Ma;if(0!==b){var c=this.coordinates,d,e;d=0;for(e=c.length;d<e;++d)c[d]=b*Math.round(c[d]/b)}}; +l.df=function(){null===this.f&&(this.f=Qd(this.$),0<this.g&&Pd(this.f,this.resolution*(this.g+1)/2,this.f));return this.f}; +l.Ha=function(b,c){var d=this.c;if(null===b)d.fillStyle=void 0;else{var e=b.a;d.fillStyle=yg(null===e?El:e)}null===c?(d.strokeStyle=void 0,d.lineCap=void 0,d.lineDash=null,d.lineJoin=void 0,d.lineWidth=void 0,d.miterLimit=void 0):(e=c.a,d.strokeStyle=yg(null===e?Gl:e),e=c.f,d.lineCap=m(e)?e:"round",e=c.c,d.lineDash=null===e?Fl:e.slice(),e=c.g,d.lineJoin=m(e)?e:"round",e=c.b,d.lineWidth=m(e)?e:1,e=c.i,d.miterLimit=m(e)?e:10,d.lineWidth>this.g&&(this.g=d.lineWidth,this.f=null))}; +function Rm(b){var c=b.c,d=c.fillStyle,e=c.strokeStyle,f=c.lineCap,g=c.lineDash,h=c.lineJoin,k=c.lineWidth,n=c.miterLimit;m(d)&&c.eg!=d&&(b.b.push([9,d]),c.eg=c.fillStyle);!m(e)||c.hd==e&&c.cd==f&&c.dd==g&&c.ed==h&&c.fd==k&&c.gd==n||(b.b.push([10,e,k,f,h,n,g]),c.hd=e,c.cd=f,c.dd=g,c.ed=h,c.fd=k,c.gd=n)}function Sm(b,c,d){Fm.call(this,b,c,d);this.da=this.fa=this.G=null;this.l="";this.u=this.A=this.v=this.B=0;this.j=this.i=this.c=null}w(Sm,Fm); +Sm.prototype.yb=function(b,c,d,e,f,g){if(""!==this.l&&null!==this.j&&(null!==this.c||null!==this.i)){if(null!==this.c){f=this.c;var h=this.G;if(null===h||h.fillStyle!=f.fillStyle){var k=[9,f.fillStyle];this.b.push(k);this.a.push(k);null===h?this.G={fillStyle:f.fillStyle}:h.fillStyle=f.fillStyle}}null!==this.i&&(f=this.i,h=this.fa,null===h||h.lineCap!=f.lineCap||h.lineDash!=f.lineDash||h.lineJoin!=f.lineJoin||h.lineWidth!=f.lineWidth||h.miterLimit!=f.miterLimit||h.strokeStyle!=f.strokeStyle)&&(k=[10, +f.strokeStyle,f.lineWidth,f.lineCap,f.lineJoin,f.miterLimit,f.lineDash,!1],this.b.push(k),this.a.push(k),null===h?this.fa={lineCap:f.lineCap,lineDash:f.lineDash,lineJoin:f.lineJoin,lineWidth:f.lineWidth,miterLimit:f.miterLimit,strokeStyle:f.strokeStyle}:(h.lineCap=f.lineCap,h.lineDash=f.lineDash,h.lineJoin=f.lineJoin,h.lineWidth=f.lineWidth,h.miterLimit=f.miterLimit,h.strokeStyle=f.strokeStyle));f=this.j;h=this.da;if(null===h||h.font!=f.font||h.textAlign!=f.textAlign||h.textBaseline!=f.textBaseline)k= +[11,f.font,f.textAlign,f.textBaseline],this.b.push(k),this.a.push(k),null===h?this.da={font:f.font,textAlign:f.textAlign,textBaseline:f.textBaseline}:(h.font=f.font,h.textAlign=f.textAlign,h.textBaseline=f.textBaseline);Hm(this,g);f=this.coordinates.length;b=Gm(this,b,c,d,e,!1);b=[5,f,b,this.l,this.B,this.v,this.A,this.u,null!==this.c,null!==this.i];this.b.push(b);this.a.push(b);Km(this,g)}}; +Sm.prototype.Ia=function(b){if(null===b)this.l="";else{var c=b.a;null===c?this.c=null:(c=c.a,c=yg(null===c?El:c),null===this.c?this.c={fillStyle:c}:this.c.fillStyle=c);var d=b.i;if(null===d)this.i=null;else{var c=d.a,e=d.f,f=d.c,g=d.g,h=d.b,d=d.i,e=m(e)?e:"round",f=null!=f?f.slice():Fl,g=m(g)?g:"round",h=m(h)?h:1,d=m(d)?d:10,c=yg(null===c?Gl:c);if(null===this.i)this.i={lineCap:e,lineDash:f,lineJoin:g,lineWidth:h,miterLimit:d,strokeStyle:c};else{var k=this.i;k.lineCap=e;k.lineDash=f;k.lineJoin=g;k.lineWidth= +h;k.miterLimit=d;k.strokeStyle=c}}var n=b.f,c=b.B,e=b.v,f=b.g,h=b.b,d=b.c,g=b.j,k=b.l;b=m(n)?n:"10px sans-serif";g=m(g)?g:"center";k=m(k)?k:"middle";null===this.j?this.j={font:b,textAlign:g,textBaseline:k}:(n=this.j,n.font=b,n.textAlign=g,n.textBaseline=k);this.l=m(d)?d:"";this.B=m(c)?c:0;this.v=m(e)?e:0;this.A=m(f)?f:0;this.u=m(h)?h:1}};function Tm(b,c,d,e){this.l=b;this.c=c;this.j=d;this.f=e;this.b={};this.g=Vi(1,1);this.i=Ad()} +function Um(b){for(var c in b.b){var d=b.b[c],e;for(e in d)d[e].qe()}}function Vm(b,c,d,e,f,g){var h=b.i;ok(h,.5,.5,1/d,-1/d,-e,-c[0],-c[1]);var k=b.g;k.clearRect(0,0,1,1);var n;m(b.f)&&(n=Ld(),Md(n,c),Pd(n,d*b.f,n));return Wm(b,k,h,e,f,function(b){if(0<k.getImageData(0,0,1,1).data[3]){if(b=g(b))return b;k.clearRect(0,0,1,1)}},n)}Tm.prototype.a=function(b,c){var d=m(b)?b.toString():"0",e=this.b[d];m(e)||(e={},this.b[d]=e);d=e[c];m(d)||(d=new Xm[c](this.l,this.c,this.j),e[c]=d);return d}; +Tm.prototype.wa=function(){return xb(this.b)};function Ym(b,c,d,e,f,g){var h=Ua(tb(b.b),Number);gb(h);var k=b.c,n=k[0],p=k[1],q=k[2],k=k[3],n=[n,p,n,k,q,k,q,p];Ye(n,0,8,2,e,n);c.save();c.beginPath();c.moveTo(n[0],n[1]);c.lineTo(n[2],n[3]);c.lineTo(n[4],n[5]);c.lineTo(n[6],n[7]);c.closePath();c.clip();for(var r,t,n=0,p=h.length;n<p;++n)for(r=b.b[h[n].toString()],q=0,k=Em.length;q<k;++q)t=r[Em[q]],m(t)&&Im(t,c,d,e,f,g,t.b,void 0);c.restore()} +function Wm(b,c,d,e,f,g,h){var k=Ua(tb(b.b),Number);gb(k,function(b,c){return c-b});var n,p,q,r,t;n=0;for(p=k.length;n<p;++n)for(r=b.b[k[n].toString()],q=Em.length-1;0<=q;--q)if(t=r[Em[q]],m(t)&&(t=Im(t,c,1,d,e,f,t.a,g,h)))return t}var Xm={Image:Lm,LineString:Mm,Polygon:Pm,Text:Sm};function Zm(b,c,d){Ze.call(this);this.Lf(b,m(c)?c:0,d)}w(Zm,Ze);l=Zm.prototype;l.clone=function(){var b=new Zm(null);af(b,this.b,this.o.slice());b.s();return b};l.Ya=function(b,c,d,e){var f=this.o;b-=f[0];var g=c-f[1];c=b*b+g*g;if(c<e){if(0===c)for(e=0;e<this.H;++e)d[e]=f[e];else for(e=this.xf()/Math.sqrt(c),d[0]=f[0]+e*b,d[1]=f[1]+e*g,e=2;e<this.H;++e)d[e]=f[e];d.length=this.H;return c}return e};l.ic=function(b,c){var d=this.o,e=b-d[0],d=c-d[1];return e*e+d*d<=$m(this)}; +l.rd=function(){return this.o.slice(0,this.H)};l.Md=function(b){var c=this.o,d=c[this.H]-c[0];return Od(c[0]-d,c[1]-d,c[0]+d,c[1]+d,b)};l.xf=function(){return Math.sqrt($m(this))};function $m(b){var c=b.o[b.H]-b.o[0];b=b.o[b.H+1]-b.o[1];return c*c+b*b}l.V=function(){return"Circle"};l.sa=function(b){var c=this.R();return ie(b,c)?(c=this.rd(),b[0]<=c[0]&&b[2]>=c[0]||b[1]<=c[1]&&b[3]>=c[1]?!0:$d(b,this.Ye,this)):!1}; +l.Al=function(b){var c=this.H,d=b.slice();d[c]=d[0]+(this.o[c]-this.o[0]);var e;for(e=1;e<c;++e)d[c+e]=b[e];af(this,this.b,d);this.s()};l.Lf=function(b,c,d){if(null===b)af(this,"XY",null);else{bf(this,d,b,0);null===this.o&&(this.o=[]);d=this.o;b=mf(d,b);d[b++]=d[0]+c;var e;c=1;for(e=this.H;c<e;++c)d[b++]=d[c];d.length=b}this.s()};l.Bl=function(b){this.o[this.H]=this.o[0]+b;this.s()};function an(b){Xe.call(this);this.f=m(b)?b:null;bn(this)}w(an,Xe);function cn(b){var c=[],d,e;d=0;for(e=b.length;d<e;++d)c.push(b[d].clone());return c}function dn(b){var c,d;if(null!==b.f)for(c=0,d=b.f.length;c<d;++c)Wc(b.f[c],"change",b.s,!1,b)}function bn(b){var c,d;if(null!==b.f)for(c=0,d=b.f.length;c<d;++c)x(b.f[c],"change",b.s,!1,b)}l=an.prototype;l.clone=function(){var b=new an(null);b.Ih(this.f);return b}; +l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;var f=this.f,g,h;g=0;for(h=f.length;g<h;++g)e=f[g].Ya(b,c,d,e);return e};l.ic=function(b,c){var d=this.f,e,f;e=0;for(f=d.length;e<f;++e)if(d[e].ic(b,c))return!0;return!1};l.Md=function(b){Od(Infinity,Infinity,-Infinity,-Infinity,b);for(var c=this.f,d=0,e=c.length;d<e;++d)Yd(b,c[d].R());return b};l.mg=function(){return cn(this.f)}; +l.mf=function(b){this.l!=this.a&&(yb(this.g),this.i=0,this.l=this.a);if(0>b||0!==this.i&&b<this.i)return this;var c=b.toString();if(this.g.hasOwnProperty(c))return this.g[c];var d=[],e=this.f,f=!1,g,h;g=0;for(h=e.length;g<h;++g){var k=e[g],n=k.mf(b);d.push(n);n!==k&&(f=!0)}if(f)return b=new an(null),dn(b),b.f=d,bn(b),b.s(),this.g[c]=b;this.i=b;return this};l.V=function(){return"GeometryCollection"};l.sa=function(b){var c=this.f,d,e;d=0;for(e=c.length;d<e;++d)if(c[d].sa(b))return!0;return!1}; +l.wa=function(){return 0==this.f.length};l.Ih=function(b){b=cn(b);dn(this);this.f=b;bn(this);this.s()};l.va=function(b){var c=this.f,d,e;d=0;for(e=c.length;d<e;++d)c[d].va(b);this.s()};l.Ua=function(b,c){var d=this.f,e,f;e=0;for(f=d.length;e<f;++e)d[e].Ua(b,c);this.s()};l.X=function(){dn(this);an.aa.X.call(this)};function en(b,c,d,e,f){var g=NaN,h=NaN,k=(d-c)/e;if(0!==k)if(1==k)g=b[c],h=b[c+1];else if(2==k)g=.5*b[c]+.5*b[c+e],h=.5*b[c+1]+.5*b[c+e+1];else{var h=b[c],k=b[c+1],n=0,g=[0],p;for(p=c+e;p<d;p+=e){var q=b[p],r=b[p+1],n=n+Math.sqrt((q-h)*(q-h)+(r-k)*(r-k));g.push(n);h=q;k=r}d=.5*n;for(var t,h=hb,k=0,n=g.length;k<n;)p=k+n>>1,q=h(d,g[p]),0<q?k=p+1:(n=p,t=!q);t=t?k:~k;0>t?(d=(d-g[-t-2])/(g[-t-1]-g[-t-2]),c+=(-t-2)*e,g=Yb(b[c],b[c+e],d),h=Yb(b[c+1],b[c+e+1],d)):(g=b[c+t*e],h=b[c+t*e+1])}return null!=f? +(f[0]=g,f[1]=h,f):[g,h]}function fn(b,c,d,e,f,g){if(d==c)return null;if(f<b[c+e-1])return g?(d=b.slice(c,c+e),d[e-1]=f,d):null;if(b[d-1]<f)return g?(d=b.slice(d-e,d),d[e-1]=f,d):null;if(f==b[c+e-1])return b.slice(c,c+e);c/=e;for(d/=e;c<d;)g=c+d>>1,f<b[(g+1)*e-1]?d=g:c=g+1;d=b[c*e-1];if(f==d)return b.slice((c-1)*e,(c-1)*e+e);g=(f-d)/(b[(c+1)*e-1]-d);d=[];var h;for(h=0;h<e-1;++h)d.push(Yb(b[(c-1)*e+h],b[c*e+h],g));d.push(f);return d} +function gn(b,c,d,e,f,g){var h=0;if(g)return fn(b,h,c[c.length-1],d,e,f);if(e<b[d-1])return f?(b=b.slice(0,d),b[d-1]=e,b):null;if(b[b.length-1]<e)return f?(b=b.slice(b.length-d),b[d-1]=e,b):null;f=0;for(g=c.length;f<g;++f){var k=c[f];if(h!=k){if(e<b[h+d-1])break;if(e<=b[k-1])return fn(b,h,k,d,e,!1);h=k}}return null};function N(b,c){Ze.call(this);this.c=null;this.u=this.G=this.j=-1;this.ja(b,c)}w(N,Ze);l=N.prototype;l.Ni=function(b){null===this.o?this.o=b.slice():cb(this.o,b);this.s()};l.clone=function(){var b=new N(null);hn(b,this.b,this.o.slice());return b};l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;this.u!=this.a&&(this.G=Math.sqrt(hf(this.o,0,this.o.length,this.H,0)),this.u=this.a);return kf(this.o,0,this.o.length,this.H,this.G,!1,b,c,d,e)}; +l.bj=function(b,c){return Af(this.o,0,this.o.length,this.H,b,c)};l.Cl=function(b,c){return"XYM"!=this.b&&"XYZM"!=this.b?null:fn(this.o,0,this.o.length,this.H,b,m(c)?c:!1)};l.W=function(){return pf(this.o,0,this.o.length,this.H)};l.Dl=function(){var b=this.o,c=this.H,d=b[0],e=b[1],f=0,g;for(g=0+c;g<this.o.length;g+=c)var h=b[g],k=b[g+1],f=f+Math.sqrt((h-d)*(h-d)+(k-e)*(k-e)),d=h,e=k;return f};function um(b){b.j!=b.a&&(b.c=en(b.o,0,b.o.length,b.H,b.c),b.j=b.a);return b.c} +l.Gc=function(b){var c=[];c.length=rf(this.o,0,this.o.length,this.H,b,c,0);b=new N(null);hn(b,"XY",c);return b};l.V=function(){return"LineString"};l.sa=function(b){return Bf(this.o,0,this.o.length,this.H,b)};l.ja=function(b,c){null===b?hn(this,"XY",null):(bf(this,c,b,1),null===this.o&&(this.o=[]),this.o.length=nf(this.o,0,b,this.H),this.s())};function hn(b,c,d){af(b,c,d);b.s()};function O(b,c){Ze.call(this);this.c=[];this.j=this.u=-1;this.ja(b,c)}w(O,Ze);l=O.prototype;l.Oi=function(b){null===this.o?this.o=b.o.slice():cb(this.o,b.o.slice());this.c.push(this.o.length);this.s()};l.clone=function(){var b=new O(null);jn(b,this.b,this.o.slice(),this.c.slice());return b};l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;this.j!=this.a&&(this.u=Math.sqrt(jf(this.o,0,this.c,this.H,0)),this.j=this.a);return lf(this.o,0,this.c,this.H,this.u,!1,b,c,d,e)}; +l.Fl=function(b,c,d){return"XYM"!=this.b&&"XYZM"!=this.b||0===this.o.length?null:gn(this.o,this.c,this.H,b,m(c)?c:!1,m(d)?d:!1)};l.W=function(){return qf(this.o,0,this.c,this.H)};l.zj=function(b){if(0>b||this.c.length<=b)return null;var c=new N(null);hn(c,this.b,this.o.slice(0===b?0:this.c[b-1],this.c[b]));return c};l.md=function(){var b=this.o,c=this.c,d=this.b,e=[],f=0,g,h;g=0;for(h=c.length;g<h;++g){var k=c[g],n=new N(null);hn(n,d,b.slice(f,k));e.push(n);f=k}return e}; +function vm(b){var c=[],d=b.o,e=0,f=b.c;b=b.H;var g,h;g=0;for(h=f.length;g<h;++g){var k=f[g],e=en(d,e,k,b);cb(c,e);e=k}return c}l.Gc=function(b){var c=[],d=[],e=this.o,f=this.c,g=this.H,h=0,k=0,n,p;n=0;for(p=f.length;n<p;++n){var q=f[n],k=rf(e,h,q,g,b,c,k);d.push(k);h=q}c.length=k;b=new O(null);jn(b,"XY",c,d);return b};l.V=function(){return"MultiLineString"};l.sa=function(b){a:{var c=this.o,d=this.c,e=this.H,f=0,g,h;g=0;for(h=d.length;g<h;++g){if(Bf(c,f,d[g],e,b)){b=!0;break a}f=d[g]}b=!1}return b}; +l.ja=function(b,c){if(null===b)jn(this,"XY",null,this.c);else{bf(this,c,b,2);null===this.o&&(this.o=[]);var d=of(this.o,0,b,this.H,this.c);this.o.length=0===d.length?0:d[d.length-1];this.s()}};function jn(b,c,d,e){af(b,c,d);b.c=e;b.s()}function kn(b,c){var d="XY",e=[],f=[],g,h;g=0;for(h=c.length;g<h;++g){var k=c[g];0===g&&(d=k.b);cb(e,k.o);f.push(e.length)}jn(b,d,e,f)};function ln(b,c){Ze.call(this);this.ja(b,c)}w(ln,Ze);l=ln.prototype;l.Qi=function(b){null===this.o?this.o=b.o.slice():cb(this.o,b.o);this.s()};l.clone=function(){var b=new ln(null);af(b,this.b,this.o.slice());b.s();return b};l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;var f=this.o,g=this.H,h,k,n;h=0;for(k=f.length;h<k;h+=g)if(n=ff(b,c,f[h],f[h+1]),n<e){e=n;for(n=0;n<g;++n)d[n]=f[h+n];d.length=g}return e};l.W=function(){return pf(this.o,0,this.o.length,this.H)}; +l.Ij=function(b){var c=null===this.o?0:this.o.length/this.H;if(0>b||c<=b)return null;c=new D(null);vf(c,this.b,this.o.slice(b*this.H,(b+1)*this.H));return c};l.pe=function(){var b=this.o,c=this.b,d=this.H,e=[],f,g;f=0;for(g=b.length;f<g;f+=d){var h=new D(null);vf(h,c,b.slice(f,f+d));e.push(h)}return e};l.V=function(){return"MultiPoint"};l.sa=function(b){var c=this.o,d=this.H,e,f,g,h;e=0;for(f=c.length;e<f;e+=d)if(g=c[e],h=c[e+1],Td(b,g,h))return!0;return!1}; +l.ja=function(b,c){null===b?af(this,"XY",null):(bf(this,c,b,1),null===this.o&&(this.o=[]),this.o.length=nf(this.o,0,b,this.H));this.s()};function P(b,c){Ze.call(this);this.c=[];this.u=-1;this.G=null;this.U=this.C=this.K=-1;this.j=null;this.ja(b,c)}w(P,Ze);l=P.prototype;l.Ri=function(b){if(null===this.o)this.o=b.o.slice(),b=b.c.slice(),this.c.push();else{var c=this.o.length;cb(this.o,b.o);b=b.c.slice();var d,e;d=0;for(e=b.length;d<e;++d)b[d]+=c}this.c.push(b);this.s()};l.clone=function(){var b=new P(null),c=Eb(this.c);mn(b,this.b,this.o.slice(),c);return b}; +l.Ya=function(b,c,d,e){if(e<Rd(this.R(),b,c))return e;if(this.C!=this.a){var f=this.c,g=0,h=0,k,n;k=0;for(n=f.length;k<n;++k)var p=f[k],h=jf(this.o,g,p,this.H,h),g=p[p.length-1];this.K=Math.sqrt(h);this.C=this.a}f=wm(this);g=this.c;h=this.H;k=this.K;n=0;var p=m(void 0)?void 0:[NaN,NaN],q,r;q=0;for(r=g.length;q<r;++q){var t=g[q];e=lf(f,n,t,h,k,!0,b,c,d,e,p);n=t[t.length-1]}return e}; +l.ic=function(b,c){var d;a:{d=wm(this);var e=this.c,f=0;if(0!==e.length){var g,h;g=0;for(h=e.length;g<h;++g){var k=e[g];if(yf(d,f,k,this.H,b,c)){d=!0;break a}f=k[k.length-1]}}d=!1}return d};l.Gl=function(){var b=wm(this),c=this.c,d=0,e=0,f,g;f=0;for(g=c.length;f<g;++f)var h=c[f],e=e+df(b,d,h,this.H),d=h[h.length-1];return e}; +l.W=function(b){var c;m(b)?(c=wm(this).slice(),Gf(c,this.c,this.H,b)):c=this.o;b=c;c=this.c;var d=this.H,e=0,f=m(void 0)?void 0:[],g=0,h,k;h=0;for(k=c.length;h<k;++h){var n=c[h];f[g++]=qf(b,e,n,d,f[g]);e=n[n.length-1]}f.length=g;return f}; +function xm(b){if(b.u!=b.a){var c=b.o,d=b.c,e=b.H,f=0,g=[],h,k,n=Ld();h=0;for(k=d.length;h<k;++h){var p=d[h],n=Zd(Od(Infinity,Infinity,-Infinity,-Infinity,void 0),c,f,p[0],e);g.push((n[0]+n[2])/2,(n[1]+n[3])/2);f=p[p.length-1]}c=wm(b);d=b.c;e=b.H;f=0;h=[];k=0;for(n=d.length;k<n;++k)p=d[k],h=zf(c,f,p,e,g,2*k,h),f=p[p.length-1];b.G=h;b.u=b.a}return b.G}l.wj=function(){var b=new ln(null),c=xm(this).slice();af(b,"XY",c);b.s();return b}; +function wm(b){if(b.U!=b.a){var c=b.o,d;a:{d=b.c;var e,f;e=0;for(f=d.length;e<f;++e)if(!Ef(c,d[e],b.H,void 0)){d=!1;break a}d=!0}d?b.j=c:(b.j=c.slice(),b.j.length=Gf(b.j,b.c,b.H));b.U=b.a}return b.j}l.Gc=function(b){var c=[],d=[],e=this.o,f=this.c,g=this.H;b=Math.sqrt(b);var h=0,k=0,n,p;n=0;for(p=f.length;n<p;++n){var q=f[n],r=[],k=sf(e,h,q,g,b,c,k,r);d.push(r);h=q[q.length-1]}c.length=k;e=new P(null);mn(e,"XY",c,d);return e}; +l.Kj=function(b){if(0>b||this.c.length<=b)return null;var c;0===b?c=0:(c=this.c[b-1],c=c[c.length-1]);b=this.c[b].slice();var d=b[b.length-1];if(0!==c){var e,f;e=0;for(f=b.length;e<f;++e)b[e]-=c}e=new F(null);Hf(e,this.b,this.o.slice(c,d),b);return e};l.Wd=function(){var b=this.b,c=this.o,d=this.c,e=[],f=0,g,h,k,n;g=0;for(h=d.length;g<h;++g){var p=d[g].slice(),q=p[p.length-1];if(0!==f)for(k=0,n=p.length;k<n;++k)p[k]-=f;k=new F(null);Hf(k,b,c.slice(f,q),p);e.push(k);f=q}return e};l.V=function(){return"MultiPolygon"}; +l.sa=function(b){a:{var c=wm(this),d=this.c,e=this.H,f=0,g,h;g=0;for(h=d.length;g<h;++g){var k=d[g];if(Cf(c,f,k,e,b)){b=!0;break a}f=k[k.length-1]}b=!1}return b};l.ja=function(b,c){if(null===b)mn(this,"XY",null,this.c);else{bf(this,c,b,3);null===this.o&&(this.o=[]);var d=this.o,e=this.H,f=this.c,g=0,f=m(f)?f:[],h=0,k,n;k=0;for(n=b.length;k<n;++k)g=of(d,g,b[k],e,f[h]),f[h++]=g,g=g[g.length-1];f.length=h;0===f.length?this.o.length=0:(d=f[f.length-1],this.o.length=0===d.length?0:d[d.length-1]);this.s()}}; +function mn(b,c,d,e){af(b,c,d);b.c=e;b.s()}function nn(b,c){var d="XY",e=[],f=[],g,h,k;g=0;for(h=c.length;g<h;++g){var n=c[g];0===g&&(d=n.b);var p=e.length;k=n.c;var q,r;q=0;for(r=k.length;q<r;++q)k[q]+=p;cb(e,n.o);f.push(k)}mn(b,d,e,f)};function on(b,c){return ma(b)-ma(c)}function pn(b,c){var d=.5*b/c;return d*d}function qn(b,c,d,e,f,g){var h=!1,k,n;k=d.i;null!==k&&(n=k.wd(),2==n||3==n?k.Of(f,g):(0==n&&k.load(),k.rf(f,g),h=!0));f=(0,d.g)(c);null!=f&&(e=f.mf(e),(0,rn[e.V()])(b,e,d,c));return h} +var rn={Point:function(b,c,d,e){var f=d.i;if(null!==f){if(2!=f.wd())return;var g=b.a(d.a,"Image");g.hb(f);g.xb(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f),b.yb(c.W(),0,2,2,c,e))},LineString:function(b,c,d,e){var f=d.c;if(null!==f){var g=b.a(d.a,"LineString");g.Ha(null,f);g.Hb(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f),b.yb(um(c),0,2,2,c,e))},Polygon:function(b,c,d,e){var f=d.f,g=d.c;if(null!==f||null!==g){var h=b.a(d.a,"Polygon");h.Ha(f,g);h.Zb(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f), +b.yb(Jf(c),0,2,2,c,e))},MultiPoint:function(b,c,d,e){var f=d.i;if(null!==f){if(2!=f.wd())return;var g=b.a(d.a,"Image");g.hb(f);g.wb(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f),d=c.o,b.yb(d,0,d.length,c.H,c,e))},MultiLineString:function(b,c,d,e){var f=d.c;if(null!==f){var g=b.a(d.a,"LineString");g.Ha(null,f);g.Ec(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f),d=vm(c),b.yb(d,0,d.length,2,c,e))},MultiPolygon:function(b,c,d,e){var f=d.f,g=d.c;if(null!==g||null!==f){var h=b.a(d.a,"Polygon");h.Ha(f, +g);h.Fc(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f),d=xm(c),b.yb(d,0,d.length,2,c,e))},GeometryCollection:function(b,c,d,e){c=c.f;var f,g;f=0;for(g=c.length;f<g;++f)(0,rn[c[f].V()])(b,c[f],d,e)},Circle:function(b,c,d,e){var f=d.f,g=d.c;if(null!==f||null!==g){var h=b.a(d.a,"Polygon");h.Ha(f,g);h.Dc(c,e)}f=d.b;null!==f&&(b=b.a(d.a,"Text"),b.Ia(f),b.yb(c.rd(),0,2,2,c,e))}};function sn(b,c,d,e,f,g){this.f=m(g)?g:null;mk.call(this,b,c,d,m(g)?0:2,e);this.c=f;this.b=null}w(sn,mk);sn.prototype.getError=function(){return this.b};sn.prototype.j=function(b){b?(this.b=b,this.state=3):this.state=2;nk(this)};sn.prototype.load=function(){0==this.state&&(this.state=1,nk(this),this.f(ra(this.j,this)))};sn.prototype.a=function(){return this.c};function tn(b){Dh.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state});this.v=m(b.resolutions)?b.resolutions:null}w(tn,Dh);function un(b,c){if(null!==b.v){var d=bc(b.v,c,0);c=b.v[d]}return c}tn.prototype.l=function(b){b=b.target;switch(b.state){case 1:C(this,new vn(wn,b));break;case 2:C(this,new vn(xn,b));break;case 3:C(this,new vn(yn,b))}};function zn(b,c){b.a().src=c}function vn(b,c){sc.call(this,b);this.image=c}w(vn,sc); +var wn="imageloadstart",xn="imageloadend",yn="imageloaderror";function An(b){tn.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions,state:m(b.state)?b.state:void 0});this.$=b.canvasFunction;this.K=null;this.T=0;this.ba=m(b.ratio)?b.ratio:1.5}w(An,tn);An.prototype.mc=function(b,c,d,e){c=un(this,c);var f=this.K;if(null!==f&&this.T==this.a&&f.resolution==c&&f.g==d&&Ud(f.R(),b))return f;b=b.slice();le(b,this.ba);e=this.$(b,c,d,[je(b)/c*d,ge(b)/c*d],e);null===e||(f=new sn(b,c,d,this.g,e));this.K=f;this.T=this.a;return f};function Bn(b){gd.call(this);this.ha=void 0;this.b="geometry";this.g=null;this.c=void 0;this.f=null;x(this,id(this.b),this.be,!1,this);m(b)&&(b instanceof Xe||null===b?this.Sa(b):this.I(b))}w(Bn,gd);l=Bn.prototype;l.clone=function(){var b=new Bn(this.P());b.Sc(this.b);var c=this.Z();null!=c&&b.Sa(c.clone());c=this.g;null===c||b.uf(c);return b};l.Z=function(){return this.get(this.b)};l.tj=function(){return this.ha};l.sj=function(){return this.b};l.al=function(){return this.g};l.bl=function(){return this.c}; +l.cl=function(){this.s()};l.be=function(){null!==this.f&&(Xc(this.f),this.f=null);var b=this.Z();null!=b&&(this.f=x(b,"change",this.cl,!1,this));this.s()};l.Sa=function(b){this.set(this.b,b)};l.uf=function(b){this.g=b;null===b?b=void 0:ka(b)||(b=ga(b)?b:[b],b=ne(b));this.c=b;this.s()};l.Xb=function(b){this.ha=b;this.s()};l.Sc=function(b){Wc(this,id(this.b),this.be,!1,this);this.b=b;x(this,id(this.b),this.be,!1,this);this.be()};function Cn(b){b.prototype.then=b.prototype.then;b.prototype.$goog_Thenable=!0}function Dn(b){if(!b)return!1;try{return!!b.$goog_Thenable}catch(c){return!1}};function En(b,c){Fn||Gn();Hn||(Fn(),Hn=!0);In.push(new Jn(b,c))}var Fn;function Gn(){if(ba.Promise&&ba.Promise.resolve){var b=ba.Promise.resolve();Fn=function(){b.then(Kn)}}else Fn=function(){qi(Kn)}}var Hn=!1,In=[];function Kn(){for(;In.length;){var b=In;In=[];for(var c=0;c<b.length;c++){var d=b[c];try{d.a.call(d.b)}catch(e){pi(e)}}}Hn=!1}function Jn(b,c){this.a=b;this.b=c};function Ln(b,c){this.b=Mn;this.i=void 0;this.a=this.c=null;this.f=this.g=!1;try{var d=this;b.call(c,function(b){Nn(d,On,b)},function(b){Nn(d,Pn,b)})}catch(e){Nn(this,Pn,e)}}var Mn=0,On=2,Pn=3;Ln.prototype.then=function(b,c,d){return Qn(this,ka(b)?b:null,ka(c)?c:null,d)};Cn(Ln);Ln.prototype.cancel=function(b){this.b==Mn&&En(function(){var c=new Rn(b);Sn(this,c)},this)}; +function Sn(b,c){if(b.b==Mn)if(b.c){var d=b.c;if(d.a){for(var e=0,f=-1,g=0,h;h=d.a[g];g++)if(h=h.bd)if(e++,h==b&&(f=g),0<=f&&1<e)break;0<=f&&(d.b==Mn&&1==e?Sn(d,c):(e=d.a.splice(f,1)[0],Tn(d,e,Pn,c)))}}else Nn(b,Pn,c)}function Un(b,c){b.a&&b.a.length||b.b!=On&&b.b!=Pn||Vn(b);b.a||(b.a=[]);b.a.push(c)} +function Qn(b,c,d,e){var f={bd:null,jh:null,lh:null};f.bd=new Ln(function(b,h){f.jh=c?function(d){try{var f=c.call(e,d);b(f)}catch(p){h(p)}}:b;f.lh=d?function(c){try{var f=d.call(e,c);!m(f)&&c instanceof Rn?h(c):b(f)}catch(p){h(p)}}:h});f.bd.c=b;Un(b,f);return f.bd}Ln.prototype.j=function(b){this.b=Mn;Nn(this,On,b)};Ln.prototype.l=function(b){this.b=Mn;Nn(this,Pn,b)}; +function Nn(b,c,d){if(b.b==Mn){if(b==d)c=Pn,d=new TypeError("Promise cannot resolve to itself");else{if(Dn(d)){b.b=1;d.then(b.j,b.l,b);return}if(la(d))try{var e=d.then;if(ka(e)){Wn(b,d,e);return}}catch(f){c=Pn,d=f}}b.i=d;b.b=c;Vn(b);c!=Pn||d instanceof Rn||Xn(b,d)}}function Wn(b,c,d){function e(c){g||(g=!0,b.l(c))}function f(c){g||(g=!0,b.j(c))}b.b=1;var g=!1;try{d.call(c,f,e)}catch(h){e(h)}}function Vn(b){b.g||(b.g=!0,En(b.B,b))} +Ln.prototype.B=function(){for(;this.a&&this.a.length;){var b=this.a;this.a=[];for(var c=0;c<b.length;c++)Tn(this,b[c],this.b,this.i)}this.g=!1};function Tn(b,c,d,e){if(d==On)c.jh(e);else{if(c.bd)for(;b&&b.f;b=b.c)b.f=!1;c.lh(e)}}function Xn(b,c){b.f=!0;En(function(){b.f&&Yn.call(null,c)})}var Yn=pi;function Rn(b){xa.call(this,b)}w(Rn,xa);Rn.prototype.name="cancel";function Zn(b,c,d){if(ka(b))d&&(b=ra(b,d));else if(b&&"function"==typeof b.handleEvent)b=ra(b.handleEvent,b);else throw Error("Invalid listener argument");return 2147483647<c?-1:ba.setTimeout(b,c||0)};var $n=ba.JSON.parse,ao=ba.JSON.stringify;function bo(){}bo.prototype.a=null;function co(b){var c;(c=b.a)||(c={},eo(b)&&(c[0]=!0,c[1]=!0),c=b.a=c);return c};var fo;function go(){}w(go,bo);function ho(b){return(b=eo(b))?new ActiveXObject(b):new XMLHttpRequest}function eo(b){if(!b.b&&"undefined"==typeof XMLHttpRequest&&"undefined"!=typeof ActiveXObject){for(var c=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],d=0;d<c.length;d++){var e=c[d];try{return new ActiveXObject(e),b.b=e}catch(f){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return b.b}fo=new go;var io=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/;function jo(b){if(ko){ko=!1;var c=ba.location;if(c){var d=c.href;if(d&&(d=(d=jo(d)[3]||null)?decodeURI(d):d)&&d!=c.hostname)throw ko=!0,Error();}}return b.match(io)}var ko=Lb; +function lo(b,c){for(var d=b.split("&"),e=0;e<d.length;e++){var f=d[e].indexOf("="),g=null,h=null;0<=f?(g=d[e].substring(0,f),h=d[e].substring(f+1)):g=d[e];c(g,h?decodeURIComponent(h.replace(/\+/g," ")):"")}}function mo(b){if(b[1]){var c=b[0],d=c.indexOf("#");0<=d&&(b.push(c.substr(d)),b[0]=c=c.substr(0,d));d=c.indexOf("?");0>d?b[1]="?":d==c.length-1&&(b[1]=void 0)}return b.join("")} +function no(b,c,d){if(ga(c))for(var e=0;e<c.length;e++)no(b,String(c[e]),d);else null!=c&&d.push("&",b,""===c?"":"=",encodeURIComponent(String(c)))}function oo(b,c){for(var d in c)no(d,c[d],b);return b};function po(b){ad.call(this);this.C=new wi;this.l=b||null;this.a=!1;this.j=this.ga=null;this.g=this.A="";this.b=this.v=this.f=this.B=!1;this.i=0;this.c=null;this.u=qo;this.G=this.K=!1}w(po,ad);var qo="",ro=/^https?$/i,so=["POST","PUT"];l=po.prototype; +l.send=function(b,c,d,e){if(this.ga)throw Error("[goog.net.XhrIo] Object is active with another request="+this.A+"; newUri="+b);c=c?c.toUpperCase():"GET";this.A=b;this.g="";this.B=!1;this.a=!0;this.ga=this.l?ho(this.l):ho(fo);this.j=this.l?co(this.l):co(fo);this.ga.onreadystatechange=ra(this.kh,this);try{this.v=!0,this.ga.open(c,String(b),!0),this.v=!1}catch(f){to(this,f);return}b=d||"";var g=this.C.clone();e&&vi(e,function(b,c){g.set(c,b)});e=Wa(g.O(),uo);d=ba.FormData&&b instanceof ba.FormData; +!Ya(so,c)||e||d||g.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");g.forEach(function(b,c){this.ga.setRequestHeader(c,b)},this);this.u&&(this.ga.responseType=this.u);"withCredentials"in this.ga&&(this.ga.withCredentials=this.K);try{vo(this),0<this.i&&((this.G=wo(this.ga))?(this.ga.timeout=this.i,this.ga.ontimeout=ra(this.xc,this)):this.c=Zn(this.xc,this.i,this)),this.f=!0,this.ga.send(b),this.f=!1}catch(h){to(this,h)}}; +function wo(b){return Jb&&Sb(9)&&ja(b.timeout)&&m(b.ontimeout)}function uo(b){return"content-type"==b.toLowerCase()}l.xc=function(){"undefined"!=typeof aa&&this.ga&&(this.g="Timed out after "+this.i+"ms, aborting",C(this,"timeout"),this.ga&&this.a&&(this.a=!1,this.b=!0,this.ga.abort(),this.b=!1,C(this,"complete"),C(this,"abort"),xo(this)))};function to(b,c){b.a=!1;b.ga&&(b.b=!0,b.ga.abort(),b.b=!1);b.g=c;yo(b);xo(b)}function yo(b){b.B||(b.B=!0,C(b,"complete"),C(b,"error"))} +l.X=function(){this.ga&&(this.a&&(this.a=!1,this.b=!0,this.ga.abort(),this.b=!1),xo(this,!0));po.aa.X.call(this)};l.kh=function(){this.fa||(this.v||this.f||this.b?zo(this):this.sn())};l.sn=function(){zo(this)}; +function zo(b){if(b.a&&"undefined"!=typeof aa&&(!b.j[1]||4!=Ao(b)||2!=Bo(b)))if(b.f&&4==Ao(b))Zn(b.kh,0,b);else if(C(b,"readystatechange"),4==Ao(b)){b.a=!1;try{if(Co(b))C(b,"complete"),C(b,"success");else{var c;try{c=2<Ao(b)?b.ga.statusText:""}catch(d){c=""}b.g=c+" ["+Bo(b)+"]";yo(b)}}finally{xo(b)}}}function xo(b,c){if(b.ga){vo(b);var d=b.ga,e=b.j[0]?ca:null;b.ga=null;b.j=null;c||C(b,"ready");try{d.onreadystatechange=e}catch(f){}}} +function vo(b){b.ga&&b.G&&(b.ga.ontimeout=null);ja(b.c)&&(ba.clearTimeout(b.c),b.c=null)}function Co(b){var c=Bo(b),d;a:switch(c){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:d=!0;break a;default:d=!1}if(!d){if(c=0===c)b=jo(String(b.A))[1]||null,!b&&self.location&&(b=self.location.protocol,b=b.substr(0,b.length-1)),c=!ro.test(b?b.toLowerCase():"");d=c}return d}function Ao(b){return b.ga?b.ga.readyState:0}function Bo(b){try{return 2<Ao(b)?b.ga.status:-1}catch(c){return-1}} +function Do(b){try{return b.ga?b.ga.responseText:""}catch(c){return""}};function Eo(b){if("undefined"!=typeof XMLSerializer)return(new XMLSerializer).serializeToString(b);if(b=b.xml)return b;throw Error("Your browser does not support serializing XML documents");};var Fo;a:if(document.implementation&&document.implementation.createDocument)Fo=document.implementation.createDocument("","",null);else{if("undefined"!=typeof ActiveXObject){var Go=new ActiveXObject("MSXML2.DOMDocument");if(Go){Go.resolveExternals=!1;Go.validateOnParse=!1;try{Go.setProperty("ProhibitDTD",!0),Go.setProperty("MaxXMLSize",2048),Go.setProperty("MaxElementDepth",256)}catch(Ho){}}if(Go){Fo=Go;break a}}throw Error("Your browser does not support creating new documents");}var Io=Fo; +function Jo(b,c){return Io.createElementNS(b,c)}function Ko(b,c){null===b&&(b="");return Io.createNode(1,c,b)}var Lo=document.implementation&&document.implementation.createDocument?Jo:Ko;function Mo(b,c){return No(b,c,[]).join("")}function No(b,c,d){if(4==b.nodeType||3==b.nodeType)c?d.push(String(b.nodeValue).replace(/(\r\n|\r|\n)/g,"")):d.push(b.nodeValue);else for(b=b.firstChild;null!==b;b=b.nextSibling)No(b,c,d);return d}function Oo(b){return b.localName} +function Po(b){var c=b.localName;return m(c)?c:b.baseName}var Qo=Jb?Po:Oo;function Ro(b){return b instanceof Document}function So(b){return la(b)&&9==b.nodeType}var To=Jb?So:Ro;function Uo(b){return b instanceof Node}function Vo(b){return la(b)&&m(b.nodeType)}var Wo=Jb?Vo:Uo;function Xo(b,c,d){return b.getAttributeNS(c,d)||""}function Yo(b,c,d){var e="";b=Zo(b,c,d);m(b)&&(e=b.nodeValue);return e}var $o=document.implementation&&document.implementation.createDocument?Xo:Yo; +function ap(b,c,d){return b.getAttributeNodeNS(c,d)}function bp(b,c,d){var e=null;b=b.attributes;for(var f,g,h=0,k=b.length;h<k;++h)if(f=b[h],f.namespaceURI==c&&(g=f.prefix?f.prefix+":"+d:d,g==f.nodeName)){e=f;break}return e}var Zo=document.implementation&&document.implementation.createDocument?ap:bp;function cp(b,c,d,e){b.setAttributeNS(c,d,e)}function dp(b,c,d,e){null===c?b.setAttribute(d,e):(c=b.ownerDocument.createNode(2,d,c),c.nodeValue=e,b.setAttributeNode(c))} +var ep=document.implementation&&document.implementation.createDocument?cp:dp;function fp(b){return(new DOMParser).parseFromString(b,"application/xml")}function gp(b,c){return function(d,e){var f=b.call(c,d,e);m(f)&&cb(e[e.length-1],f)}}function hp(b,c){return function(d,e){var f=b.call(m(c)?c:this,d,e);m(f)&&e[e.length-1].push(f)}}function ip(b,c){return function(d,e){var f=b.call(m(c)?c:this,d,e);m(f)&&(e[e.length-1]=f)}} +function jp(b){return function(c,d){var e=b.call(m(void 0)?void 0:this,c,d);m(e)&&Bb(d[d.length-1],m(void 0)?void 0:c.localName).push(e)}}function Q(b,c){return function(d,e){var f=b.call(m(void 0)?void 0:this,d,e);m(f)&&(e[e.length-1][m(c)?c:d.localName]=f)}}function T(b,c,d){return kp(b,c,d)}function U(b,c){return function(d,e,f){b.call(m(c)?c:this,d,e,f);f[f.length-1].node.appendChild(d)}} +function lp(b){var c,d;return function(e,f,g){if(!m(c)){c={};var h={};h[e.localName]=b;c[e.namespaceURI]=h;d=mp(e.localName)}np(c,d,f,g)}}function mp(b,c){return function(d,e,f){d=e[e.length-1].node;e=b;m(e)||(e=f);f=c;m(c)||(f=d.namespaceURI);return Lo(f,e)}}var op=mp();function pp(b,c){for(var d=c.length,e=Array(d),f=0;f<d;++f)e[f]=b[c[f]];return e}function kp(b,c,d){d=m(d)?d:{};var e,f;e=0;for(f=b.length;e<f;++e)d[b[e]]=c;return d} +function qp(b,c,d,e){for(c=c.firstElementChild;null!==c;c=c.nextElementSibling){var f=b[c.namespaceURI];m(f)&&(f=f[c.localName],m(f)&&f.call(e,c,d))}}function V(b,c,d,e,f){e.push(b);qp(c,d,e,f);return e.pop()}function np(b,c,d,e,f,g){for(var h=(m(f)?f:d).length,k,n,p=0;p<h;++p)k=d[p],m(k)&&(n=c.call(g,k,e,m(f)?f[p]:void 0),m(n)&&b[n.namespaceURI][n.localName].call(g,n,k,e))}function rp(b,c,d,e,f,g,h){f.push(b);np(c,d,e,f,g,h);f.pop()};function sp(b,c,d){return function(e,f,g){e=new po;e.u="text";x(e,"complete",function(b){b=b.target;if(Co(b)){var e=c.V(),f;if("json"==e)f=Do(b);else if("text"==e)f=Do(b);else if("xml"==e){if(!Jb)try{f=b.ga?b.ga.responseXML:null}catch(p){f=null}null!=f||(f=fp(Do(b)))}null!=f&&(f=c.ra(f,{featureProjection:g}),d.call(this,f))}rc(b)},!1,this);e.send(b)}}function tp(b,c){return sp(b,c,function(b){this.Bc(b)})};function up(){return[[-Infinity,-Infinity,Infinity,Infinity]]};var vp,wp; +(function(){var b={kb:{}};(function(){function c(b,d){if(!(this instanceof c))return new c(b,d);this.Te=Math.max(4,b||9);this.Zf=Math.max(2,Math.ceil(.4*this.Te));d&&this.Gi(d);this.clear()}function d(b,c){b.bbox=e(b,0,b.children.length,c)}function e(b,c,d,e){for(var g=[Infinity,Infinity,-Infinity,-Infinity],h;c<d;c++)h=b.children[c],f(g,b.Ba?e(h):h.bbox);return g}function f(b,c){b[0]=Math.min(b[0],c[0]);b[1]=Math.min(b[1],c[1]);b[2]=Math.max(b[2],c[2]);b[3]=Math.max(b[3],c[3])}function g(b,c){return b.bbox[0]- +c.bbox[0]}function h(b,c){return b.bbox[1]-c.bbox[1]}function k(b){return(b[2]-b[0])*(b[3]-b[1])}function n(b){return b[2]-b[0]+(b[3]-b[1])}function p(b,c){return b[0]<=c[0]&&b[1]<=c[1]&&c[2]<=b[2]&&c[3]<=b[3]}function q(b,c){return c[0]<=b[2]&&c[1]<=b[3]&&c[2]>=b[0]&&c[3]>=b[1]}function r(b,c,d,e,f){for(var g=[c,d],h;g.length;)d=g.pop(),c=g.pop(),d-c<=e||(h=c+Math.ceil((d-c)/e/2)*e,t(b,c,d,h,f),g.push(c,h,h,d))}function t(b,c,d,e,f){for(var g,h,k,n,p;d>c;){600<d-c&&(g=d-c+1,h=e-c+1,k=Math.log(g), +n=.5*Math.exp(2*k/3),p=.5*Math.sqrt(k*n*(g-n)/g)*(0>h-g/2?-1:1),k=Math.max(c,Math.floor(e-h*n/g+p)),h=Math.min(d,Math.floor(e+(g-h)*n/g+p)),t(b,k,h,e,f));g=b[e];h=c;n=d;v(b,c,e);for(0<f(b[d],g)&&v(b,c,d);h<n;){v(b,h,n);h++;for(n--;0>f(b[h],g);)h++;for(;0<f(b[n],g);)n--}0===f(b[c],g)?v(b,c,n):(n++,v(b,n,d));n<=e&&(c=n+1);e<=n&&(d=n-1)}}function v(b,c,d){var e=b[c];b[c]=b[d];b[d]=e}c.prototype={all:function(){return this.Uf(this.data,[])},search:function(b){var c=this.data,d=[],e=this.La;if(!q(b,c.bbox))return d; +for(var f=[],g,h,k,n;c;){g=0;for(h=c.children.length;g<h;g++)k=c.children[g],n=c.Ba?e(k):k.bbox,q(b,n)&&(c.Ba?d.push(k):p(b,n)?this.Uf(k,d):f.push(k));c=f.pop()}return d},load:function(b){if(!b||!b.length)return this;if(b.length<this.Zf){for(var c=0,d=b.length;c<d;c++)this.oa(b[c]);return this}b=this.Wf(b.slice(),0,b.length-1,0);this.data.children.length?this.data.height===b.height?this.ag(this.data,b):(this.data.height<b.height&&(c=this.data,this.data=b,b=c),this.Yf(b,this.data.height-b.height-1, +!0)):this.data=b;return this},oa:function(b){b&&this.Yf(b,this.data.height-1);return this},clear:function(){this.data={children:[],height:1,bbox:[Infinity,Infinity,-Infinity,-Infinity],Ba:!0};return this},remove:function(b){if(!b)return this;for(var c=this.data,d=this.La(b),e=[],f=[],g,h,k,n;c||e.length;){c||(c=e.pop(),h=e[e.length-1],g=f.pop(),n=!0);if(c.Ba&&(k=c.children.indexOf(b),-1!==k)){c.children.splice(k,1);e.push(c);this.Ei(e);break}n||c.Ba||!p(c.bbox,d)?h?(g++,c=h.children[g],n=!1):c=null: +(e.push(c),f.push(g),g=0,h=c,c=c.children[0])}return this},La:function(b){return b},We:function(b,c){return b[0]-c[0]},Xe:function(b,c){return b[1]-c[1]},toJSON:function(){return this.data},Uf:function(b,c){for(var d=[];b;)b.Ba?c.push.apply(c,b.children):d.push.apply(d,b.children),b=d.pop();return c},Wf:function(b,c,e,f){var g=e-c+1,h=this.Te,k;if(g<=h)return k={children:b.slice(c,e+1),height:1,bbox:null,Ba:!0},d(k,this.La),k;f||(f=Math.ceil(Math.log(g)/Math.log(h)),h=Math.ceil(g/Math.pow(h,f-1))); +k={children:[],height:f,bbox:null};var g=Math.ceil(g/h),h=g*Math.ceil(Math.sqrt(h)),n,p,q;for(r(b,c,e,h,this.We);c<=e;c+=h)for(p=Math.min(c+h-1,e),r(b,c,p,g,this.Xe),n=c;n<=p;n+=g)q=Math.min(n+g-1,p),k.children.push(this.Wf(b,n,q,f-1));d(k,this.La);return k},Di:function(b,c,d,e){for(var f,g,h,n,p,q,r,t;;){e.push(c);if(c.Ba||e.length-1===d)break;r=t=Infinity;f=0;for(g=c.children.length;f<g;f++){h=c.children[f];p=k(h.bbox);q=b;var v=h.bbox;q=(Math.max(v[2],q[2])-Math.min(v[0],q[0]))*(Math.max(v[3], +q[3])-Math.min(v[1],q[1]))-p;q<t?(t=q,r=p<r?p:r,n=h):q===t&&p<r&&(r=p,n=h)}c=n}return c},Yf:function(b,c,d){var e=this.La;d=d?b.bbox:e(b);var e=[],g=this.Di(d,this.data,c,e);g.children.push(b);for(f(g.bbox,d);0<=c;)if(e[c].children.length>this.Te)this.Ji(e,c),c--;else break;this.Ai(d,e,c)},Ji:function(b,c){var e=b[c],f=e.children.length,g=this.Zf;this.Bi(e,g,f);f={children:e.children.splice(this.Ci(e,g,f)),height:e.height};e.Ba&&(f.Ba=!0);d(e,this.La);d(f,this.La);c?b[c-1].children.push(f):this.ag(e, +f)},ag:function(b,c){this.data={children:[b,c],height:b.height+1};d(this.data,this.La)},Ci:function(b,c,d){var f,g,h,n,p,q,r;p=q=Infinity;for(f=c;f<=d-c;f++){g=e(b,0,f,this.La);h=e(b,f,d,this.La);var t=g,v=h;n=Math.max(t[0],v[0]);var Aa=Math.max(t[1],v[1]),fb=Math.min(t[2],v[2]),t=Math.min(t[3],v[3]);n=Math.max(0,fb-n)*Math.max(0,t-Aa);g=k(g)+k(h);n<p?(p=n,r=f,q=g<q?g:q):n===p&&g<q&&(q=g,r=f)}return r},Bi:function(b,c,d){var e=b.Ba?this.We:g,f=b.Ba?this.Xe:h,k=this.Vf(b,c,d,e);c=this.Vf(b,c,d,f); +k<c&&b.children.sort(e)},Vf:function(b,c,d,g){b.children.sort(g);g=this.La;var h=e(b,0,c,g),k=e(b,d-c,d,g),p=n(h)+n(k),q,r;for(q=c;q<d-c;q++)r=b.children[q],f(h,b.Ba?g(r):r.bbox),p+=n(h);for(q=d-c-1;q>=c;q--)r=b.children[q],f(k,b.Ba?g(r):r.bbox),p+=n(k);return p},Ai:function(b,c,d){for(;0<=d;d--)f(c[d].bbox,b)},Ei:function(b){for(var c=b.length-1,e;0<=c;c--)0===b[c].children.length?0<c?(e=b[c-1].children,e.splice(e.indexOf(b[c]),1)):this.clear():d(b[c],this.La)},Gi:function(b){var c=["return a"," - b", +";"];this.We=new Function("a","b",c.join(b[0]));this.Xe=new Function("a","b",c.join(b[1]));this.La=new Function("a","return [a"+b.join(", a")+"];")}};"undefined"!==typeof b?b.kb=c:"undefined"!==typeof self?self.a=c:window.a=c})();vp=b.kb})();function xp(b){this.b=vp(b);this.a={}}l=xp.prototype;l.oa=function(b,c){var d=[b[0],b[1],b[2],b[3],c];this.b.oa(d);this.a[ma(c)]=d};l.load=function(b,c){for(var d=Array(c.length),e=0,f=c.length;e<f;e++){var g=b[e],h=c[e],g=[g[0],g[1],g[2],g[3],h];d[e]=g;this.a[ma(h)]=g}this.b.load(d)};l.remove=function(b){b=ma(b);var c=this.a[b];zb(this.a,b);return null!==this.b.remove(c)};l.update=function(b,c){var d=ma(c);Xd(this.a[d].slice(0,4),b)||(this.remove(c),this.oa(b,c))}; +function yp(b){b=b.b.all();return Ua(b,function(b){return b[4]})}function zp(b,c){var d=b.b.search(c);return Ua(d,function(b){return b[4]})}l.forEach=function(b,c){return Ap(yp(this),b,c)};function Bp(b,c,d,e){return Ap(zp(b,c),d,e)}function Ap(b,c,d){for(var e,f=0,g=b.length;f<g&&!(e=c.call(d,b[f]));f++);return e}l.wa=function(){return xb(this.a)};l.clear=function(){this.b.clear();this.a={}};l.R=function(){return this.b.data.bbox};function W(b){b=m(b)?b:{};Dh.call(this,{attributions:b.attributions,logo:b.logo,projection:void 0,state:"ready",wrapX:m(b.wrapX)?b.wrapX:!0});this.T=ca;m(b.loader)?this.T=b.loader:m(b.url)&&(this.T=tp(b.url,b.format));this.xa=m(b.strategy)?b.strategy:up;var c=m(b.useSpatialIndex)?b.useSpatialIndex:!0;this.b=c?new xp:null;this.$=new xp;this.f={};this.i={};this.l={};this.v={};this.c=null;var d,e;b.features instanceof rg?(d=b.features,e=d.b):ga(b.features)&&(e=b.features);c||m(d)||(d=new rg(e));m(e)&& +Cp(this,e);m(d)&&Dp(this,d)}w(W,Dh);l=W.prototype;l.vd=function(b){var c=ma(b).toString();if(Ep(this,c,b)){Fp(this,c,b);var d=b.Z();null!=d?(c=d.R(),null===this.b||this.b.oa(c,b)):this.f[c]=b;C(this,new Gp("addfeature",b))}this.s()};function Fp(b,c,d){b.v[c]=[x(d,"change",b.xg,!1,b),x(d,"propertychange",b.xg,!1,b)]}function Ep(b,c,d){var e=!0,f=d.ha;m(f)?f.toString()in b.i?e=!1:b.i[f.toString()]=d:b.l[c]=d;return e}l.Bc=function(b){Cp(this,b);this.s()}; +function Cp(b,c){var d,e,f,g,h=[],k=[],n=[];e=0;for(f=c.length;e<f;e++)g=c[e],d=ma(g).toString(),Ep(b,d,g)&&k.push(g);e=0;for(f=k.length;e<f;e++){g=k[e];d=ma(g).toString();Fp(b,d,g);var p=g.Z();null!=p?(d=p.R(),h.push(d),n.push(g)):b.f[d]=g}null===b.b||b.b.load(h,n);e=0;for(f=k.length;e<f;e++)C(b,new Gp("addfeature",k[e]))} +function Dp(b,c){var d=!1;x(b,"addfeature",function(b){d||(d=!0,c.push(b.feature),d=!1)});x(b,"removefeature",function(b){d||(d=!0,c.remove(b.feature),d=!1)});x(c,"add",function(b){d||(b=b.element,d=!0,this.vd(b),d=!1)},!1,b);x(c,"remove",function(b){d||(b=b.element,d=!0,this.Mc(b),d=!1)},!1,b);b.c=c} +l.clear=function(b){if(b){for(var c in this.v)Sa(this.v[c],Xc);null===this.c&&(this.v={},this.i={},this.l={})}else b=this.Bh,null!==this.b&&(this.b.forEach(b,this),pb(this.f,b,this));null===this.c||this.c.clear();null===this.b||this.b.clear();this.$.clear();this.f={};C(this,new Gp("clear"));this.s()};l.ig=function(b,c){if(null!==this.b)return this.b.forEach(b,c);if(null!==this.c)return this.c.forEach(b,c)}; +function Hp(b,c,d){b.ld([c[0],c[1],c[0],c[1]],function(b){if(b.Z().Ye(c))return d.call(void 0,b)})}l.ld=function(b,c,d){if(null!==this.b)return Bp(this.b,b,c,d);if(null!==this.c)return this.c.forEach(c,d)};l.Ib=function(b,c,d,e){return this.ld(b,d,e)};l.cf=function(b,c,d){return this.ld(b,function(e){if(e.Z().sa(b)&&(e=c.call(d,e)))return e})};l.gf=function(){return this.c};l.Lc=function(){var b;null!==this.c?b=this.c.b:null!==this.b&&(b=yp(this.b),xb(this.f)||cb(b,sb(this.f)));return b}; +l.ff=function(b){var c=[];Hp(this,b,function(b){c.push(b)});return c};l.hf=function(b){return zp(this.b,b)};l.kg=function(b){var c=b[0],d=b[1],e=null,f=[NaN,NaN],g=Infinity,h=[-Infinity,-Infinity,Infinity,Infinity];Bp(this.b,h,function(b){var n=b.Z(),p=g;g=n.Ya(c,d,f,g);g<p&&(e=b,b=Math.sqrt(g),h[0]=c-b,h[1]=d-b,h[2]=c+b,h[3]=d+b)});return e};l.R=function(){return this.b.R()};l.ef=function(b){b=this.i[b.toString()];return m(b)?b:null}; +l.xg=function(b){b=b.target;var c=ma(b).toString(),d=b.Z();null!=d?(d=d.R(),c in this.f?(delete this.f[c],null===this.b||this.b.oa(d,b)):null===this.b||this.b.update(d,b)):c in this.f||(null===this.b||this.b.remove(b),this.f[c]=b);d=b.ha;m(d)?(d=d.toString(),c in this.l?(delete this.l[c],this.i[d]=b):this.i[d]!==b&&(Ip(this,b),this.i[d]=b)):c in this.l||(Ip(this,b),this.l[c]=b);this.s();C(this,new Gp("changefeature",b))};l.wa=function(){return this.b.wa()&&xb(this.f)}; +l.hc=function(b,c,d){var e=this.$;b=this.xa(b,c);var f,g;f=0;for(g=b.length;f<g;++f){var h=b[f];Bp(e,h,function(b){return Ud(b.extent,h)})||(this.T.call(this,h,c,d),e.oa(h,{extent:h.slice()}))}};l.Mc=function(b){var c=ma(b).toString();c in this.f?delete this.f[c]:null===this.b||this.b.remove(b);this.Bh(b);this.s()};l.Bh=function(b){var c=ma(b).toString();Sa(this.v[c],Xc);delete this.v[c];var d=b.ha;m(d)?delete this.i[d.toString()]:delete this.l[c];C(this,new Gp("removefeature",b))}; +function Ip(b,c){for(var d in b.i)if(b.i[d]===c){delete b.i[d];break}}function Gp(b,c){sc.call(this,b);this.feature=c}w(Gp,sc);function Jp(b){this.b=b.source;this.ea=Ad();this.c=Vi();this.f=[0,0];this.u=null;An.call(this,{attributions:b.attributions,canvasFunction:ra(this.Ui,this),logo:b.logo,projection:b.projection,ratio:b.ratio,resolutions:b.resolutions,state:this.b.A});this.C=null;this.i=void 0;this.$g(b.style);x(this.b,"change",this.mm,void 0,this)}w(Jp,An);l=Jp.prototype; +l.Ui=function(b,c,d,e,f){var g=new Tm(.5*c/d,b,c);this.b.hc(b,c,f);var h=!1;this.b.Ib(b,c,function(b){var e;if(!(e=h)){var f;m(b.c)?f=b.c.call(b,c):m(this.i)&&(f=this.i(b,c));if(null!=f){var q,r=!1;e=0;for(q=f.length;e<q;++e)r=qn(g,b,f[e],pn(c,d),this.lm,this)||r;e=r}else e=!1}h=e},this);Um(g);if(h)return null;this.f[0]!=e[0]||this.f[1]!=e[1]?(this.c.canvas.width=e[0],this.c.canvas.height=e[1],this.f[0]=e[0],this.f[1]=e[1]):this.c.clearRect(0,0,e[0],e[1]);b=Kp(this,ee(b),c,d,e);Ym(g,this.c,d,b,0, +{});this.u=g;return this.c.canvas};l.te=function(b,c,d,e,f){if(null!==this.u){var g={};return Vm(this.u,b,c,0,e,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,f(b)})}};l.im=function(){return this.b};l.jm=function(){return this.C};l.km=function(){return this.i};function Kp(b,c,d,e,f){return ok(b.ea,f[0]/2,f[1]/2,e/d,-e/d,0,-c[0],-c[1])}l.lm=function(){this.s()};l.mm=function(){Fh(this,this.b.A)};l.$g=function(b){this.C=m(b)?b:Ml;this.i=null===b?void 0:Ll(this.C);this.s()};function Lp(b){zm.call(this,b);this.g=null;this.i=Ad();this.c=this.f=null}w(Lp,zm);l=Lp.prototype;l.Va=function(b,c,d,e){var f=this.b;return f.ca().te(b,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(b){return d.call(e,b,f)})}; +l.lc=function(b,c,d,e){if(!fa(this.ud()))if(this.b.ca()instanceof Jp){if(b=b.slice(),qk(c.pixelToCoordinateMatrix,b,b),this.Va(b,c,pe,this))return d.call(e,this.b)}else if(null===this.f&&(this.f=Ad(),Gd(this.i,this.f)),c=Cm(b,this.f),null===this.c&&(this.c=Vi(1,1)),this.c.clearRect(0,0,1,1),this.c.drawImage(this.ud(),c[0],c[1],1,1,0,0,1,1),0<this.c.getImageData(0,0,1,1).data[3])return d.call(e,this.b)};l.ud=function(){return null===this.g?null:this.g.a()};l.kf=function(){return this.i}; +l.se=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.center,g=e.resolution,h=e.rotation,k,n=this.b.ca(),p=b.viewHints;k=b.extent;m(c.extent)&&(k=he(k,c.extent));p[0]||p[1]||ke(k)||(e=e.projection,p=n.j,null===p||(e=p),k=n.mc(k,g,d,e),null!==k&&tk(this,k)&&(this.g=k));if(null!==this.g){k=this.g;var e=k.R(),p=k.resolution,q=k.g,g=d*p/(g*q);ok(this.i,d*b.size[0]/2,d*b.size[1]/2,g,g,h,q*(e[0]-f[0])/p,q*(f[1]-e[3])/p);this.f=null;vk(b.attributions,k.i);wk(b,n)}return!0};function Mp(b){zm.call(this,b);this.c=this.i=null;this.B=!1;this.j=null;this.v=Ad();this.g=null;this.G=this.C=this.u=NaN;this.l=this.f=null;this.U=[0,0]}w(Mp,zm);Mp.prototype.ud=function(){return this.i};Mp.prototype.kf=function(){return this.v}; +Mp.prototype.se=function(b,c){var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.b,h=g.ca(),k=Th(h,f),n=h.Sd(),p=Oh(k,e.resolution),q=h.dc(p,b.pixelRatio,f),r=q[0]/md(k.Ja(p),this.U)[0],t=k.ua(p),r=t/r,v=e.center,B;t==e.resolution?(v=yk(v,t,b.size),B=fe(v,t,e.rotation,b.size)):B=b.extent;m(c.extent)&&(B=he(B,c.extent));if(ke(B))return!1;var z=Mh(k,B,t),E=q[0]*ng(z),A=q[1]*(z.c-z.b+1),y,J;null===this.i?(J=Vi(E,A),this.i=J.canvas,this.c=[E,A],this.j=J,this.B=!Dm(this.c)):(y=this.i,J=this.j,this.c[0]< +E||this.c[1]<A||this.C!==q[0]||this.G!==q[1]||this.B&&(this.c[0]>E||this.c[1]>A)?(y.width=E,y.height=A,this.c=[E,A],this.B=!Dm(this.c),this.f=null):(E=this.c[0],A=this.c[1],(y=p!=this.u)||(y=this.f,y=!(y.a<=z.a&&z.f<=y.f&&y.b<=z.b&&z.c<=y.c)),y&&(this.f=null)));var L,H;null===this.f?(E/=q[0],A/=q[1],L=z.a-Math.floor((E-ng(z))/2),H=z.b-Math.floor((A-(z.c-z.b+1))/2),this.u=p,this.C=q[0],this.G=q[1],this.f=new lg(L,L+E-1,H,H+A-1),this.l=Array(E*A),A=this.f):(A=this.f,E=ng(A));y={};y[p]={};var S=[],ta= +this.Od(h,y),Pa=g.c(),R=Ld(),Aa=new lg(0,0,0,0),fb,Ia,Db;for(H=z.a;H<=z.f;++H)for(Db=z.b;Db<=z.c;++Db)Ia=h.cc(p,H,Db,d,f),L=Ia.state,2==L||4==L||3==L&&!Pa?y[p][eg(Ia.a)]=Ia:(fb=Jh(k,Ia.a,ta,Aa,R),fb||(S.push(Ia),fb=Lh(k,Ia.a,Aa,R),null===fb||ta(p+1,fb)));ta=0;for(fb=S.length;ta<fb;++ta)Ia=S[ta],H=q[0]*(Ia.a[1]-A.a),Db=q[1]*(A.c-Ia.a[2]),J.clearRect(H,Db,q[0],q[1]);S=Ua(tb(y),Number);gb(S);var $a=h.ba,Hc=de(Kh(k,[p,A.a,A.c],R)),lc,Qe,wj,ai,Tf,sm,ta=0;for(fb=S.length;ta<fb;++ta)if(lc=S[ta],q=h.dc(lc, +d,f),ai=y[lc],lc==p)for(wj in ai)Ia=ai[wj],Qe=(Ia.a[2]-A.b)*E+(Ia.a[1]-A.a),this.l[Qe]!=Ia&&(H=q[0]*(Ia.a[1]-A.a),Db=q[1]*(A.c-Ia.a[2]),L=Ia.state,4!=L&&(3!=L||Pa)&&$a||J.clearRect(H,Db,q[0],q[1]),2==L&&J.drawImage(Ia.Ta(),n,n,q[0],q[1],H,Db,q[0],q[1]),this.l[Qe]=Ia);else for(wj in lc=k.ua(lc)/t,ai)for(Ia=ai[wj],Qe=Kh(k,Ia.a,R),H=(Qe[0]-Hc[0])/r,Db=(Hc[1]-Qe[3])/r,sm=lc*q[0],Tf=lc*q[1],L=Ia.state,4!=L&&$a||J.clearRect(H,Db,sm,Tf),2==L&&J.drawImage(Ia.Ta(),n,n,q[0],q[1],H,Db,sm,Tf),Ia=jg(k,Qe,p,Aa), +L=Math.max(Ia.a,A.a),Db=Math.min(Ia.f,A.f),H=Math.max(Ia.b,A.b),Ia=Math.min(Ia.c,A.c);L<=Db;++L)for(Tf=H;Tf<=Ia;++Tf)Qe=(Tf-A.b)*E+(L-A.a),this.l[Qe]=void 0;xk(b.usedTiles,h,p,z);zk(b,h,k,d,f,B,p,g.b());uk(b,h);wk(b,h);ok(this.v,d*b.size[0]/2,d*b.size[1]/2,d*r/e.resolution,d*r/e.resolution,e.rotation,(Hc[0]-v[0])/r,(v[1]-Hc[1])/r);this.g=null;return!0}; +Mp.prototype.lc=function(b,c,d,e){if(null!==this.j&&(null===this.g&&(this.g=Ad(),Gd(this.v,this.g)),b=Cm(b,this.g),0<this.j.getImageData(b[0],b[1],1,1).data[3]))return d.call(e,this.b)};function Np(b){zm.call(this,b);this.f=!1;this.B=-1;this.l=NaN;this.i=Ld();this.c=this.j=null;this.g=Vi()}w(Np,zm); +Np.prototype.A=function(b,c,d){var e=b.extent,f=b.pixelRatio,g=c.Qb?b.skippedFeatureUids:{},h=b.viewState,k=h.projection,h=h.rotation,n=k.R(),p=this.b.ca(),q=Bm(this,b,0);Am(this,"precompose",d,b,q);var r=this.c;if(null!==r&&!r.wa()){var t;cd(this.b,"render")?(this.g.canvas.width=d.canvas.width,this.g.canvas.height=d.canvas.height,t=this.g):t=d;var v=t.globalAlpha;t.globalAlpha=c.opacity;Ym(r,t,f,q,h,g);if(p.G&&k.g&&!Ud(n,e)){c=e[0];k=je(n);for(p=0;c<n[0];)--p,q=k*p,q=Bm(this,b,q),Ym(r,t,f,q,h,g), +c+=k;p=0;for(c=e[2];c>n[2];)++p,q=k*p,q=Bm(this,b,q),Ym(r,t,f,q,h,g),c-=k;q=Bm(this,b,0)}t!=d&&(Am(this,"render",t,b,q),d.drawImage(t.canvas,0,0));t.globalAlpha=v}Am(this,"postcompose",d,b,q)};Np.prototype.Va=function(b,c,d,e){if(null!==this.c){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.b,k=c.layerStates[ma(h)],n={};return Vm(this.c,b,f,g,k.Qb?c.skippedFeatureUids:{},function(b){var c=ma(b).toString();if(!(c in n))return n[c]=!0,d.call(e,b,h)})}};Np.prototype.v=function(){sk(this)}; +Np.prototype.se=function(b){function c(b){var c;m(b.c)?c=b.c.call(b,p):m(d.b)&&(c=(0,d.b)(b,p));if(null!=c){if(null!=c){var e,f,g=!1;e=0;for(f=c.length;e<f;++e)g=qn(t,b,c[e],pn(p,q),this.v,this)||g;b=g}else b=!1;this.f=this.f||b}}var d=this.b,e=d.ca();vk(b.attributions,e.g);wk(b,e);var f=b.viewHints[0],g=b.viewHints[1],h=d.A,k=d.u;if(!this.f&&!h&&f||!k&&g)return!0;var n=b.extent,k=b.viewState,f=k.projection,p=k.resolution,q=b.pixelRatio,g=d.a,r=d.c,h=d.get("renderOrder");m(h)||(h=on);n=Pd(n,r*p); +r=k.projection.R();e.G&&k.projection.g&&!Ud(r,b.extent)&&(b=Math.max(je(n)/2,je(r)),n[0]=r[0]-b,n[2]=r[2]+b);if(!this.f&&this.l==p&&this.B==g&&this.j==h&&Ud(this.i,n))return!0;rc(this.c);this.c=null;this.f=!1;var t=new Tm(.5*p/q,n,p,d.c);e.hc(n,p,f);if(null===h)e.Ib(n,p,c,this);else{var v=[];e.Ib(n,p,function(b){v.push(b)},this);gb(v,h);Sa(v,c,this)}Um(t);this.l=p;this.B=g;this.j=h;this.i=n;this.c=t;return!0};function Op(b,c){Fk.call(this,0,c);this.c=Vi();this.a=this.c.canvas;this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Sg(b,this.a,0);this.b=!0;this.g=Ad()}w(Op,Fk);Op.prototype.Ze=function(b){return b instanceof I?new Lp(b):b instanceof K?new Mp(b):b instanceof M?new Np(b):null}; +function Pp(b,c,d){var e=b.i,f=b.c;if(cd(e,c)){var g=d.extent,h=d.pixelRatio,k=d.viewState.rotation,n=d.pixelRatio,p=d.viewState,q=p.resolution;b=ok(b.g,b.a.width/2,b.a.height/2,n/q,-n/q,-p.rotation,-p.center[0],-p.center[1]);g=new lm(f,h,g,b,k);C(e,new kk(c,e,g,d,f,null));ym(g)}}Op.prototype.V=function(){return"canvas"}; +Op.prototype.Ge=function(b){if(null===b)this.b&&(oh(this.a,!1),this.b=!1);else{var c=this.c,d=b.size[0]*b.pixelRatio,e=b.size[1]*b.pixelRatio;this.a.width!=d||this.a.height!=e?(this.a.width=d,this.a.height=e):c.clearRect(0,0,this.a.width,this.a.height);Gk(b);Pp(this,"precompose",b);var d=b.layerStatesArray,e=b.viewState.resolution,f,g,h,k;f=0;for(g=d.length;f<g;++f)k=d[f],h=k.layer,h=Ik(this,h),lk(k,e)&&"ready"==k.l&&h.se(b,k)&&h.A(b,k,c);Pp(this,"postcompose",b);this.b||(oh(this.a,!0),this.b=!0); +Jk(this,b);b.postRenderFunctions.push(Hk)}};function Qp(b,c){rk.call(this,b);this.target=c}w(Qp,rk);Qp.prototype.g=ca;Qp.prototype.l=ca;function Rp(b){var c=Pg("DIV");c.style.position="absolute";Qp.call(this,b,c);this.c=null;this.f=Cd()}w(Rp,Qp);Rp.prototype.Va=function(b,c,d,e){var f=this.b;return f.ca().te(b,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(b){return d.call(e,b,f)})};Rp.prototype.g=function(){Rg(this.target);this.c=null}; +Rp.prototype.i=function(b,c){var d=b.viewState,e=d.center,f=d.resolution,g=d.rotation,h=this.c,k=this.b.ca(),n=b.viewHints,p=b.extent;m(c.extent)&&(p=he(p,c.extent));n[0]||n[1]||ke(p)||(d=d.projection,n=k.j,null===n||(d=n),p=k.mc(p,f,b.pixelRatio,d),null===p||tk(this,p)&&(h=p));null!==h&&(d=h.R(),n=h.resolution,p=Ad(),ok(p,b.size[0]/2,b.size[1]/2,n/f,n/f,g,(d[0]-e[0])/n,(e[1]-d[3])/n),h!=this.c&&(e=h.a(this),e.style.maxWidth="none",e.style.position="absolute",Rg(this.target),this.target.appendChild(e), +this.c=h),pk(p,this.f)||(Zi(this.target,p),Dd(this.f,p)),vk(b.attributions,h.i),wk(b,k));return!0};function Sp(b){var c=Pg("DIV");c.style.position="absolute";Qp.call(this,b,c);this.f=!0;this.B=1;this.j=0;this.c={}}w(Sp,Qp);Sp.prototype.g=function(){Rg(this.target);this.j=0}; +Sp.prototype.i=function(b,c){if(!c.visible)return this.f&&(oh(this.target,!1),this.f=!1),!0;var d=b.pixelRatio,e=b.viewState,f=e.projection,g=this.b,h=g.ca(),k=Th(h,f),n=h.Sd(),p=Oh(k,e.resolution),q=k.ua(p),r=e.center,t;q==e.resolution?(r=yk(r,q,b.size),t=fe(r,q,e.rotation,b.size)):t=b.extent;m(c.extent)&&(t=he(t,c.extent));var q=Mh(k,t,q),v={};v[p]={};var B=this.Od(h,v),z=g.c(),E=Ld(),A=new lg(0,0,0,0),y,J,L,H;for(L=q.a;L<=q.f;++L)for(H=q.b;H<=q.c;++H)y=h.cc(p,L,H,d,f),J=y.state,2==J?v[p][eg(y.a)]= +y:4==J||3==J&&!z||(J=Jh(k,y.a,B,A,E),J||(y=Lh(k,y.a,A,E),null===y||B(p+1,y)));var S;if(this.j!=h.a){for(S in this.c)z=this.c[+S],Tg(z.target);this.c={};this.j=h.a}E=Ua(tb(v),Number);gb(E);var B={},ta;L=0;for(H=E.length;L<H;++L){S=E[L];S in this.c?z=this.c[S]:(z=k.Zd(r,S),z=new Tp(k,z),B[S]=!0,this.c[S]=z);S=v[S];for(ta in S){y=z;J=S[ta];var Pa=n,R=J.a,Aa=R[0],fb=R[1],Ia=R[2],R=eg(R);if(!(R in y.b)){var Aa=md(y.f.Ja(Aa),y.l),Db=J.Ta(y),$a=Db.style;$a.maxWidth="none";var Hc=void 0,lc=void 0;0<Pa?(Hc= +Pg("DIV"),lc=Hc.style,lc.overflow="hidden",lc.width=Aa[0]+"px",lc.height=Aa[1]+"px",$a.position="absolute",$a.left=-Pa+"px",$a.top=-Pa+"px",$a.width=Aa[0]+2*Pa+"px",$a.height=Aa[1]+2*Pa+"px",Hc.appendChild(Db)):($a.width=Aa[0]+"px",$a.height=Aa[1]+"px",Hc=Db,lc=$a);lc.position="absolute";lc.left=(fb-y.c[1])*Aa[0]+"px";lc.top=(y.c[2]-Ia)*Aa[1]+"px";null===y.a&&(y.a=document.createDocumentFragment());y.a.appendChild(Hc);y.b[R]=J}}null!==z.a&&(z.target.appendChild(z.a),z.a=null)}n=Ua(tb(this.c),Number); +gb(n);L=Ad();ta=0;for(E=n.length;ta<E;++ta)if(S=n[ta],z=this.c[S],S in v)if(y=z.j,H=z.i,ok(L,b.size[0]/2,b.size[1]/2,y/e.resolution,y/e.resolution,e.rotation,(H[0]-r[0])/y,(r[1]-H[1])/y),H=z,y=L,pk(y,H.g)||(Zi(H.target,y),Dd(H.g,y)),S in B){for(--S;0<=S;--S)if(S in this.c){H=this.c[S].target;H.parentNode&&H.parentNode.insertBefore(z.target,H.nextSibling);break}0>S&&Sg(this.target,z.target,0)}else{if(!b.viewHints[0]&&!b.viewHints[1]){J=jg(z.f,t,z.c[0],A);S=[];y=H=void 0;for(y in z.b)H=z.b[y],J.contains(H.a)|| +S.push(H);Pa=J=void 0;J=0;for(Pa=S.length;J<Pa;++J)H=S[J],y=eg(H.a),Tg(H.Ta(z)),delete z.b[y]}}else Tg(z.target),delete this.c[S];c.opacity!=this.B&&(this.B=this.target.style.opacity=c.opacity);c.visible&&!this.f&&(oh(this.target,!0),this.f=!0);xk(b.usedTiles,h,p,q);zk(b,h,k,d,f,t,p,g.b());uk(b,h);wk(b,h);return!0}; +function Tp(b,c){this.target=Pg("DIV");this.target.style.position="absolute";this.target.style.width="100%";this.target.style.height="100%";this.f=b;this.c=c;this.i=de(Kh(b,c));this.j=b.ua(c[0]);this.b={};this.a=null;this.g=Cd();this.l=[0,0]};function Up(b){this.j=Vi();var c=this.j.canvas;c.style.maxWidth="none";c.style.position="absolute";Qp.call(this,b,c);this.f=!1;this.u=-1;this.A=NaN;this.B=Ld();this.c=this.v=null;this.C=Ad();this.G=Ad()}w(Up,Qp); +Up.prototype.l=function(b,c){var d=b.viewState,e=d.center,f=d.rotation,g=d.resolution,d=b.pixelRatio,h=b.size[0],k=b.size[1],n=h*d,p=k*d,e=ok(this.C,d*h/2,d*k/2,d/g,-d/g,-f,-e[0],-e[1]),g=this.j;g.canvas.width=n;g.canvas.height=p;h=ok(this.G,0,0,1/d,1/d,0,-(n-h)/2*d,-(p-k)/2*d);Zi(g.canvas,h);Vp(this,"precompose",b,e);h=this.c;null===h||h.wa()||(g.globalAlpha=c.opacity,Ym(h,g,d,e,f,c.Qb?b.skippedFeatureUids:{}),Vp(this,"render",b,e));Vp(this,"postcompose",b,e)}; +function Vp(b,c,d,e){var f=b.j;b=b.b;cd(b,c)&&(e=new lm(f,d.pixelRatio,d.extent,e,d.viewState.rotation),C(b,new kk(c,b,e,d,f,null)),ym(e))}Up.prototype.Va=function(b,c,d,e){if(null!==this.c){var f=c.viewState.resolution,g=c.viewState.rotation,h=this.b,k=c.layerStates[ma(h)],n={};return Vm(this.c,b,f,g,k.Qb?c.skippedFeatureUids:{},function(b){var c=ma(b).toString();if(!(c in n))return n[c]=!0,d.call(e,b,h)})}};Up.prototype.K=function(){sk(this)}; +Up.prototype.i=function(b){function c(b){var c;m(b.c)?c=b.c.call(b,n):m(d.b)&&(c=(0,d.b)(b,n));if(null!=c){if(null!=c){var e,f,g=!1;e=0;for(f=c.length;e<f;++e)g=qn(q,b,c[e],pn(n,p),this.K,this)||g;b=g}else b=!1;this.f=this.f||b}}var d=this.b,e=d.ca();vk(b.attributions,e.g);wk(b,e);var f=b.viewHints[0],g=b.viewHints[1],h=d.A,k=d.u;if(!this.f&&!h&&f||!k&&g)return!0;var g=b.extent,h=b.viewState,f=h.projection,n=h.resolution,p=b.pixelRatio;b=d.a;k=d.c;h=d.get("renderOrder");m(h)||(h=on);g=Pd(g,k*n);if(!this.f&& +this.A==n&&this.u==b&&this.v==h&&Ud(this.B,g))return!0;rc(this.c);this.c=null;this.f=!1;var q=new Tm(.5*n/p,g,n,d.c);e.hc(g,n,f);if(null===h)e.Ib(g,n,c,this);else{var r=[];e.Ib(g,n,function(b){r.push(b)},this);gb(r,h);Sa(r,c,this)}Um(q);this.A=n;this.u=b;this.v=h;this.B=g;this.c=q;return!0};function Wp(b,c){Fk.call(this,0,c);this.b=null;this.b=Vi();var d=this.b.canvas;d.style.position="absolute";d.style.width="100%";d.style.height="100%";d.className="ol-unselectable";Sg(b,d,0);this.g=Ad();this.a=Pg("DIV");this.a.className="ol-unselectable";d=this.a.style;d.position="absolute";d.width="100%";d.height="100%";x(this.a,"touchstart",uc);Sg(b,this.a,0);this.c=!0}w(Wp,Fk);Wp.prototype.X=function(){Tg(this.a);Wp.aa.X.call(this)}; +Wp.prototype.Ze=function(b){if(b instanceof I)b=new Rp(b);else if(b instanceof K)b=new Sp(b);else if(b instanceof M)b=new Up(b);else return null;return b};function Xp(b,c,d){var e=b.i;if(cd(e,c)){var f=d.extent,g=d.pixelRatio,h=d.viewState,k=h.rotation,n=b.b,p=n.canvas;ok(b.g,p.width/2,p.height/2,g/h.resolution,-g/h.resolution,-h.rotation,-h.center[0],-h.center[1]);b=new lm(n,g,f,b.g,k);C(e,new kk(c,e,b,d,n,null));ym(b)}}Wp.prototype.V=function(){return"dom"}; +Wp.prototype.Ge=function(b){if(null===b)this.c&&(oh(this.a,!1),this.c=!1);else{var c;c=function(b,c){Sg(this.a,b,c)};var d=this.i;if(cd(d,"precompose")||cd(d,"postcompose")){var d=this.b.canvas,e=b.pixelRatio;d.width=b.size[0]*e;d.height=b.size[1]*e}Xp(this,"precompose",b);var d=b.layerStatesArray,e=b.viewState.resolution,f,g,h,k;f=0;for(g=d.length;f<g;++f)k=d[f],h=k.layer,h=Ik(this,h),c.call(this,h.target,f),lk(k,e)&&"ready"==k.l?h.i(b,k)&&h.l(b,k):h.g();c=b.layerStates;for(var n in this.f)n in c|| +(h=this.f[n],Tg(h.target));this.c||(oh(this.a,!0),this.c=!0);Gk(b);Jk(this,b);b.postRenderFunctions.push(Hk);Xp(this,"postcompose",b)}};function Yp(b){this.a=b}function Zp(b){this.a=b}w(Zp,Yp);Zp.prototype.V=function(){return 35632};function $p(b){this.a=b}w($p,Yp);$p.prototype.V=function(){return 35633};function aq(){this.a="precision mediump float;varying vec2 a;varying float b;uniform mat4 k;uniform float l;uniform sampler2D m;void main(void){vec4 texColor=texture2D(m,a);float alpha=texColor.a*b*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;gl_FragColor.rgb=(k*vec4(texColor.rgb,1.)).rgb;}"}w(aq,Zp);da(aq); +function bq(){this.a="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}w(bq,$p);da(bq); +function cq(b,c){this.v=b.getUniformLocation(c,"k");this.l=b.getUniformLocation(c,"j");this.B=b.getUniformLocation(c,"i");this.i=b.getUniformLocation(c,"l");this.j=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.b=b.getAttribLocation(c,"f");this.f=b.getAttribLocation(c,"c");this.c=b.getAttribLocation(c,"g");this.g=b.getAttribLocation(c,"d")};function dq(){this.a="precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}"}w(dq,Zp);da(dq); +function eq(){this.a="varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}"}w(eq,$p);da(eq); +function fq(b,c){this.l=b.getUniformLocation(c,"j");this.B=b.getUniformLocation(c,"i");this.i=b.getUniformLocation(c,"k");this.j=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"e");this.b=b.getAttribLocation(c,"f");this.f=b.getAttribLocation(c,"c");this.c=b.getAttribLocation(c,"g");this.g=b.getAttribLocation(c,"d")};function gq(b){this.a=m(b)?b:[];this.b=m(void 0)?void 0:35044};function hq(b,c){this.v=b;this.a=c;this.b={};this.i={};this.g={};this.l=this.B=this.f=this.j=null;(this.c=Ya(wa,"OES_element_index_uint"))&&c.getExtension("OES_element_index_uint");x(this.v,"webglcontextlost",this.ln,!1,this);x(this.v,"webglcontextrestored",this.mn,!1,this)} +function iq(b,c,d){var e=b.a,f=d.a,g=ma(d);if(g in b.b)e.bindBuffer(c,b.b[g].buffer);else{var h=e.createBuffer();e.bindBuffer(c,h);var k;34962==c?k=new Float32Array(f):34963==c&&(k=b.c?new Uint32Array(f):new Uint16Array(f));e.bufferData(c,k,d.b);b.b[g]={c:d,buffer:h}}}function jq(b,c){var d=b.a,e=ma(c),f=b.b[e];d.isContextLost()||d.deleteBuffer(f.buffer);delete b.b[e]}l=hq.prototype; +l.X=function(){var b=this.a;b.isContextLost()||(pb(this.b,function(c){b.deleteBuffer(c.buffer)}),pb(this.g,function(c){b.deleteProgram(c)}),pb(this.i,function(c){b.deleteShader(c)}),b.deleteFramebuffer(this.f),b.deleteRenderbuffer(this.l),b.deleteTexture(this.B))};l.kn=function(){return this.a}; +l.jf=function(){if(null===this.f){var b=this.a,c=b.createFramebuffer();b.bindFramebuffer(b.FRAMEBUFFER,c);var d=kq(b,1,1),e=b.createRenderbuffer();b.bindRenderbuffer(b.RENDERBUFFER,e);b.renderbufferStorage(b.RENDERBUFFER,b.DEPTH_COMPONENT16,1,1);b.framebufferTexture2D(b.FRAMEBUFFER,b.COLOR_ATTACHMENT0,b.TEXTURE_2D,d,0);b.framebufferRenderbuffer(b.FRAMEBUFFER,b.DEPTH_ATTACHMENT,b.RENDERBUFFER,e);b.bindTexture(b.TEXTURE_2D,null);b.bindRenderbuffer(b.RENDERBUFFER,null);b.bindFramebuffer(b.FRAMEBUFFER, +null);this.f=c;this.B=d;this.l=e}return this.f};function lq(b,c){var d=ma(c);if(d in b.i)return b.i[d];var e=b.a,f=e.createShader(c.V());e.shaderSource(f,c.a);e.compileShader(f);return b.i[d]=f}function mq(b,c,d){var e=ma(c)+"/"+ma(d);if(e in b.g)return b.g[e];var f=b.a,g=f.createProgram();f.attachShader(g,lq(b,c));f.attachShader(g,lq(b,d));f.linkProgram(g);return b.g[e]=g}l.ln=function(){yb(this.b);yb(this.i);yb(this.g);this.l=this.B=this.f=this.j=null};l.mn=function(){}; +l.Ae=function(b){if(b==this.j)return!1;this.a.useProgram(b);this.j=b;return!0};function nq(b,c,d){var e=b.createTexture();b.bindTexture(b.TEXTURE_2D,e);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.LINEAR);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.LINEAR);m(c)&&b.texParameteri(3553,10242,c);m(d)&&b.texParameteri(3553,10243,d);return e}function kq(b,c,d){var e=nq(b,void 0,void 0);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,c,d,0,b.RGBA,b.UNSIGNED_BYTE,null);return e} +function oq(b,c){var d=nq(b,33071,33071);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,b.RGBA,b.UNSIGNED_BYTE,c);return d};function pq(b,c){this.fa=this.G=void 0;this.tb=new Ag;this.B=ee(c);this.u=[];this.i=[];this.K=void 0;this.g=[];this.f=[];this.T=this.U=void 0;this.b=[];this.C=this.da=this.l=null;this.$=void 0;this.rb=Cd();this.sb=Cd();this.ea=this.ba=void 0;this.ub=Cd();this.xa=this.Ma=this.qa=void 0;this.Xa=[];this.j=[];this.a=[];this.A=null;this.c=[];this.v=[];this.Na=void 0}w(pq,jk); +function qq(b,c){var d=b.A,e=b.l,f=b.Xa,g=b.j,h=c.a;return function(){if(!h.isContextLost()){var b,n;b=0;for(n=f.length;b<n;++b)h.deleteTexture(f[b]);b=0;for(n=g.length;b<n;++b)h.deleteTexture(g[b])}jq(c,d);jq(c,e)}} +function rq(b,c,d,e){var f=b.G,g=b.fa,h=b.K,k=b.U,n=b.T,p=b.$,q=b.ba,r=b.ea,t=b.qa?1:0,v=b.Ma,B=b.xa,z=b.Na,E=Math.cos(v),v=Math.sin(v),A=b.b.length,y=b.a.length,J,L,H,S,ta,Pa;for(J=0;J<d;J+=e)ta=c[J]-b.B[0],Pa=c[J+1]-b.B[1],L=y/8,H=-B*f,S=-B*(h-g),b.a[y++]=ta,b.a[y++]=Pa,b.a[y++]=H*E-S*v,b.a[y++]=H*v+S*E,b.a[y++]=q/n,b.a[y++]=(r+h)/k,b.a[y++]=p,b.a[y++]=t,H=B*(z-f),S=-B*(h-g),b.a[y++]=ta,b.a[y++]=Pa,b.a[y++]=H*E-S*v,b.a[y++]=H*v+S*E,b.a[y++]=(q+z)/n,b.a[y++]=(r+h)/k,b.a[y++]=p,b.a[y++]=t,H=B*(z- +f),S=B*g,b.a[y++]=ta,b.a[y++]=Pa,b.a[y++]=H*E-S*v,b.a[y++]=H*v+S*E,b.a[y++]=(q+z)/n,b.a[y++]=r/k,b.a[y++]=p,b.a[y++]=t,H=-B*f,S=B*g,b.a[y++]=ta,b.a[y++]=Pa,b.a[y++]=H*E-S*v,b.a[y++]=H*v+S*E,b.a[y++]=q/n,b.a[y++]=r/k,b.a[y++]=p,b.a[y++]=t,b.b[A++]=L,b.b[A++]=L+1,b.b[A++]=L+2,b.b[A++]=L,b.b[A++]=L+2,b.b[A++]=L+3}pq.prototype.wb=function(b,c){this.c.push(this.b.length);this.v.push(c);var d=b.o;rq(this,d,d.length,b.H)}; +pq.prototype.xb=function(b,c){this.c.push(this.b.length);this.v.push(c);var d=b.o;rq(this,d,d.length,b.H)};function sq(b,c){var d=c.a;b.u.push(b.b.length);b.i.push(b.b.length);b.A=new gq(b.a);iq(c,34962,b.A);b.l=new gq(b.b);iq(c,34963,b.l);var e={};tq(b.Xa,b.g,e,d);tq(b.j,b.f,e,d);b.G=void 0;b.fa=void 0;b.K=void 0;b.g=null;b.f=null;b.U=void 0;b.T=void 0;b.b=null;b.$=void 0;b.ba=void 0;b.ea=void 0;b.qa=void 0;b.Ma=void 0;b.xa=void 0;b.a=null;b.Na=void 0} +function tq(b,c,d,e){var f,g,h,k=c.length;for(h=0;h<k;++h)f=c[h],g=ma(f).toString(),g in d?f=d[g]:(f=oq(e,f),d[g]=f),b[h]=f} +function uq(b,c,d,e,f,g,h,k,n,p,q,r,t,v,B){var z=c.a;iq(c,34962,b.A);iq(c,34963,b.l);var E=k||1!=n||p||1!=q,A,y;E?(A=aq.Pa(),y=bq.Pa()):(A=dq.Pa(),y=eq.Pa());y=mq(c,A,y);E?null===b.da?(A=new cq(z,y),b.da=A):A=b.da:null===b.C?(A=new fq(z,y),b.C=A):A=b.C;c.Ae(y);z.enableVertexAttribArray(A.f);z.vertexAttribPointer(A.f,2,5126,!1,32,0);z.enableVertexAttribArray(A.a);z.vertexAttribPointer(A.a,2,5126,!1,32,8);z.enableVertexAttribArray(A.g);z.vertexAttribPointer(A.g,2,5126,!1,32,16);z.enableVertexAttribArray(A.b); +z.vertexAttribPointer(A.b,1,5126,!1,32,24);z.enableVertexAttribArray(A.c);z.vertexAttribPointer(A.c,1,5126,!1,32,28);y=b.ub;ok(y,0,0,2/(e*g[0]),2/(e*g[1]),-f,-(d[0]-b.B[0]),-(d[1]-b.B[1]));d=b.sb;e=2/g[0];g=2/g[1];Ed(d);d[0]=e;d[5]=g;d[10]=1;d[15]=1;g=b.rb;Ed(g);0!==f&&Jd(g,-f);z.uniformMatrix4fv(A.j,!1,y);z.uniformMatrix4fv(A.B,!1,d);z.uniformMatrix4fv(A.l,!1,g);z.uniform1f(A.i,h);E&&z.uniformMatrix4fv(A.v,!1,Bg(b.tb,k,n,p,q));var J;if(m(t)){if(v)a:{f=c.c?5125:5123;c=c.c?4:2;p=b.c.length-1;for(h= +b.j.length-1;0<=h;--h)for(z.bindTexture(3553,b.j[h]),k=0<h?b.i[h-1]:0,q=b.i[h];0<=p&&b.c[p]>=k;){n=b.c[p];v=b.v[p];E=ma(v).toString();if(!m(r[E])&&(!m(B)||ie(B,v.Z().R()))&&(z.clear(z.COLOR_BUFFER_BIT|z.DEPTH_BUFFER_BIT),z.drawElements(4,q-n,f,n*c),q=t(v))){b=q;break a}q=n;p--}b=void 0}else z.clear(z.COLOR_BUFFER_BIT|z.DEPTH_BUFFER_BIT),vq(b,z,c,r,b.j,b.i),b=(b=t(null))?b:void 0;J=b}else vq(b,z,c,r,b.Xa,b.u);z.disableVertexAttribArray(A.f);z.disableVertexAttribArray(A.a);z.disableVertexAttribArray(A.g); +z.disableVertexAttribArray(A.b);z.disableVertexAttribArray(A.c);return J} +function vq(b,c,d,e,f,g){var h=d.c?5125:5123;d=d.c?4:2;if(xb(e)){var k;b=0;e=f.length;for(k=0;b<e;++b){c.bindTexture(3553,f[b]);var n=g[b];c.drawElements(4,n-k,h,k*d);k=n}}else{k=0;var p,n=0;for(p=f.length;n<p;++n){c.bindTexture(3553,f[n]);for(var q=0<n?g[n-1]:0,r=g[n],t=q;k<b.c.length&&b.c[k]<=r;){var v=ma(b.v[k]).toString();m(e[v])?(t!==q&&c.drawElements(4,q-t,h,t*d),q=t=k===b.c.length-1?r:b.c[k+1]):q=k===b.c.length-1?r:b.c[k+1];k++}t!==q&&c.drawElements(4,q-t,h,t*d)}}} +pq.prototype.hb=function(b){var c=b.zb(),d=b.Tb(1),e=b.Td(),f=b.ue(1),g=b.u,h=b.Eb(),k=b.G,n=b.B,p=b.fb();b=b.v;var q;0===this.g.length?this.g.push(d):(q=this.g[this.g.length-1],ma(q)!=ma(d)&&(this.u.push(this.b.length),this.g.push(d)));0===this.f.length?this.f.push(f):(q=this.f[this.f.length-1],ma(q)!=ma(f)&&(this.i.push(this.b.length),this.f.push(f)));this.G=c[0];this.fa=c[1];this.K=p[1];this.U=e[1];this.T=e[0];this.$=g;this.ba=h[0];this.ea=h[1];this.Ma=n;this.qa=k;this.xa=b;this.Na=p[0]}; +function wq(b,c,d){this.f=c;this.g=b;this.c=d;this.b={}}function xq(b,c){var d=[],e;for(e in b.b)d.push(qq(b.b[e],c));return te.apply(null,d)}function yq(b,c){for(var d in b.b)sq(b.b[d],c)}wq.prototype.a=function(b,c){var d=this.b[c];m(d)||(d=new zq[c](this.g,this.f),this.b[c]=d);return d};wq.prototype.wa=function(){return xb(this.b)};function Aq(b,c,d,e,f,g,h,k,n,p,q,r,t,v){var B=Bq,z,E;for(z=Em.length-1;0<=z;--z)if(E=b.b[Em[z]],m(E)&&(E=uq(E,c,d,e,f,B,g,h,k,n,p,q,r,t,v)))return E} +function Cq(b,c,d,e,f,g,h,k,n,p,q,r){var t=d.a;t.bindFramebuffer(t.FRAMEBUFFER,d.jf());var v;m(b.c)&&(v=Pd(Wd(c),e*b.c));return Aq(b,d,c,e,f,g,h,k,n,p,q,function(b){var c=new Uint8Array(4);t.readPixels(0,0,1,1,t.RGBA,t.UNSIGNED_BYTE,c);if(0<c[3]&&(b=r(b)))return b},!0,v)}function Dq(b,c,d,e,f,g,h,k,n,p,q){var r=d.a;r.bindFramebuffer(r.FRAMEBUFFER,d.jf());b=Aq(b,d,c,e,f,g,h,k,n,p,q,function(){var b=new Uint8Array(4);r.readPixels(0,0,1,1,r.RGBA,r.UNSIGNED_BYTE,b);return 0<b[3]},!1);return m(b)} +var zq={Image:pq},Bq=[1,1];function Eq(b,c,d,e,f,g){this.b=b;this.g=c;this.f=g;this.l=f;this.j=e;this.i=d;this.c=null;this.a={}}w(Eq,jk);l=Eq.prototype;l.Cc=function(b,c){var d=b.toString(),e=this.a[d];m(e)?e.push(c):this.a[d]=[c]};l.Dc=function(){};l.$e=function(b,c){var d=(0,c.g)(b);if(null!=d&&ie(this.f,d.R())){var e=c.a;m(e)||(e=0);this.Cc(e,function(b){b.Ha(c.f,c.c);b.hb(c.i);b.Ia(c.b);var e=Fq[d.V()];e&&e.call(b,d,null)})}}; +l.Pd=function(b,c){var d=b.f,e,f;e=0;for(f=d.length;e<f;++e){var g=d[e],h=Fq[g.V()];h&&h.call(this,g,c)}};l.xb=function(b,c){var d=this.b,e=(new wq(1,this.f)).a(0,"Image");e.hb(this.c);e.xb(b,c);sq(e,d);uq(e,this.b,this.g,this.i,this.j,this.l,1,0,1,0,1,{},void 0,!1);qq(e,d)()};l.Hb=function(){};l.Ec=function(){};l.wb=function(b,c){var d=this.b,e=(new wq(1,this.f)).a(0,"Image");e.hb(this.c);e.wb(b,c);sq(e,d);uq(e,this.b,this.g,this.i,this.j,this.l,1,0,1,0,1,{},void 0,!1);qq(e,d)()};l.Fc=function(){}; +l.Zb=function(){};l.yb=function(){};l.Ha=function(){};l.hb=function(b){this.c=b};l.Ia=function(){};var Fq={Point:Eq.prototype.xb,MultiPoint:Eq.prototype.wb,GeometryCollection:Eq.prototype.Pd};function Gq(){this.a="precision mediump float;varying vec2 a;uniform mat4 f;uniform float g;uniform sampler2D h;void main(void){vec4 texColor=texture2D(h,a);gl_FragColor.rgb=(f*vec4(texColor.rgb,1.)).rgb;gl_FragColor.a=texColor.a*g;}"}w(Gq,Zp);da(Gq);function Hq(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}w(Hq,$p);da(Hq); +function Iq(b,c){this.j=b.getUniformLocation(c,"f");this.c=b.getUniformLocation(c,"g");this.f=b.getUniformLocation(c,"e");this.i=b.getUniformLocation(c,"d");this.g=b.getUniformLocation(c,"h");this.a=b.getAttribLocation(c,"b");this.b=b.getAttribLocation(c,"c")};function Jq(){this.a="precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}"}w(Jq,Zp);da(Jq);function Kq(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}"}w(Kq,$p);da(Kq); +function Lq(b,c){this.c=b.getUniformLocation(c,"f");this.f=b.getUniformLocation(c,"e");this.i=b.getUniformLocation(c,"d");this.g=b.getUniformLocation(c,"g");this.a=b.getAttribLocation(c,"b");this.b=b.getAttribLocation(c,"c")};function Mq(b,c){rk.call(this,c);this.c=b;this.T=new gq([-1,-1,0,0,1,-1,1,0,-1,1,0,1,1,1,1,1]);this.g=this.Wa=null;this.i=void 0;this.B=Ad();this.u=Cd();this.$=new Ag;this.A=this.v=null}w(Mq,rk); +function Nq(b,c,d){var e=b.c.c;if(m(b.i)&&b.i==d)e.bindFramebuffer(36160,b.g);else{c.postRenderFunctions.push(sa(function(b,c,d){b.isContextLost()||(b.deleteFramebuffer(c),b.deleteTexture(d))},e,b.g,b.Wa));c=kq(e,d,d);var f=e.createFramebuffer();e.bindFramebuffer(36160,f);e.framebufferTexture2D(36160,36064,3553,c,0);b.Wa=c;b.g=f;b.i=d}} +Mq.prototype.Zg=function(b,c,d){Oq(this,"precompose",d,b);iq(d,34962,this.T);var e=d.a,f=c.brightness||1!=c.contrast||c.hue||1!=c.saturation,g,h;f?(g=Gq.Pa(),h=Hq.Pa()):(g=Jq.Pa(),h=Kq.Pa());g=mq(d,g,h);f?null===this.v?this.v=h=new Iq(e,g):h=this.v:null===this.A?this.A=h=new Lq(e,g):h=this.A;d.Ae(g)&&(e.enableVertexAttribArray(h.a),e.vertexAttribPointer(h.a,2,5126,!1,16,0),e.enableVertexAttribArray(h.b),e.vertexAttribPointer(h.b,2,5126,!1,16,8),e.uniform1i(h.g,0));e.uniformMatrix4fv(h.i,!1,this.B); +e.uniformMatrix4fv(h.f,!1,this.u);f&&e.uniformMatrix4fv(h.j,!1,Bg(this.$,c.brightness,c.contrast,c.hue,c.saturation));e.uniform1f(h.c,c.opacity);e.bindTexture(3553,this.Wa);e.drawArrays(5,0,4);Oq(this,"postcompose",d,b)};function Oq(b,c,d,e){b=b.b;if(cd(b,c)){var f=e.viewState;C(b,new kk(c,b,new Eq(d,f.center,f.resolution,f.rotation,e.size,e.extent),e,null,d))}}Mq.prototype.zf=function(){this.g=this.Wa=null;this.i=void 0};function Pq(b,c){Mq.call(this,b,c);this.l=this.j=this.f=null}w(Pq,Mq);function Qq(b,c){var d=c.a();return oq(b.c.c,d)}Pq.prototype.Va=function(b,c,d,e){var f=this.b;return f.ca().te(b,c.viewState.resolution,c.viewState.rotation,c.skippedFeatureUids,function(b){return d.call(e,b,f)})}; +Pq.prototype.Af=function(b,c){var d=this.c.c,e=b.pixelRatio,f=b.viewState,g=f.center,h=f.resolution,k=f.rotation,n=this.f,p=this.Wa,q=this.b.ca(),r=b.viewHints,t=b.extent;m(c.extent)&&(t=he(t,c.extent));r[0]||r[1]||ke(t)||(f=f.projection,r=q.j,null===r||(f=r),t=q.mc(t,h,e,f),null!==t&&tk(this,t)&&(n=t,p=Qq(this,t),null===this.Wa||b.postRenderFunctions.push(sa(function(b,c){b.isContextLost()||b.deleteTexture(c)},d,this.Wa))));null!==n&&(d=this.c.g.v,Rq(this,d.width,d.height,e,g,h,k,n.R()),this.l=null, +e=this.B,Ed(e),Id(e,1,-1),Hd(e,0,-1),this.f=n,this.Wa=p,vk(b.attributions,n.i),wk(b,q));return!0};function Rq(b,c,d,e,f,g,h,k){c*=g;d*=g;b=b.u;Ed(b);Id(b,2*e/c,2*e/d);Jd(b,-h);Hd(b,k[0]-f[0],k[1]-f[1]);Id(b,(k[2]-k[0])/2,(k[3]-k[1])/2);Hd(b,1,1)}Pq.prototype.re=function(b,c){var d=this.Va(b,c,pe,this);return m(d)}; +Pq.prototype.lc=function(b,c,d,e){if(null!==this.f&&!fa(this.f.a()))if(this.b.ca()instanceof Jp){if(b=b.slice(),qk(c.pixelToCoordinateMatrix,b,b),this.Va(b,c,pe,this))return d.call(e,this.b)}else{var f=[this.f.a().width,this.f.a().height];if(null===this.l){var g=c.size;c=Ad();Ed(c);Hd(c,-1,-1);Id(c,2/g[0],2/g[1]);Hd(c,0,g[1]);Id(c,1,-1);g=Ad();Gd(this.u,g);var h=Ad();Ed(h);Hd(h,0,f[1]);Id(h,1,-1);Id(h,f[0]/2,f[1]/2);Hd(h,1,1);var k=Ad();Fd(h,g,k);Fd(k,c,k);this.l=k}c=[0,0];qk(this.l,b,c);if(!(0>c[0]|| +c[0]>f[0]||0>c[1]||c[1]>f[1])&&(null===this.j&&(this.j=Vi(1,1)),this.j.clearRect(0,0,1,1),this.j.drawImage(this.f.a(),c[0],c[1],1,1,0,0,1,1),0<this.j.getImageData(0,0,1,1).data[3]))return d.call(e,this.b)}};function Sq(){this.a="precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}"}w(Sq,Zp);da(Sq);function Tq(){this.a="varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}"}w(Tq,$p);da(Tq);function Uq(b,c){this.c=b.getUniformLocation(c,"e");this.f=b.getUniformLocation(c,"d");this.a=b.getAttribLocation(c,"b");this.b=b.getAttribLocation(c,"c")};function Vq(b,c){Mq.call(this,b,c);this.K=Sq.Pa();this.ba=Tq.Pa();this.f=null;this.C=new gq([0,0,0,1,1,0,1,1,0,1,0,0,1,1,1,0]);this.G=this.j=null;this.l=-1;this.U=[0,0]}w(Vq,Mq);l=Vq.prototype;l.X=function(){jq(this.c.g,this.C);Vq.aa.X.call(this)};l.Od=function(b,c){var d=this.c;return function(e,f){return Sh(b,e,f,function(b){var f=yh(d.b,b.pb());f&&(c[e]||(c[e]={}),c[e][b.a.toString()]=b);return f})}};l.zf=function(){Vq.aa.zf.call(this);this.f=null}; +l.Af=function(b,c,d){var e=this.c,f=d.a,g=b.viewState,h=g.projection,k=this.b,n=k.ca(),p=Th(n,h),q=Oh(p,g.resolution),r=p.ua(q),t=n.dc(q,b.pixelRatio,h),v=t[0]/md(p.Ja(q),this.U)[0],B=r/v,z=n.Sd(),E=g.center,A;r==g.resolution?(E=yk(E,r,b.size),A=fe(E,r,g.rotation,b.size)):A=b.extent;r=Mh(p,A,r);if(null!==this.j&&mg(this.j,r)&&this.l==n.a)B=this.G;else{var y=[ng(r),r.c-r.b+1],y=Math.max(y[0]*t[0],y[1]*t[1]),J=Math.pow(2,Math.ceil(Math.log(y)/Math.LN2)),y=B*J,L=p.Nc(q),H=L[0]+r.a*t[0]*B,B=L[1]+r.b* +t[1]*B,B=[H,B,H+y,B+y];Nq(this,b,J);f.viewport(0,0,J,J);f.clearColor(0,0,0,0);f.clear(16384);f.disable(3042);J=mq(d,this.K,this.ba);d.Ae(J);null===this.f&&(this.f=new Uq(f,J));iq(d,34962,this.C);f.enableVertexAttribArray(this.f.a);f.vertexAttribPointer(this.f.a,2,5126,!1,16,0);f.enableVertexAttribArray(this.f.b);f.vertexAttribPointer(this.f.b,2,5126,!1,16,8);f.uniform1i(this.f.c,0);d={};d[q]={};var S=this.Od(n,d),ta=k.c(),J=!0,H=Ld(),Pa=new lg(0,0,0,0),R,Aa,fb;for(Aa=r.a;Aa<=r.f;++Aa)for(fb=r.b;fb<= +r.c;++fb){L=n.cc(q,Aa,fb,v,h);if(m(c.extent)&&(R=Kh(p,L.a,H),!ie(R,c.extent)))continue;R=L.state;if(2==R){if(yh(e.b,L.pb())){d[q][eg(L.a)]=L;continue}}else if(4==R||3==R&&!ta)continue;J=!1;R=Jh(p,L.a,S,Pa,H);R||(L=Lh(p,L.a,Pa,H),null===L||S(q+1,L))}c=Ua(tb(d),Number);gb(c);for(var S=new Float32Array(4),Ia,Db,$a,ta=0,Pa=c.length;ta<Pa;++ta)for(Ia in Db=d[c[ta]],Db)L=Db[Ia],R=Kh(p,L.a,H),Aa=2*(R[2]-R[0])/y,fb=2*(R[3]-R[1])/y,$a=2*(R[0]-B[0])/y-1,R=2*(R[1]-B[1])/y-1,zd(S,Aa,fb,$a,R),f.uniform4fv(this.f.f, +S),Wq(e,L,t,z*v),f.drawArrays(5,0,4);J?(this.j=r,this.G=B,this.l=n.a):(this.G=this.j=null,this.l=-1,b.animate=!0)}xk(b.usedTiles,n,q,r);var Hc=e.l;zk(b,n,p,v,h,A,q,k.b(),function(b){var c;(c=2!=b.state||yh(e.b,b.pb()))||(c=b.pb()in Hc.c);c||Ak(Hc,[b,gg(p,b.a),p.ua(b.a[0]),t,z*v])},this);uk(b,n);wk(b,n);f=this.B;Ed(f);Hd(f,(E[0]-B[0])/(B[2]-B[0]),(E[1]-B[1])/(B[3]-B[1]));0!==g.rotation&&Jd(f,g.rotation);Id(f,b.size[0]*g.resolution/(B[2]-B[0]),b.size[1]*g.resolution/(B[3]-B[1]));Hd(f,-.5,-.5);return!0}; +l.lc=function(b,c,d,e){if(null!==this.g){var f=[0,0];qk(this.B,[b[0]/c.size[0],(c.size[1]-b[1])/c.size[1]],f);b=[f[0]*this.i,f[1]*this.i];c=this.c.g.a;c.bindFramebuffer(c.FRAMEBUFFER,this.g);f=new Uint8Array(4);c.readPixels(b[0],b[1],1,1,c.RGBA,c.UNSIGNED_BYTE,f);if(0<f[3])return d.call(e,this.b)}};function Xq(b,c){Mq.call(this,b,c);this.l=!1;this.U=-1;this.K=NaN;this.G=Ld();this.j=this.f=this.C=null}w(Xq,Mq);l=Xq.prototype;l.Zg=function(b,c,d){this.j=c;var e=b.viewState,f=this.f;if(null!==f&&!f.wa()){var g=e.center,h=e.resolution,e=e.rotation,k=b.size,n=c.opacity,p=c.brightness,q=c.contrast,r=c.hue,t=c.saturation;b=c.Qb?b.skippedFeatureUids:{};var v,B;c=0;for(v=Em.length;c<v;++c)B=f.b[Em[c]],m(B)&&uq(B,d,g,h,e,k,n,p,q,r,t,b,void 0,!1)}}; +l.X=function(){var b=this.f;null!==b&&(xq(b,this.c.g)(),this.f=null);Xq.aa.X.call(this)};l.Va=function(b,c,d,e){if(null!==this.f&&null!==this.j){var f=c.viewState,g=this.b,h=this.j,k={};return Cq(this.f,b,this.c.g,f.resolution,f.rotation,h.opacity,h.brightness,h.contrast,h.hue,h.saturation,h.Qb?c.skippedFeatureUids:{},function(b){var c=ma(b).toString();if(!(c in k))return k[c]=!0,d.call(e,b,g)})}}; +l.re=function(b,c){if(null===this.f||null===this.j)return!1;var d=c.viewState,e=this.j;return Dq(this.f,b,this.c.g,d.resolution,d.rotation,e.opacity,e.brightness,e.contrast,e.hue,e.saturation,c.skippedFeatureUids)};l.lc=function(b,c,d,e){b=b.slice();qk(c.pixelToCoordinateMatrix,b,b);if(this.re(b,c))return d.call(e,this.b)};l.cm=function(){sk(this)}; +l.Af=function(b,c,d){function e(b){var c;m(b.c)?c=b.c.call(b,p):m(f.b)&&(c=(0,f.b)(b,p));if(null!=c){if(null!=c){var d,e,g=!1;d=0;for(e=c.length;d<e;++d)g=qn(t,b,c[d],pn(p,q),this.cm,this)||g;b=g}else b=!1;this.l=this.l||b}}var f=this.b;c=f.ca();vk(b.attributions,c.g);wk(b,c);var g=b.viewHints[0],h=b.viewHints[1],k=f.A,n=f.u;if(!this.l&&!k&&g||!n&&h)return!0;var h=b.extent,k=b.viewState,g=k.projection,p=k.resolution,q=b.pixelRatio,k=f.a,r=f.c,n=f.get("renderOrder");m(n)||(n=on);h=Pd(h,r*p);if(!this.l&& +this.K==p&&this.U==k&&this.C==n&&Ud(this.G,h))return!0;null===this.f||b.postRenderFunctions.push(xq(this.f,d));this.l=!1;var t=new wq(.5*p/q,h,f.c);c.hc(h,p,g);if(null===n)c.Ib(h,p,e,this);else{var v=[];c.Ib(h,p,function(b){v.push(b)},this);gb(v,n);Sa(v,e,this)}yq(t,d);this.K=p;this.U=k;this.C=n;this.G=h;this.f=t;return!0};function Yq(b,c){Fk.call(this,0,c);this.a=Pg("CANVAS");this.a.style.width="100%";this.a.style.height="100%";this.a.className="ol-unselectable";Sg(b,this.a,0);this.u=this.G=0;this.C=Vi();this.B=!0;this.c=aj(this.a,{antialias:!0,depth:!1,failIfMajorPerformanceCaveat:!0,preserveDrawingBuffer:!1,stencil:!0});this.g=new hq(this.a,this.c);x(this.a,"webglcontextlost",this.am,!1,this);x(this.a,"webglcontextrestored",this.bm,!1,this);this.b=new xh;this.A=null;this.l=new Kk(ra(function(b){var c=b[1];b=b[2]; +var f=c[0]-this.A[0],c=c[1]-this.A[1];return 65536*Math.log(b)+Math.sqrt(f*f+c*c)/b},this),function(b){return b[0].pb()});this.K=ra(function(){if(!this.l.wa()){Ok(this.l);var b=Lk(this.l);Wq(this,b[0],b[3],b[4])}},this);this.j=0;Zq(this)}w(Yq,Fk); +function Wq(b,c,d,e){var f=b.c,g=c.pb();if(yh(b.b,g))b=b.b.get(g),f.bindTexture(3553,b.Wa),9729!=b.Fg&&(f.texParameteri(3553,10240,9729),b.Fg=9729),9729!=b.Gg&&(f.texParameteri(3553,10240,9729),b.Gg=9729);else{var h=f.createTexture();f.bindTexture(3553,h);if(0<e){var k=b.C.canvas,n=b.C;b.G!==d[0]||b.u!==d[1]?(k.width=d[0],k.height=d[1],b.G=d[0],b.u=d[1]):n.clearRect(0,0,d[0],d[1]);n.drawImage(c.Ta(),e,e,d[0],d[1],0,0,d[0],d[1]);f.texImage2D(3553,0,6408,6408,5121,k)}else f.texImage2D(3553,0,6408,6408, +5121,c.Ta());f.texParameteri(3553,10240,9729);f.texParameteri(3553,10241,9729);f.texParameteri(3553,10242,33071);f.texParameteri(3553,10243,33071);b.b.set(g,{Wa:h,Fg:9729,Gg:9729})}}l=Yq.prototype;l.Ze=function(b){return b instanceof I?new Pq(this,b):b instanceof K?new Vq(this,b):b instanceof M?new Xq(this,b):null}; +function $q(b,c,d){var e=b.i;if(cd(e,c)){var f=b.g;b=d.viewState;b=new Eq(f,b.center,b.resolution,b.rotation,d.size,d.extent);C(e,new kk(c,e,b,d,null,f));c=Ua(tb(b.a),Number);gb(c);var g,h;d=0;for(e=c.length;d<e;++d)for(f=b.a[c[d].toString()],g=0,h=f.length;g<h;++g)f[g](b)}}l.X=function(){var b=this.c;b.isContextLost()||this.b.forEach(function(c){null===c||b.deleteTexture(c.Wa)});rc(this.g);Yq.aa.X.call(this)}; +l.Xi=function(b,c){for(var d=this.c,e;1024<this.b.ac()-this.j;){e=this.b.a.zc;if(null===e)if(+this.b.a.ie==c.index)break;else--this.j;else d.deleteTexture(e.Wa);this.b.pop()}};l.V=function(){return"webgl"};l.am=function(b){b.preventDefault();this.b.clear();this.j=0;pb(this.f,function(b){b.zf()})};l.bm=function(){Zq(this);this.i.render()};function Zq(b){b=b.c;b.activeTexture(33984);b.blendFuncSeparate(770,771,1,771);b.disable(2884);b.disable(2929);b.disable(3089);b.disable(2960)} +l.Ge=function(b){var c=this.g,d=this.c;if(d.isContextLost())return!1;if(null===b)return this.B&&(oh(this.a,!1),this.B=!1),!1;this.A=b.focus;this.b.set((-b.index).toString(),null);++this.j;$q(this,"precompose",b);var e=[],f=b.layerStatesArray,g=b.viewState.resolution,h,k,n,p;h=0;for(k=f.length;h<k;++h)p=f[h],lk(p,g)&&"ready"==p.l&&(n=Ik(this,p.layer),n.Af(b,p,c)&&e.push(p));f=b.size[0]*b.pixelRatio;g=b.size[1]*b.pixelRatio;if(this.a.width!=f||this.a.height!=g)this.a.width=f,this.a.height=g;d.bindFramebuffer(36160, +null);d.clearColor(0,0,0,0);d.clear(16384);d.enable(3042);d.viewport(0,0,this.a.width,this.a.height);h=0;for(k=e.length;h<k;++h)p=e[h],n=Ik(this,p.layer),n.Zg(b,p,c);this.B||(oh(this.a,!0),this.B=!0);Gk(b);1024<this.b.ac()-this.j&&b.postRenderFunctions.push(ra(this.Xi,this));this.l.wa()||(b.postRenderFunctions.push(this.K),b.animate=!0);$q(this,"postcompose",b);Jk(this,b);b.postRenderFunctions.push(Hk)}; +l.yf=function(b,c,d,e,f,g){var h;if(this.c.isContextLost())return!1;var k=c.viewState,n=c.layerStatesArray,p;for(p=n.length-1;0<=p;--p){h=n[p];var q=h.layer;if(lk(h,k.resolution)&&f.call(g,q)&&(h=Ik(this,q).Va(b,c,d,e)))return h}};l.Yg=function(b,c,d,e){var f=!1;if(this.c.isContextLost())return!1;var g=c.viewState,h=c.layerStatesArray,k;for(k=h.length-1;0<=k;--k){var n=h[k],p=n.layer;if(lk(n,g.resolution)&&d.call(e,p)&&(f=Ik(this,p).re(b,c)))return!0}return f}; +l.Xg=function(b,c,d,e,f){if(this.c.isContextLost())return!1;var g=c.viewState,h,k=c.layerStatesArray,n;for(n=k.length-1;0<=n;--n){h=k[n];var p=h.layer;if(lk(h,g.resolution)&&f.call(e,p)&&(h=Ik(this,p).lc(b,c,d,e)))return h}};var ar=["canvas","webgl","dom"]; +function X(b){gd.call(this);var c=br(b);this.sb=m(b.loadTilesWhileAnimating)?b.loadTilesWhileAnimating:!1;this.tb=m(b.loadTilesWhileInteracting)?b.loadTilesWhileInteracting:!1;this.Xc=m(b.pixelRatio)?b.pixelRatio:cj;this.ub=c.logos;this.A=new li(this.fo,void 0,this);qc(this,this.A);this.Xa=Ad();this.Qe=Ad();this.rb=0;this.c=null;this.xa=Ld();this.j=this.K=null;this.b=Mg("DIV","ol-viewport");this.b.style.position="relative";this.b.style.overflow="hidden";this.b.style.width="100%";this.b.style.height= +"100%";this.b.style.msTouchAction="none";hj&&ah(this.b,"ol-touch");this.ea=Mg("DIV","ol-overlaycontainer");this.b.appendChild(this.ea);this.G=Mg("DIV","ol-overlaycontainer-stopevent");x(this.G,["click","dblclick","mousedown","touchstart","MSPointerDown",dk,Kb?"DOMMouseScroll":"mousewheel"],tc);this.b.appendChild(this.G);b=new Vj(this);x(b,sb(gk),this.yg,!1,this);qc(this,b);this.$=c.keyboardEventTarget;this.u=new Fi;x(this.u,"key",this.wg,!1,this);qc(this,this.u);b=new Ni(this.b);x(b,"mousewheel", +this.wg,!1,this);qc(this,b);this.g=c.controls;this.f=c.interactions;this.i=c.overlays;this.l=new c.io(this.b,this);qc(this,this.l);this.Na=new Ai;qc(this,this.Na);this.U=this.v=null;this.C=[];this.qa=[];this.ba=new Pk(ra(this.Rj,this),ra(this.ml,this));this.T={};x(this,id("layergroup"),this.fk,!1,this);x(this,id("view"),this.Bk,!1,this);x(this,id("size"),this.yk,!1,this);x(this,id("target"),this.Ak,!1,this);this.I(c.values);this.g.forEach(function(b){b.setMap(this)},this);x(this.g,"add",function(b){b.element.setMap(this)}, +!1,this);x(this.g,"remove",function(b){b.element.setMap(null)},!1,this);this.f.forEach(function(b){b.setMap(this)},this);x(this.f,"add",function(b){b.element.setMap(this)},!1,this);x(this.f,"remove",function(b){b.element.setMap(null)},!1,this);this.i.forEach(function(b){b.setMap(this)},this);x(this.i,"add",function(b){b.element.setMap(this)},!1,this);x(this.i,"remove",function(b){b.element.setMap(null)},!1,this)}w(X,gd);l=X.prototype;l.Li=function(b){this.g.push(b)};l.Mi=function(b){this.f.push(b)}; +l.bg=function(b){this.bc().Kc().push(b)};l.cg=function(b){this.i.push(b)};l.Oa=function(b){this.render();Array.prototype.push.apply(this.C,arguments)};l.X=function(){Tg(this.b);X.aa.X.call(this)};l.bf=function(b,c,d,e,f){if(null!==this.c)return b=this.ta(b),this.l.yf(b,this.c,c,m(d)?d:null,m(e)?e:pe,m(f)?f:null)};l.ll=function(b,c,d,e,f){if(null!==this.c)return this.l.Xg(b,this.c,c,m(d)?d:null,m(e)?e:pe,m(f)?f:null)}; +l.Dk=function(b,c,d){if(null===this.c)return!1;b=this.ta(b);return this.l.Yg(b,this.c,m(c)?c:pe,m(d)?d:null)};l.mj=function(b){return this.ta(this.Rd(b))};l.Rd=function(b){if(m(b.changedTouches)){var c=b.changedTouches[0];b=lh(this.b);return[c.clientX-b.x,c.clientY-b.y]}c=this.b;b=lh(b);c=lh(c);c=new Dg(b.x-c.x,b.y-c.y);return[c.x,c.y]};l.vf=function(){return this.get("target")};l.od=function(){var b=this.vf();return m(b)?Ig(b):null}; +l.ta=function(b){var c=this.c;if(null===c)return null;b=b.slice();return qk(c.pixelToCoordinateMatrix,b,b)};l.kj=function(){return this.g};l.Gj=function(){return this.i};l.uj=function(){return this.f};l.bc=function(){return this.get("layergroup")};l.Mg=function(){return this.bc().Kc()};l.ya=function(b){var c=this.c;if(null===c)return null;b=b.slice(0,2);return qk(c.coordinateToPixelMatrix,b,b)};l.Ca=function(){return this.get("size")};l.Y=function(){return this.get("view")};l.Tj=function(){return this.b}; +l.Rj=function(b,c,d,e){var f=this.c;if(!(null!==f&&c in f.wantedTiles&&f.wantedTiles[c][eg(b.a)]))return Infinity;b=d[0]-f.focus[0];d=d[1]-f.focus[1];return 65536*Math.log(e)+Math.sqrt(b*b+d*d)/e};l.wg=function(b,c){var d=new Tj(c||b.type,this,b);this.yg(d)};l.yg=function(b){if(null!==this.c){this.U=b.coordinate;b.frameState=this.c;var c=this.f.b,d;if(!1!==C(this,b))for(d=c.length-1;0<=d;d--){var e=c[d];if(e.b()&&!e.handleEvent(b))break}}}; +l.vk=function(){var b=this.c,c=this.ba;if(!c.wa()){var d=16,e=d,f=0;null!==b&&(f=b.viewHints,f[0]&&(d=this.sb?8:0,e=2),f[1]&&(d=this.tb?8:0,e=2),f=rb(b.wantedTiles));d*=f;e*=f;c.f<d&&(Ok(c),Qk(c,d,e))}c=this.qa;d=0;for(e=c.length;d<e;++d)c[d](this,b);c.length=0};l.yk=function(){this.render()}; +l.Ak=function(){var b=this.od();Mi(this.u);null===b?(Tg(this.b),null!==this.v&&(Xc(this.v),this.v=null)):(b.appendChild(this.b),Gi(this.u,null===this.$?b:this.$),null===this.v&&(this.v=x(this.Na,"resize",this.Uc,!1,this)));this.Uc()};l.ml=function(){this.render()};l.Ck=function(){this.render()};l.Bk=function(){null!==this.K&&(Xc(this.K),this.K=null);var b=this.Y();null!==b&&(this.K=x(b,"propertychange",this.Ck,!1,this));this.render()};l.gk=function(){this.render()};l.hk=function(){this.render()}; +l.fk=function(){if(null!==this.j){for(var b=this.j.length,c=0;c<b;++c)Xc(this.j[c]);this.j=null}b=this.bc();null!=b&&(this.j=[x(b,"propertychange",this.hk,!1,this),x(b,"change",this.gk,!1,this)]);this.render()};l.ho=function(){var b=this.A;mi(b);b.hg()};l.render=function(){null!=this.A.ha||this.A.start()};l.$n=function(b){if(m(this.g.remove(b)))return b};l.ao=function(b){var c;m(this.f.remove(b))&&(c=b);return c};l.bo=function(b){return this.bc().Kc().remove(b)};l.co=function(b){if(m(this.i.remove(b)))return b}; +l.fo=function(b){var c,d,e,f=this.Ca(),g=this.Y(),h=null;if(m(f)&&0<f[0]&&0<f[1]&&null!==g&&Sf(g)){var h=g.c.slice(),k=this.bc().lf(),n={};c=0;for(d=k.length;c<d;++c)n[ma(k[c].layer)]=k[c];e=Rf(g);h={animate:!1,attributions:{},coordinateToPixelMatrix:this.Xa,extent:null,focus:null===this.U?e.center:this.U,index:this.rb++,layerStates:n,layerStatesArray:k,logos:Cb(this.ub),pixelRatio:this.Xc,pixelToCoordinateMatrix:this.Qe,postRenderFunctions:[],size:f,skippedFeatureUids:this.T,tileQueue:this.ba,time:b, +usedTiles:{},viewState:e,viewHints:h,wantedTiles:{}}}if(null!==h){b=this.C;c=f=0;for(d=b.length;c<d;++c)g=b[c],g(this,h)&&(b[f++]=g);b.length=f;h.extent=fe(e.center,e.resolution,e.rotation,h.size)}this.c=h;this.l.Ge(h);null!==h&&(h.animate&&this.render(),Array.prototype.push.apply(this.qa,h.postRenderFunctions),0!==this.C.length||h.viewHints[0]||h.viewHints[1]||Xd(h.extent,this.xa)||(C(this,new vh("moveend",this,h)),Qd(h.extent,this.xa)));C(this,new vh("postrender",this,h));qi(this.vk,this)}; +l.Kh=function(b){this.set("layergroup",b)};l.Mf=function(b){this.set("size",b)};l.nl=function(b){this.set("target",b)};l.zo=function(b){this.set("view",b)};l.Ph=function(b){b=ma(b).toString();this.T[b]=!0;this.render()}; +l.Uc=function(){var b=this.od();if(null===b)this.Mf(void 0);else{var c=Hg(b),d=Jb&&b.currentStyle;d&&Xg(Fg(c))&&"auto"!=d.width&&"auto"!=d.height&&!d.boxSizing?(c=ph(b,d.width,"width","pixelWidth"),b=ph(b,d.height,"height","pixelHeight"),b=new Eg(c,b)):(d=new Eg(b.offsetWidth,b.offsetHeight),c=rh(b,"padding"),b=uh(b),b=new Eg(d.width-b.left-c.left-c.right-b.right,d.height-b.top-c.top-c.bottom-b.bottom));this.Mf([b.width,b.height])}};l.Uh=function(b){b=ma(b).toString();delete this.T[b];this.render()}; +function br(b){var c=null;m(b.keyboardEventTarget)&&(c=ia(b.keyboardEventTarget)?document.getElementById(b.keyboardEventTarget):b.keyboardEventTarget);var d={},e={};if(!m(b.logo)||"boolean"==typeof b.logo&&b.logo)e["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszWWMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvYasvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvXH1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1VkbMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLPVcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqTacrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaarldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+HizeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDnBAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSFhYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJREFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxCBrb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7ahgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCnB3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDgq82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC"]= +"http://openlayers.org/";else{var f=b.logo;ia(f)?e[f]="":la(f)&&(e[f.src]=f.href)}f=b.layers instanceof dm?b.layers:new dm({layers:b.layers});d.layergroup=f;d.target=b.target;d.view=m(b.view)?b.view:new Of;var f=Fk,g;m(b.renderer)?ga(b.renderer)?g=b.renderer:ia(b.renderer)&&(g=[b.renderer]):g=ar;var h,k;h=0;for(k=g.length;h<k;++h){var n=g[h];if("canvas"==n){if(ej){f=Op;break}}else if("dom"==n){f=Wp;break}else if("webgl"==n&&bj){f=Yq;break}}var p;m(b.controls)?p=ga(b.controls)?new rg(b.controls.slice()): +b.controls:p=ci();var q;m(b.interactions)?q=ga(b.interactions)?new rg(b.interactions.slice()):b.interactions:q=cm();b=m(b.overlays)?ga(b.overlays)?new rg(b.overlays.slice()):b.overlays:new rg;return{controls:p,interactions:q,keyboardEventTarget:c,logos:e,overlays:b,io:f,values:d}}km();function cr(b){gd.call(this);this.l=m(b.insertFirst)?b.insertFirst:!0;this.v=m(b.stopEvent)?b.stopEvent:!0;this.c=Mg("DIV",{"class":"ol-overlay-container"});this.c.style.position="absolute";this.j=m(b.autoPan)?b.autoPan:!1;this.g=m(b.autoPanAnimation)?b.autoPanAnimation:{};this.i=m(b.autoPanMargin)?b.autoPanMargin:20;this.b={Kd:"",je:"",He:"",Ie:"",visible:!0};this.f=null;x(this,id("element"),this.bk,!1,this);x(this,id("map"),this.mk,!1,this);x(this,id("offset"),this.rk,!1,this);x(this,id("position"), +this.tk,!1,this);x(this,id("positioning"),this.uk,!1,this);m(b.element)&&this.Hh(b.element);this.Mh(m(b.offset)?b.offset:[0,0]);this.Nh(m(b.positioning)?b.positioning:"top-left");m(b.position)&&this.wf(b.position)}w(cr,gd);l=cr.prototype;l.me=function(){return this.get("element")};l.ne=function(){return this.get("map")};l.rg=function(){return this.get("offset")};l.Ng=function(){return this.get("position")};l.ug=function(){return this.get("positioning")}; +l.bk=function(){Rg(this.c);var b=this.me();null!=b&&Qg(this.c,b)};l.mk=function(){null!==this.f&&(Tg(this.c),Xc(this.f),this.f=null);var b=this.ne();null!=b&&(this.f=x(b,"postrender",this.render,!1,this),dr(this),b=this.v?b.G:b.ea,this.l?Sg(b,this.c,0):Qg(b,this.c))};l.render=function(){dr(this)};l.rk=function(){dr(this)}; +l.tk=function(){dr(this);if(m(this.get("position"))&&this.j){var b=this.ne();if(m(b)&&!fa(b.od())){var c=er(b.od(),b.Ca()),d=this.me(),e=d.offsetWidth,f=d.currentStyle||window.getComputedStyle(d),e=e+(parseInt(f.marginLeft,10)+parseInt(f.marginRight,10)),f=d.offsetHeight,g=d.currentStyle||window.getComputedStyle(d),f=f+(parseInt(g.marginTop,10)+parseInt(g.marginBottom,10)),h=er(d,[e,f]),d=this.i;Ud(c,h)||(e=h[0]-c[0],f=c[2]-h[2],g=h[1]-c[1],h=c[3]-h[3],c=[0,0],0>e?c[0]=e-d:0>f&&(c[0]=Math.abs(f)+ +d),0>g?c[1]=g-d:0>h&&(c[1]=Math.abs(h)+d),0===c[0]&&0===c[1])||(d=b.Y().Ka(),e=b.ya(d),c=[e[0]+c[0],e[1]+c[1]],null!==this.g&&(this.g.source=d,b.Oa(Zf(this.g))),b.Y().eb(b.ta(c)))}}};l.uk=function(){dr(this)};l.Hh=function(b){this.set("element",b)};l.setMap=function(b){this.set("map",b)};l.Mh=function(b){this.set("offset",b)};l.wf=function(b){this.set("position",b)}; +function er(b,c){var d=Hg(b);hh(b,"position");var e=new Dg(0,0),f;f=d?Hg(d):document;f=!Jb||Jb&&9<=Ub||Xg(Fg(f))?f.documentElement:f.body;b!=f&&(f=kh(b),d=Yg(Fg(d)),e.x=f.left+d.x,e.y=f.top+d.y);return[e.x,e.y,e.x+c[0],e.y+c[1]]}l.Nh=function(b){this.set("positioning",b)}; +function dr(b){var c=b.ne(),d=b.Ng();if(m(c)&&null!==c.c&&m(d)){var d=c.ya(d),e=c.Ca(),c=b.c.style,f=b.rg(),g=b.ug(),h=f[0],f=f[1];if("bottom-right"==g||"center-right"==g||"top-right"==g)""!==b.b.je&&(b.b.je=c.left=""),h=Math.round(e[0]-d[0]-h)+"px",b.b.He!=h&&(b.b.He=c.right=h);else{""!==b.b.He&&(b.b.He=c.right="");if("bottom-center"==g||"center-center"==g||"top-center"==g)h-=mh(b.c).width/2;h=Math.round(d[0]+h)+"px";b.b.je!=h&&(b.b.je=c.left=h)}if("bottom-left"==g||"bottom-center"==g||"bottom-right"== +g)""!==b.b.Ie&&(b.b.Ie=c.top=""),d=Math.round(e[1]-d[1]-f)+"px",b.b.Kd!=d&&(b.b.Kd=c.bottom=d);else{""!==b.b.Kd&&(b.b.Kd=c.bottom="");if("center-left"==g||"center-center"==g||"center-right"==g)f-=mh(b.c).height/2;d=Math.round(d[1]+f)+"px";b.b.Ie!=d&&(b.b.Ie=c.top=d)}b.b.visible||(oh(b.c,!0),b.b.visible=!0)}else b.b.visible&&(oh(b.c,!1),b.b.visible=!1)};function fr(b){b=m(b)?b:{};this.i=m(b.collapsed)?b.collapsed:!0;this.j=m(b.collapsible)?b.collapsible:!0;this.j||(this.i=!1);var c=m(b.className)?b.className:"ol-overviewmap",d=m(b.tipLabel)?b.tipLabel:"Overview map",e=m(b.collapseLabel)?b.collapseLabel:"\u00ab";this.A=ia(e)?Mg("SPAN",{},e):e;e=m(b.label)?b.label:"\u00bb";this.u=ia(e)?Mg("SPAN",{},e):e;d=Mg("BUTTON",{type:"button",title:d},this.j&&!this.i?this.A:this.u);x(d,"click",this.xl,!1,this);var e=Mg("DIV","ol-overviewmap-map"),f=this.f=new X({controls:new rg, +interactions:new rg,target:e});m(b.layers)&&b.layers.forEach(function(b){f.bg(b)},this);var g=Mg("DIV","ol-overviewmap-box");this.l=new cr({position:[0,0],positioning:"bottom-left",element:g});this.f.cg(this.l);c=Mg("DIV",c+" ol-unselectable ol-control"+(this.i&&this.j?" ol-collapsed":"")+(this.j?"":" ol-uncollapsible"),e,d);wh.call(this,{element:c,render:m(b.render)?b.render:gr,target:b.target})}w(fr,wh);l=fr.prototype; +l.setMap=function(b){var c=this.b;b!==c&&(c&&(c=c.Y())&&Wc(c,id("rotation"),this.de,!1,this),fr.aa.setMap.call(this,b),b&&(this.v.push(x(b,"propertychange",this.nk,!1,this)),0===this.f.Mg().Rb()&&this.f.Kh(b.bc()),b=b.Y()))&&(x(b,id("rotation"),this.de,!1,this),Sf(b)&&(this.f.Uc(),hr(this)))};l.nk=function(b){"view"===b.key&&((b=b.oldValue)&&Wc(b,id("rotation"),this.de,!1,this),b=this.b.Y(),x(b,id("rotation"),this.de,!1,this))};l.de=function(){this.f.Y().oe(this.b.Y().Ea())}; +function gr(){var b=this.b,c=this.f;if(null!==b.c&&null!==c.c){var d=b.Ca(),b=b.Y().Zc(d),e=c.Ca(),d=c.Y().Zc(e),f=c.ya(de(b)),c=c.ya(be(b)),c=new Eg(Math.abs(f[0]-c[0]),Math.abs(f[1]-c[1])),f=e[0],e=e[1];c.width<.1*f||c.height<.1*e||c.width>.75*f||c.height>.75*e?hr(this):Ud(d,b)||(b=this.f,d=this.b.Y(),b.Y().eb(d.Ka()))}ir(this)}function hr(b){var c=b.b;b=b.f;var d=c.Ca(),c=c.Y().Zc(d),d=b.Ca();b=b.Y();var e=Math.log(7.5)/Math.LN2;le(c,1/(.1*Math.pow(2,e/2)));b.af(c,d)} +function ir(b){var c=b.b,d=b.f;if(null!==c.c&&null!==d.c){var e=c.Ca(),f=c.Y(),g=d.Y();d.Ca();var c=f.Ea(),h=b.l,d=b.l.me(),f=f.Zc(e),e=g.Da(),g=ae(f),f=ce(f),k;b=b.b.Y().Ka();m(b)&&(k=[g[0]-b[0],g[1]-b[1]],sd(k,c),nd(k,b));h.wf(k);null!=d&&(k=new Eg(Math.abs((g[0]-f[0])/e),Math.abs((f[1]-g[1])/e)),c=Xg(Fg(Hg(d))),!Jb||Sb("10")||c&&Sb("8")?(d=d.style,Kb?d.MozBoxSizing="border-box":Lb?d.WebkitBoxSizing="border-box":d.boxSizing="border-box",d.width=Math.max(k.width,0)+"px",d.height=Math.max(k.height, +0)+"px"):(b=d.style,c?(c=rh(d,"padding"),d=uh(d),b.pixelWidth=k.width-d.left-c.left-c.right-d.right,b.pixelHeight=k.height-d.top-c.top-c.bottom-d.bottom):(b.pixelWidth=k.width,b.pixelHeight=k.height)))}}l.xl=function(b){b.preventDefault();jr(this)};function jr(b){ch(b.element,"ol-collapsed");b.i?Ug(b.A,b.u):Ug(b.u,b.A);b.i=!b.i;var c=b.f;b.i||null!==c.c||(c.Uc(),hr(b),Vc(c,"postrender",function(){ir(this)},!1,b))}l.wl=function(){return this.j}; +l.zl=function(b){this.j!==b&&(this.j=b,ch(this.element,"ol-uncollapsible"),!b&&this.i&&jr(this))};l.yl=function(b){this.j&&this.i!==b&&jr(this)};l.vl=function(){return this.i};function kr(b){b=m(b)?b:{};var c=m(b.className)?b.className:"ol-scale-line";this.l=Mg("DIV",c+"-inner");this.j=Mg("DIV",c+" ol-unselectable",this.l);this.u=null;this.A=m(b.minWidth)?b.minWidth:64;this.f=!1;this.K=void 0;this.G="";this.i=null;wh.call(this,{element:this.j,render:m(b.render)?b.render:lr,target:b.target});x(this,id("units"),this.$,!1,this);this.T(b.units||"metric")}w(kr,wh);var mr=[1,2,5];kr.prototype.C=function(){return this.get("units")}; +function lr(b){b=b.frameState;null===b?this.u=null:this.u=b.viewState;nr(this)}kr.prototype.$=function(){nr(this)};kr.prototype.T=function(b){this.set("units",b)}; +function nr(b){var c=b.u;if(null===c)b.f&&(oh(b.j,!1),b.f=!1);else{var d=c.center,e=c.projection,c=e.getPointResolution(c.resolution,d),f=e.b,g=b.C();"degrees"!=f||"metric"!=g&&"imperial"!=g&&"us"!=g&&"nautical"!=g?"degrees"!=f&&"degrees"==g?(null===b.i&&(b.i=Ee(e,Ae("EPSG:4326"))),d=Math.cos(Zb(b.i(d)[1])),e=we.radius,e/=xe[f],c*=180/(Math.PI*d*e)):b.i=null:(b.i=null,d=Math.cos(Zb(d[1])),c*=Math.PI*d*we.radius/180);d=b.A*c;f="";"degrees"==g?d<1/60?(f="\u2033",c*=3600):1>d?(f="\u2032",c*=60):f="\u00b0": +"imperial"==g?.9144>d?(f="in",c/=.0254):1609.344>d?(f="ft",c/=.3048):(f="mi",c/=1609.344):"nautical"==g?(c/=1852,f="nm"):"metric"==g?1>d?(f="mm",c*=1E3):1E3>d?f="m":(f="km",c/=1E3):"us"==g&&(.9144>d?(f="in",c*=39.37):1609.344>d?(f="ft",c/=.30480061):(f="mi",c/=1609.3472));for(d=3*Math.floor(Math.log(b.A*c)/Math.log(10));;){e=mr[d%3]*Math.pow(10,Math.floor(d/3));g=Math.round(e/c);if(isNaN(g)){oh(b.j,!1);b.f=!1;return}if(g>=b.A)break;++d}c=e+" "+f;b.G!=c&&(b.l.innerHTML=c,b.G=c);b.K!=g&&(b.l.style.width= +g+"px",b.K=g);b.f||(oh(b.j,!0),b.f=!0)}};function or(b){nc.call(this);this.b=b;this.a={}}w(or,nc);var pr=[];or.prototype.Ra=function(b,c,d,e){ga(c)||(c&&(pr[0]=c.toString()),c=pr);for(var f=0;f<c.length;f++){var g=x(b,c[f],d||this.handleEvent,e||!1,this.b||this);if(!g)break;this.a[g.key]=g}return this}; +or.prototype.Nf=function(b,c,d,e,f){if(ga(c))for(var g=0;g<c.length;g++)this.Nf(b,c[g],d,e,f);else d=d||this.handleEvent,f=f||this.b||this,d=Pc(d),e=!!e,c=Cc(b)?Kc(b.jb,String(c),d,e,f):b?(b=Rc(b))?Kc(b,c,d,e,f):null:null,c&&(Xc(c),delete this.a[c.key]);return this};function qr(b){pb(b.a,Xc);b.a={}}or.prototype.X=function(){or.aa.X.call(this);qr(this)};or.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};function rr(b,c,d){ad.call(this);this.target=b;this.handle=c||b;this.a=d||new eh(NaN,NaN,NaN,NaN);this.c=Hg(b);this.b=new or(this);qc(this,this.b);x(this.handle,["touchstart","mousedown"],this.Qh,!1,this)}w(rr,ad);var sr=Jb||Kb&&Sb("1.9.3");l=rr.prototype;l.clientX=0;l.clientY=0;l.screenX=0;l.screenY=0;l.Rh=0;l.Sh=0;l.Hc=0;l.Ic=0;l.fc=!1;l.X=function(){rr.aa.X.call(this);Wc(this.handle,["touchstart","mousedown"],this.Qh,!1,this);qr(this.b);sr&&this.c.releaseCapture();this.handle=this.target=null}; +l.Qh=function(b){var c="mousedown"==b.type;if(this.fc||c&&!Ac(b))C(this,"earlycancel");else if(tr(b),C(this,new ur("start",this,b.clientX,b.clientY))){this.fc=!0;b.preventDefault();var c=this.c,d=c.documentElement,e=!sr;this.b.Ra(c,["touchmove","mousemove"],this.qk,e);this.b.Ra(c,["touchend","mouseup"],this.Qd,e);sr?(d.setCapture(!1),this.b.Ra(d,"losecapture",this.Qd)):this.b.Ra(c?c.parentWindow||c.defaultView:window,"blur",this.Qd);this.g&&this.b.Ra(this.g,"scroll",this.tn,e);this.clientX=this.Rh= +b.clientX;this.clientY=this.Sh=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;this.Hc=this.target.offsetLeft;this.Ic=this.target.offsetTop;this.f=Yg(Fg(this.c));ua()}};l.Qd=function(b){qr(this.b);sr&&this.c.releaseCapture();if(this.fc){tr(b);this.fc=!1;var c=vr(this,this.Hc),d=wr(this,this.Ic);C(this,new ur("end",this,b.clientX,b.clientY,0,c,d))}else C(this,"earlycancel")}; +function tr(b){var c=b.type;"touchstart"==c||"touchmove"==c?yc(b,b.a.targetTouches[0],b.c):"touchend"!=c&&"touchcancel"!=c||yc(b,b.a.changedTouches[0],b.c)} +l.qk=function(b){tr(b);var c=1*(b.clientX-this.clientX),d=b.clientY-this.clientY;this.clientX=b.clientX;this.clientY=b.clientY;this.screenX=b.screenX;this.screenY=b.screenY;if(!this.fc){var e=this.Rh-this.clientX,f=this.Sh-this.clientY;if(0<e*e+f*f)if(C(this,new ur("start",this,b.clientX,b.clientY)))this.fc=!0;else{this.fa||this.Qd(b);return}}d=xr(this,c,d);c=d.x;d=d.y;this.fc&&C(this,new ur("beforedrag",this,b.clientX,b.clientY,0,c,d))&&(yr(this,b,c,d),b.preventDefault())}; +function xr(b,c,d){var e=Yg(Fg(b.c));c+=e.x-b.f.x;d+=e.y-b.f.y;b.f=e;b.Hc+=c;b.Ic+=d;c=vr(b,b.Hc);b=wr(b,b.Ic);return new Dg(c,b)}l.tn=function(b){var c=xr(this,0,0);b.clientX=this.clientX;b.clientY=this.clientY;yr(this,b,c.x,c.y)};function yr(b,c,d,e){b.target.style.left=d+"px";b.target.style.top=e+"px";C(b,new ur("drag",b,c.clientX,c.clientY,0,d,e))} +function vr(b,c){var d=b.a,e=isNaN(d.left)?null:d.left,d=isNaN(d.width)?0:d.width;return Math.min(null!=e?e+d:Infinity,Math.max(null!=e?e:-Infinity,c))}function wr(b,c){var d=b.a,e=isNaN(d.top)?null:d.top,d=isNaN(d.height)?0:d.height;return Math.min(null!=e?e+d:Infinity,Math.max(null!=e?e:-Infinity,c))}function ur(b,c,d,e,f,g,h){sc.call(this,b);this.clientX=d;this.clientY=e;this.left=m(g)?g:c.Hc;this.top=m(h)?h:c.Ic}w(ur,sc);function zr(b){b=m(b)?b:{};this.i=void 0;this.j=Ar;this.l=null;this.u=!1;this.A=m(b.duration)?b.duration:200;var c=m(b.className)?b.className:"ol-zoomslider",d=Mg("DIV",[c+"-thumb","ol-unselectable"]),c=Mg("DIV",[c,"ol-unselectable","ol-control"],d);this.f=new rr(d);qc(this,this.f);x(this.f,"start",this.ak,!1,this);x(this.f,"drag",this.Zj,!1,this);x(this.f,"end",this.$j,!1,this);x(c,"click",this.Yj,!1,this);x(d,"click",tc);wh.call(this,{element:c,render:m(b.render)?b.render:Br})}w(zr,wh);var Ar=0; +l=zr.prototype;l.setMap=function(b){zr.aa.setMap.call(this,b);null===b||b.render()}; +function Br(b){if(null!==b.frameState){if(!this.u){var c=this.element,d=mh(c),e=Vg(c),c=rh(e,"margin"),f=new Eg(e.offsetWidth,e.offsetHeight),e=f.width+c.right+c.left,c=f.height+c.top+c.bottom;this.l=[e,c];e=d.width-e;c=d.height-c;d.width>d.height?(this.j=1,d=new eh(0,0,e,0)):(this.j=Ar,d=new eh(0,0,0,c));this.f.a=d||new eh(NaN,NaN,NaN,NaN);this.u=!0}b=b.frameState.viewState.resolution;b!==this.i&&(this.i=b,b=1-Qf(this.b.Y())(b),d=this.f,c=Vg(this.element),1==this.j?ih(c,d.a.left+d.a.width*b):ih(c, +d.a.left,d.a.top+d.a.height*b))}}l.Yj=function(b){var c=this.b,d=c.Y(),e=d.Da();c.Oa(ag({resolution:e,duration:this.A,easing:Vf}));b=Cr(this,b.offsetX-this.l[0]/2,b.offsetY-this.l[1]/2);b=Dr(this,b);d.Yb(d.constrainResolution(b))};l.ak=function(){Uf(this.b.Y(),1)};l.Zj=function(b){b=Cr(this,b.left,b.top);this.i=Dr(this,b);this.b.Y().Yb(this.i)};l.$j=function(){var b=this.b,c=b.Y();Uf(c,-1);b.Oa(ag({resolution:this.i,duration:this.A,easing:Vf}));b=c.constrainResolution(this.i);c.Yb(b)}; +function Cr(b,c,d){var e=b.f.a;return Wb(1===b.j?(c-e.left)/e.width:(d-e.top)/e.height,0,1)}function Dr(b,c){return Pf(b.b.Y())(1-c)};function Er(b){b=m(b)?b:{};this.f=m(b.extent)?b.extent:null;var c=m(b.className)?b.className:"ol-zoom-extent",d=Mg("BUTTON",{type:"button",title:m(b.tipLabel)?b.tipLabel:"Fit to extent"},m(b.label)?b.label:"E");x(d,"click",this.i,!1,this);c=Mg("DIV",c+" ol-unselectable ol-control",d);wh.call(this,{element:c,target:b.target})}w(Er,wh);Er.prototype.i=function(b){b.preventDefault();var c=this.b;b=c.Y();var d=null===this.f?b.g.R():this.f,c=c.Ca();b.af(d,c)};function Fr(b){gd.call(this);b=m(b)?b:{};this.b=null;x(this,id("tracking"),this.$k,!1,this);this.tf(m(b.tracking)?b.tracking:!1)}w(Fr,gd);l=Fr.prototype;l.X=function(){this.tf(!1);Fr.aa.X.call(this)}; +l.vn=function(b){b=b.a;if(null!=b.alpha){var c=Zb(b.alpha);this.set("alpha",c);"boolean"==typeof b.absolute&&b.absolute?this.set("heading",c):null!=b.webkitCompassHeading&&null!=b.webkitCompassAccuracy&&-1!=b.webkitCompassAccuracy&&this.set("heading",Zb(b.webkitCompassHeading))}null!=b.beta&&this.set("beta",Zb(b.beta));null!=b.gamma&&this.set("gamma",Zb(b.gamma));this.s()};l.ej=function(){return this.get("alpha")};l.hj=function(){return this.get("beta")};l.qj=function(){return this.get("gamma")}; +l.Zk=function(){return this.get("heading")};l.Ig=function(){return this.get("tracking")};l.$k=function(){if(fj){var b=this.Ig();b&&null===this.b?this.b=x(ba,"deviceorientation",this.vn,!1,this):b||null===this.b||(Xc(this.b),this.b=null)}};l.tf=function(b){this.set("tracking",b)};function Gr(){this.defaultDataProjection=null}function Hr(b,c,d){var e;m(d)&&(e={dataProjection:m(d.dataProjection)?d.dataProjection:b.Ga(c),featureProjection:d.featureProjection});return Ir(b,e)}function Ir(b,c){var d;m(c)&&(d={featureProjection:c.featureProjection,dataProjection:null!=c.dataProjection?c.dataProjection:b.defaultDataProjection,rightHanded:c.rightHanded});return d} +function Jr(b,c,d){var e=m(d)?Ae(d.featureProjection):null;d=m(d)?Ae(d.dataProjection):null;return null===e||null===d||Se(e,d)?b:b instanceof Xe?(c?b.clone():b).transform(c?e:d,c?d:e):We(c?b.slice():b,c?e:d,c?d:e)};function Kr(){this.defaultDataProjection=null}w(Kr,Gr);function Lr(b){return la(b)?b:ia(b)?(b=$n(b),m(b)?b:null):null}l=Kr.prototype;l.V=function(){return"json"};l.Fb=function(b,c){return this.Pc(Lr(b),Hr(this,b,c))};l.ra=function(b,c){return this.Ff(Lr(b),Hr(this,b,c))};l.Qc=function(b,c){return this.th(Lr(b),Hr(this,b,c))};l.Ga=function(b){return this.zh(Lr(b))};l.Dd=function(b,c){return ao(this.Vc(b,c))};l.Gb=function(b,c){return ao(this.Le(b,c))};l.Wc=function(b,c){return ao(this.Ne(b,c))};function Mr(b){b=m(b)?b:{};this.defaultDataProjection=null;this.a=b.geometryName}w(Mr,Kr); +function Nr(b,c){if(null===b)return null;var d;if(ja(b.x)&&ja(b.y))d="Point";else if(null!=b.points)d="MultiPoint";else if(null!=b.paths)d=1===b.paths.length?"LineString":"MultiLineString";else if(null!=b.rings){var e=b.rings,f=Or(b),g=[];d=[];var h,k;h=0;for(k=e.length;h<k;++h){var n=kb(e[h]);Df(n,0,n.length,f.length)?g.push([e[h]]):d.push(e[h])}for(;d.length;){e=d.shift();f=!1;for(h=g.length-1;0<=h;h--)if(Ud((new tf(g[h][0])).R(),(new tf(e)).R())){g[h].push(e);f=!0;break}f||g.push([e.reverse()])}b= +Cb(b);1===g.length?(d="Polygon",b.rings=g[0]):(d="MultiPolygon",b.rings=g)}return Jr((0,Pr[d])(b),!1,c)}function Or(b){var c="XY";!0===b.hasZ&&!0===b.hasM?c="XYZM":!0===b.hasZ?c="XYZ":!0===b.hasM&&(c="XYM");return c}function Qr(b){b=b.b;return{hasZ:"XYZ"===b||"XYZM"===b,hasM:"XYM"===b||"XYZM"===b}} +var Pr={Point:function(b){return null!=b.m&&null!=b.z?new D([b.x,b.y,b.z,b.m],"XYZM"):null!=b.z?new D([b.x,b.y,b.z],"XYZ"):null!=b.m?new D([b.x,b.y,b.m],"XYM"):new D([b.x,b.y])},LineString:function(b){return new N(b.paths[0],Or(b))},Polygon:function(b){return new F(b.rings,Or(b))},MultiPoint:function(b){return new ln(b.points,Or(b))},MultiLineString:function(b){return new O(b.paths,Or(b))},MultiPolygon:function(b){return new P(b.rings,Or(b))}},Rr={Point:function(b){var c=b.W();b=b.b;if("XYZ"===b)return{x:c[0], +y:c[1],z:c[2]};if("XYM"===b)return{x:c[0],y:c[1],m:c[2]};if("XYZM"===b)return{x:c[0],y:c[1],z:c[2],m:c[3]};if("XY"===b)return{x:c[0],y:c[1]}},LineString:function(b){var c=Qr(b);return{hasZ:c.hasZ,hasM:c.hasM,paths:[b.W()]}},Polygon:function(b){var c=Qr(b);return{hasZ:c.hasZ,hasM:c.hasM,rings:b.W(!1)}},MultiPoint:function(b){var c=Qr(b);return{hasZ:c.hasZ,hasM:c.hasM,points:b.W()}},MultiLineString:function(b){var c=Qr(b);return{hasZ:c.hasZ,hasM:c.hasM,paths:b.W()}},MultiPolygon:function(b){var c=Qr(b); +b=b.W(!1);for(var d=[],e=0;e<b.length;e++)for(var f=b[e].length-1;0<=f;f--)d.push(b[e][f]);return{hasZ:c.hasZ,hasM:c.hasM,rings:d}}};l=Mr.prototype;l.Pc=function(b,c){var d=Nr(b.geometry,c),e=new Bn;m(this.a)&&e.Sc(this.a);e.Sa(d);m(c)&&m(c.of)&&m(b.attributes[c.of])&&e.Xb(b.attributes[c.of]);m(b.attributes)&&e.I(b.attributes);return e}; +l.Ff=function(b,c){var d=m(c)?c:{};if(null!=b.features){var e=[],f=b.features,g,h;d.of=b.objectIdFieldName;g=0;for(h=f.length;g<h;++g)e.push(this.Pc(f[g],d));return e}return[this.Pc(b,d)]};l.th=function(b,c){return Nr(b,c)};l.zh=function(b){return null!=b.spatialReference&&null!=b.spatialReference.wkid?Ae("EPSG:"+b.spatialReference.wkid):null};function Sr(b,c){return(0,Rr[b.V()])(Jr(b,!0,c),c)}l.Ne=function(b,c){return Sr(b,Ir(this,c))}; +l.Vc=function(b,c){c=Ir(this,c);var d={},e=b.Z();null!=e&&(d.geometry=Sr(e,c));e=b.P();zb(e,b.b);d.attributes=xb(e)?{}:e;m(c)&&m(c.featureProjection)&&(d.spatialReference={wkid:Ae(c.featureProjection).a.split(":").pop()});return d};l.Le=function(b,c){c=Ir(this,c);var d=[],e,f;e=0;for(f=b.length;e<f;++e)d.push(this.Vc(b[e],c));return{features:d}};function Tr(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ae(null!=b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326");this.a=b.geometryName}w(Tr,Kr);function Ur(b,c){return null===b?null:Jr((0,Vr[b.type])(b),!1,c)}function Wr(b,c){return(0,Xr[b.V()])(Jr(b,!0,c),c)} +var Vr={Point:function(b){return new D(b.coordinates)},LineString:function(b){return new N(b.coordinates)},Polygon:function(b){return new F(b.coordinates)},MultiPoint:function(b){return new ln(b.coordinates)},MultiLineString:function(b){return new O(b.coordinates)},MultiPolygon:function(b){return new P(b.coordinates)},GeometryCollection:function(b,c){var d=Ua(b.geometries,function(b){return Ur(b,c)});return new an(d)}},Xr={Point:function(b){return{type:"Point",coordinates:b.W()}},LineString:function(b){return{type:"LineString", +coordinates:b.W()}},Polygon:function(b,c){var d;m(c)&&(d=c.rightHanded);return{type:"Polygon",coordinates:b.W(d)}},MultiPoint:function(b){return{type:"MultiPoint",coordinates:b.W()}},MultiLineString:function(b){return{type:"MultiLineString",coordinates:b.W()}},MultiPolygon:function(b,c){var d;m(c)&&(d=c.rightHanded);return{type:"MultiPolygon",coordinates:b.W(d)}},GeometryCollection:function(b,c){return{type:"GeometryCollection",geometries:Ua(b.f,function(b){return Wr(b,c)})}},Circle:function(){return{type:"GeometryCollection", +geometries:[]}}};l=Tr.prototype;l.Pc=function(b,c){var d=Ur(b.geometry,c),e=new Bn;m(this.a)&&e.Sc(this.a);e.Sa(d);m(b.id)&&e.Xb(b.id);m(b.properties)&&e.I(b.properties);return e};l.Ff=function(b,c){if("Feature"==b.type)return[this.Pc(b,c)];if("FeatureCollection"==b.type){var d=[],e=b.features,f,g;f=0;for(g=e.length;f<g;++f)d.push(this.Pc(e[f],c));return d}return[]};l.th=function(b,c){return Ur(b,c)}; +l.zh=function(b){b=b.crs;return null!=b?"name"==b.type?Ae(b.properties.name):"EPSG"==b.type?Ae("EPSG:"+b.properties.code):null:this.defaultDataProjection};l.Vc=function(b,c){c=Ir(this,c);var d={type:"Feature"},e=b.ha;null!=e&&(d.id=e);e=b.Z();d.geometry=null!=e?Wr(e,c):null;e=b.P();zb(e,b.b);d.properties=xb(e)?null:e;return d};l.Le=function(b,c){c=Ir(this,c);var d=[],e,f;e=0;for(f=b.length;e<f;++e)d.push(this.Vc(b[e],c));return{type:"FeatureCollection",features:d}}; +l.Ne=function(b,c){return Wr(b,Ir(this,c))};function Yr(){this.defaultDataProjection=null}w(Yr,Gr);l=Yr.prototype;l.V=function(){return"xml"};l.Fb=function(b,c){if(To(b))return Zr(this,b,c);if(Wo(b))return this.rh(b,c);if(ia(b)){var d=fp(b);return Zr(this,d,c)}return null};function Zr(b,c,d){b=$r(b,c,d);return 0<b.length?b[0]:null}l.ra=function(b,c){if(To(b))return $r(this,b,c);if(Wo(b))return this.Vb(b,c);if(ia(b)){var d=fp(b);return $r(this,d,c)}return[]}; +function $r(b,c,d){var e=[];for(c=c.firstChild;null!==c;c=c.nextSibling)1==c.nodeType&&cb(e,b.Vb(c,d));return e}l.Qc=function(b,c){if(To(b))return this.v(b,c);if(Wo(b)){var d=this.Ce(b,[Hr(this,b,m(c)?c:{})]);return m(d)?d:null}return ia(b)?(d=fp(b),this.v(d,c)):null};l.Ga=function(b){return To(b)?this.Jf(b):Wo(b)?this.Fe(b):ia(b)?(b=fp(b),this.Jf(b)):null};l.Jf=function(){return this.defaultDataProjection};l.Fe=function(){return this.defaultDataProjection};l.Dd=function(b,c){var d=this.u(b,c);return Eo(d)}; +l.Gb=function(b,c){var d=this.b(b,c);return Eo(d)};l.Wc=function(b,c){var d=this.B(b,c);return Eo(d)};function as(b){b=m(b)?b:{};this.featureType=b.featureType;this.featureNS=b.featureNS;this.srsName=b.srsName;this.schemaLocation="";this.a={};this.a["http://www.opengis.net/gml"]={featureMember:ip(as.prototype.yd),featureMembers:ip(as.prototype.yd)};this.defaultDataProjection=null}w(as,Yr);l=as.prototype; +l.yd=function(b,c){var d=Qo(b),e;if("FeatureCollection"==d)"http://www.opengis.net/wfs"===b.namespaceURI?e=V([],this.a,b,c,this):e=V(null,this.a,b,c,this);else if("featureMembers"==d||"featureMember"==d){var f=c[0],g=f.featureType;e=f.featureNS;var h,k;if(!m(g)&&null!=b.childNodes){g=[];e={};h=0;for(k=b.childNodes.length;h<k;++h){var n=b.childNodes[h];if(1===n.nodeType){var p=n.nodeName.split(":").pop();if(-1===Ra(g,p)){var q;vb(e,n.namespaceURI)?q=wb(e,function(b){return b===n.namespaceURI}):(q= +"p"+rb(e),e[q]=n.namespaceURI);g.push(q+":"+p)}}}f.featureType=g;f.featureNS=e}ia(e)&&(h=e,e={},e.p0=h);var f={},g=ga(g)?g:[g],r;for(r in e){p={};h=0;for(k=g.length;h<k;++h)(-1===g[h].indexOf(":")?"p0":g[h].split(":")[0])===r&&(p[g[h].split(":").pop()]="featureMembers"==d?hp(this.Ef,this):ip(this.Ef,this));f[e[r]]=p}e=V([],f,b,c)}m(e)||(e=[]);return e};l.Ce=function(b,c){var d=c[0];d.srsName=b.firstElementChild.getAttribute("srsName");var e=V(null,this.Tf,b,c,this);if(null!=e)return Jr(e,!1,d)}; +l.Ef=function(b,c){var d,e=b.getAttribute("fid")||$o(b,"http://www.opengis.net/gml","id"),f={},g;for(d=b.firstElementChild;null!==d;d=d.nextElementSibling){var h=Qo(d);if(0===d.childNodes.length||1===d.childNodes.length&&(3===d.firstChild.nodeType||4===d.firstChild.nodeType)){var k=Mo(d,!1);/^[\s\xa0]*$/.test(k)&&(k=void 0);f[h]=k}else"boundedBy"!==h&&(g=h),f[h]=this.Ce(d,c)}d=new Bn(f);m(g)&&d.Sc(g);e&&d.Xb(e);return d}; +l.yh=function(b,c){var d=this.Be(b,c);if(null!=d){var e=new D(null);vf(e,"XYZ",d);return e}};l.wh=function(b,c){var d=V([],this.ni,b,c,this);if(m(d))return new ln(d)};l.vh=function(b,c){var d=V([],this.mi,b,c,this);if(m(d)){var e=new O(null);kn(e,d);return e}};l.xh=function(b,c){var d=V([],this.oi,b,c,this);if(m(d)){var e=new P(null);nn(e,d);return e}};l.nh=function(b,c){qp(this.si,b,c,this)};l.Eg=function(b,c){qp(this.ki,b,c,this)};l.oh=function(b,c){qp(this.ti,b,c,this)}; +l.De=function(b,c){var d=this.Be(b,c);if(null!=d){var e=new N(null);hn(e,"XYZ",d);return e}};l.Nn=function(b,c){var d=V(null,this.Fd,b,c,this);if(null!=d)return d};l.uh=function(b,c){var d=this.Be(b,c);if(m(d)){var e=new tf(null);uf(e,"XYZ",d);return e}};l.Ee=function(b,c){var d=V([null],this.Pe,b,c,this);if(m(d)&&null!==d[0]){var e=new F(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)cb(f,d[h]),g.push(f.length);Hf(e,"XYZ",f,g);return e}};l.Be=function(b,c){return V(null,this.Fd,b,c,this)}; +l.ni=Object({"http://www.opengis.net/gml":{pointMember:hp(as.prototype.nh),pointMembers:hp(as.prototype.nh)}});l.mi=Object({"http://www.opengis.net/gml":{lineStringMember:hp(as.prototype.Eg),lineStringMembers:hp(as.prototype.Eg)}});l.oi=Object({"http://www.opengis.net/gml":{polygonMember:hp(as.prototype.oh),polygonMembers:hp(as.prototype.oh)}});l.si=Object({"http://www.opengis.net/gml":{Point:hp(as.prototype.Be)}});l.ki=Object({"http://www.opengis.net/gml":{LineString:hp(as.prototype.De)}}); +l.ti=Object({"http://www.opengis.net/gml":{Polygon:hp(as.prototype.Ee)}});l.Gd=Object({"http://www.opengis.net/gml":{LinearRing:ip(as.prototype.Nn)}});l.Vb=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};m(c)&&Gb(d,Hr(this,b,c));return this.yd(b,[d])};l.Fe=function(b){return Ae(m(this.A)?this.A:b.firstElementChild.getAttribute("srsName"))};function bs(b){b=Mo(b,!1);return cs(b)}function cs(b){if(b=/^\s*(true|1)|(false|0)\s*$/.exec(b))return m(b[1])||!1}function ds(b){b=Mo(b,!1);if(b=/^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/.exec(b)){var c=Date.UTC(parseInt(b[1],10),parseInt(b[2],10)-1,parseInt(b[3],10),parseInt(b[4],10),parseInt(b[5],10),parseInt(b[6],10))/1E3;if("Z"!=b[7]){var d="-"==b[8]?-1:1,c=c+60*d*parseInt(b[9],10);m(b[10])&&(c+=3600*d*parseInt(b[10],10))}return c}} +function es(b){b=Mo(b,!1);return fs(b)}function fs(b){if(b=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(b))return parseFloat(b[1])}function gs(b){b=Mo(b,!1);return hs(b)}function hs(b){if(b=/^\s*(\d+)\s*$/.exec(b))return parseInt(b[1],10)}function Y(b){b=Mo(b,!1);return Ca(b)}function is(b,c){js(b,c?"1":"0")}function ks(b,c){b.appendChild(Io.createTextNode(c.toPrecision()))}function ls(b,c){b.appendChild(Io.createTextNode(c.toString()))}function js(b,c){b.appendChild(Io.createTextNode(c))};function ms(b){b=m(b)?b:{};as.call(this,b);this.l=m(b.surface)?b.surface:!1;this.g=m(b.curve)?b.curve:!1;this.i=m(b.multiCurve)?b.multiCurve:!0;this.j=m(b.multiSurface)?b.multiSurface:!0;this.schemaLocation=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd"}w(ms,as);l=ms.prototype;l.Qn=function(b,c){var d=V([],this.li,b,c,this);if(m(d)){var e=new O(null);kn(e,d);return e}}; +l.Rn=function(b,c){var d=V([],this.pi,b,c,this);if(m(d)){var e=new P(null);nn(e,d);return e}};l.fg=function(b,c){qp(this.hi,b,c,this)};l.Th=function(b,c){qp(this.xi,b,c,this)};l.Un=function(b,c){return V([null],this.ri,b,c,this)};l.Wn=function(b,c){return V([null],this.wi,b,c,this)};l.Vn=function(b,c){return V([null],this.Pe,b,c,this)};l.Pn=function(b,c){return V([null],this.Fd,b,c,this)};l.Hk=function(b,c){var d=V(void 0,this.Gd,b,c,this);m(d)&&c[c.length-1].push(d)}; +l.Yi=function(b,c){var d=V(void 0,this.Gd,b,c,this);m(d)&&(c[c.length-1][0]=d)};l.Ah=function(b,c){var d=V([null],this.yi,b,c,this);if(m(d)&&null!==d[0]){var e=new F(null),f=d[0],g=[f.length],h,k;h=1;for(k=d.length;h<k;++h)cb(f,d[h]),g.push(f.length);Hf(e,"XYZ",f,g);return e}};l.ph=function(b,c){var d=V([null],this.ii,b,c,this);if(m(d)){var e=new N(null);hn(e,"XYZ",d);return e}};l.Mn=function(b,c){var d=V([null],this.ji,b,c,this);return Od(d[1][0],d[1][1],d[2][0],d[2][1])}; +l.On=function(b,c){for(var d=Mo(b,!1),e=/^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/,f=[],g;g=e.exec(d);)f.push(parseFloat(g[1])),d=d.substr(g[0].length);if(""===d){d=c[0].srsName;e="enu";null===d||(e=De(Ae(d)));if("neu"===e)for(d=0,e=f.length;d<e;d+=3)g=f[d],f[d]=f[d+1],f[d+1]=g;d=f.length;2==d&&f.push(0);return 0===d?void 0:f}}; +l.Hf=function(b,c){var d=Mo(b,!1).replace(/^\s*|\s*$/g,""),e=c[0].srsName,f=b.parentNode.getAttribute("srsDimension"),g="enu";null===e||(g=De(Ae(e)));d=d.split(/\s+/);e=2;fa(b.getAttribute("srsDimension"))?fa(b.getAttribute("dimension"))?null===f||(e=hs(f)):e=hs(b.getAttribute("dimension")):e=hs(b.getAttribute("srsDimension"));for(var h,k,n=[],p=0,q=d.length;p<q;p+=e)f=parseFloat(d[p]),h=parseFloat(d[p+1]),k=3===e?parseFloat(d[p+2]):0,"en"===g.substr(0,2)?n.push(f,h,k):n.push(h,f,k);return n}; +l.Fd=Object({"http://www.opengis.net/gml":{pos:ip(ms.prototype.On),posList:ip(ms.prototype.Hf)}});l.Pe=Object({"http://www.opengis.net/gml":{interior:ms.prototype.Hk,exterior:ms.prototype.Yi}}); +l.Tf=Object({"http://www.opengis.net/gml":{Point:ip(as.prototype.yh),MultiPoint:ip(as.prototype.wh),LineString:ip(as.prototype.De),MultiLineString:ip(as.prototype.vh),LinearRing:ip(as.prototype.uh),Polygon:ip(as.prototype.Ee),MultiPolygon:ip(as.prototype.xh),Surface:ip(ms.prototype.Ah),MultiSurface:ip(ms.prototype.Rn),Curve:ip(ms.prototype.ph),MultiCurve:ip(ms.prototype.Qn),Envelope:ip(ms.prototype.Mn)}});l.li=Object({"http://www.opengis.net/gml":{curveMember:hp(ms.prototype.fg),curveMembers:hp(ms.prototype.fg)}}); +l.pi=Object({"http://www.opengis.net/gml":{surfaceMember:hp(ms.prototype.Th),surfaceMembers:hp(ms.prototype.Th)}});l.hi=Object({"http://www.opengis.net/gml":{LineString:hp(as.prototype.De),Curve:hp(ms.prototype.ph)}});l.xi=Object({"http://www.opengis.net/gml":{Polygon:hp(as.prototype.Ee),Surface:hp(ms.prototype.Ah)}});l.yi=Object({"http://www.opengis.net/gml":{patches:ip(ms.prototype.Un)}});l.ii=Object({"http://www.opengis.net/gml":{segments:ip(ms.prototype.Wn)}}); +l.ji=Object({"http://www.opengis.net/gml":{lowerCorner:hp(ms.prototype.Hf),upperCorner:hp(ms.prototype.Hf)}});l.ri=Object({"http://www.opengis.net/gml":{PolygonPatch:ip(ms.prototype.Vn)}});l.wi=Object({"http://www.opengis.net/gml":{LineStringSegment:ip(ms.prototype.Pn)}});function ns(b,c,d){d=d[d.length-1].srsName;c=c.W();for(var e=c.length,f=Array(e),g,h=0;h<e;++h){g=c[h];var k=h,n="enu";null!=d&&(n=De(Ae(d)));f[k]="en"===n.substr(0,2)?g[0]+" "+g[1]:g[1]+" "+g[0]}js(b,f.join(" "))} +l.ci=function(b,c,d){var e=d[d.length-1].srsName;null!=e&&b.setAttribute("srsName",e);e=Lo(b.namespaceURI,"pos");b.appendChild(e);d=d[d.length-1].srsName;b="enu";null!=d&&(b=De(Ae(d)));c=c.W();js(e,"en"===b.substr(0,2)?c[0]+" "+c[1]:c[1]+" "+c[0])};var os={"http://www.opengis.net/gml":{lowerCorner:U(js),upperCorner:U(js)}};l=ms.prototype; +l.Mo=function(b,c,d){var e=d[d.length-1].srsName;m(e)&&b.setAttribute("srsName",e);rp({node:b},os,op,[c[0]+" "+c[1],c[2]+" "+c[3]],d,["lowerCorner","upperCorner"],this)};l.$h=function(b,c,d){var e=d[d.length-1].srsName;null!=e&&b.setAttribute("srsName",e);e=Lo(b.namespaceURI,"posList");b.appendChild(e);ns(e,c,d)};l.vi=function(b,c){var d=c[c.length-1],e=d.node,f=d.exteriorWritten;m(f)||(d.exteriorWritten=!0);return Lo(e.namespaceURI,m(f)?"interior":"exterior")}; +l.Oe=function(b,c,d){var e=d[d.length-1].srsName;"PolygonPatch"!==b.nodeName&&null!=e&&b.setAttribute("srsName",e);"Polygon"===b.nodeName||"PolygonPatch"===b.nodeName?(c=c.Ud(),rp({node:b,srsName:e},ps,this.vi,c,d,void 0,this)):"Surface"===b.nodeName&&(e=Lo(b.namespaceURI,"patches"),b.appendChild(e),b=Lo(e.namespaceURI,"PolygonPatch"),e.appendChild(b),this.Oe(b,c,d))}; +l.Je=function(b,c,d){var e=d[d.length-1].srsName;"LineStringSegment"!==b.nodeName&&null!=e&&b.setAttribute("srsName",e);"LineString"===b.nodeName||"LineStringSegment"===b.nodeName?(e=Lo(b.namespaceURI,"posList"),b.appendChild(e),ns(e,c,d)):"Curve"===b.nodeName&&(e=Lo(b.namespaceURI,"segments"),b.appendChild(e),b=Lo(e.namespaceURI,"LineStringSegment"),e.appendChild(b),this.Je(b,c,d))}; +l.bi=function(b,c,d){var e=d[d.length-1],f=e.srsName,e=e.surface;null!=f&&b.setAttribute("srsName",f);c=c.Wd();rp({node:b,srsName:f,surface:e},qs,this.f,c,d,void 0,this)};l.No=function(b,c,d){var e=d[d.length-1].srsName;null!=e&&b.setAttribute("srsName",e);c=c.pe();rp({node:b,srsName:e},rs,mp("pointMember"),c,d,void 0,this)};l.ai=function(b,c,d){var e=d[d.length-1],f=e.srsName,e=e.curve;null!=f&&b.setAttribute("srsName",f);c=c.md();rp({node:b,srsName:f,curve:e},ss,this.f,c,d,void 0,this)}; +l.di=function(b,c,d){var e=Lo(b.namespaceURI,"LinearRing");b.appendChild(e);this.$h(e,c,d)};l.ei=function(b,c,d){var e=this.c(c,d);m(e)&&(b.appendChild(e),this.Oe(e,c,d))};l.Oo=function(b,c,d){var e=Lo(b.namespaceURI,"Point");b.appendChild(e);this.ci(e,c,d)};l.Zh=function(b,c,d){var e=this.c(c,d);m(e)&&(b.appendChild(e),this.Je(e,c,d))}; +l.Me=function(b,c,d){var e=d[d.length-1],f=Cb(e);f.node=b;var g;ga(c)?m(e.dataProjection)?g=We(c,e.featureProjection,e.dataProjection):g=c:g=Jr(c,!0,e);rp(f,ts,this.c,[g],d,void 0,this)}; +l.Xh=function(b,c,d){var e=c.ha;m(e)&&b.setAttribute("fid",e);var e=d[d.length-1],f=e.featureNS,g=c.b;m(e.oc)||(e.oc={},e.oc[f]={});var h=c.P();c=[];var k=[],n;for(n in h){var p=h[n];null!==p&&(c.push(n),k.push(p),n==g?n in e.oc[f]||(e.oc[f][n]=U(this.Me,this)):n in e.oc[f]||(e.oc[f][n]=U(js)))}n=Cb(e);n.node=b;rp(n,e.oc,mp(void 0,f),k,d,c)}; +var qs={"http://www.opengis.net/gml":{surfaceMember:U(ms.prototype.ei),polygonMember:U(ms.prototype.ei)}},rs={"http://www.opengis.net/gml":{pointMember:U(ms.prototype.Oo)}},ss={"http://www.opengis.net/gml":{lineStringMember:U(ms.prototype.Zh),curveMember:U(ms.prototype.Zh)}},ps={"http://www.opengis.net/gml":{exterior:U(ms.prototype.di),interior:U(ms.prototype.di)}},ts={"http://www.opengis.net/gml":{Curve:U(ms.prototype.Je),MultiCurve:U(ms.prototype.ai),Point:U(ms.prototype.ci),MultiPoint:U(ms.prototype.No), +LineString:U(ms.prototype.Je),MultiLineString:U(ms.prototype.ai),LinearRing:U(ms.prototype.$h),Polygon:U(ms.prototype.Oe),MultiPolygon:U(ms.prototype.bi),Surface:U(ms.prototype.Oe),MultiSurface:U(ms.prototype.bi),Envelope:U(ms.prototype.Mo)}},us={MultiLineString:"lineStringMember",MultiCurve:"curveMember",MultiPolygon:"polygonMember",MultiSurface:"surfaceMember"};ms.prototype.f=function(b,c){return Lo("http://www.opengis.net/gml",us[c[c.length-1].node.nodeName])}; +ms.prototype.c=function(b,c){var d=c[c.length-1],e=d.multiSurface,f=d.surface,g=d.curve,d=d.multiCurve,h;ga(b)?h="Envelope":(h=b.V(),"MultiPolygon"===h&&!0===e?h="MultiSurface":"Polygon"===h&&!0===f?h="Surface":"LineString"===h&&!0===g?h="Curve":"MultiLineString"===h&&!0===d&&(h="MultiCurve"));return Lo("http://www.opengis.net/gml",h)}; +ms.prototype.B=function(b,c){c=Ir(this,c);var d=Lo("http://www.opengis.net/gml","geom"),e={node:d,srsName:this.srsName,curve:this.g,surface:this.l,multiSurface:this.j,multiCurve:this.i};m(c)&&Gb(e,c);this.Me(d,b,[e]);return d}; +ms.prototype.b=function(b,c){c=Ir(this,c);var d=Lo("http://www.opengis.net/gml","featureMembers");ep(d,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.schemaLocation);var e={srsName:this.srsName,curve:this.g,surface:this.l,multiSurface:this.j,multiCurve:this.i,featureNS:this.featureNS,featureType:this.featureType};m(c)&&Gb(e,c);var e=[e],f=e[e.length-1],g=f.featureType,h=f.featureNS,k={};k[h]={};k[h][g]=U(this.Xh,this);f=Cb(f);f.node=d;rp(f,k,mp(g,h),b,e);return d};function vs(b){b=m(b)?b:{};as.call(this,b);this.a["http://www.opengis.net/gml"].featureMember=hp(as.prototype.yd);this.schemaLocation=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd"}w(vs,as);l=vs.prototype; +l.sh=function(b,c){var d=Mo(b,!1).replace(/^\s*|\s*$/g,""),e=c[0].srsName,f=b.parentNode.getAttribute("srsDimension"),g="enu";null===e||(g=De(Ae(e)));d=d.split(/[\s,]+/);e=2;fa(b.getAttribute("srsDimension"))?fa(b.getAttribute("dimension"))?null===f||(e=hs(f)):e=hs(b.getAttribute("dimension")):e=hs(b.getAttribute("srsDimension"));for(var h,k,n=[],p=0,q=d.length;p<q;p+=e)f=parseFloat(d[p]),h=parseFloat(d[p+1]),k=3===e?parseFloat(d[p+2]):0,"en"===g.substr(0,2)?n.push(f,h,k):n.push(h,f,k);return n}; +l.Ln=function(b,c){var d=V([null],this.gi,b,c,this);return Od(d[1][0],d[1][1],d[1][3],d[1][4])};l.Fk=function(b,c){var d=V(void 0,this.Gd,b,c,this);m(d)&&c[c.length-1].push(d)};l.wn=function(b,c){var d=V(void 0,this.Gd,b,c,this);m(d)&&(c[c.length-1][0]=d)};l.Fd=Object({"http://www.opengis.net/gml":{coordinates:ip(vs.prototype.sh)}});l.Pe=Object({"http://www.opengis.net/gml":{innerBoundaryIs:vs.prototype.Fk,outerBoundaryIs:vs.prototype.wn}});l.gi=Object({"http://www.opengis.net/gml":{coordinates:hp(vs.prototype.sh)}}); +l.Tf=Object({"http://www.opengis.net/gml":{Point:ip(as.prototype.yh),MultiPoint:ip(as.prototype.wh),LineString:ip(as.prototype.De),MultiLineString:ip(as.prototype.vh),LinearRing:ip(as.prototype.uh),Polygon:ip(as.prototype.Ee),MultiPolygon:ip(as.prototype.xh),Box:ip(vs.prototype.Ln)}});function ws(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ae("EPSG:4326");this.a=b.readExtensions}w(ws,Yr);var xs=[null,"http://www.topografix.com/GPX/1/0","http://www.topografix.com/GPX/1/1"];function ys(b,c,d){b.push(parseFloat(c.getAttribute("lon")),parseFloat(c.getAttribute("lat")));"ele"in d?(b.push(d.ele),zb(d,"ele")):b.push(0);"time"in d?(b.push(d.time),zb(d,"time")):b.push(0);return b} +function zs(b,c){var d=c[c.length-1],e=b.getAttribute("href");null===e||(d.link=e);qp(As,b,c)}function Bs(b,c){c[c.length-1].extensionsNode_=b}function Cs(b,c){var d=c[0],e=V({flatCoordinates:[]},Ds,b,c);if(m(e)){var f=e.flatCoordinates;zb(e,"flatCoordinates");var g=new N(null);hn(g,"XYZM",f);Jr(g,!1,d);d=new Bn(g);d.I(e);return d}} +function Es(b,c){var d=c[0],e=V({flatCoordinates:[],ends:[]},Fs,b,c);if(m(e)){var f=e.flatCoordinates;zb(e,"flatCoordinates");var g=e.ends;zb(e,"ends");var h=new O(null);jn(h,"XYZM",f,g);Jr(h,!1,d);d=new Bn(h);d.I(e);return d}}function Gs(b,c){var d=c[0],e=V({},Hs,b,c);if(m(e)){var f=ys([],b,e),f=new D(f,"XYZM");Jr(f,!1,d);d=new Bn(f);d.I(e);return d}} +var Is={rte:Cs,trk:Es,wpt:Gs},Js=T(xs,{rte:hp(Cs),trk:hp(Es),wpt:hp(Gs)}),As=T(xs,{text:Q(Y,"linkText"),type:Q(Y,"linkType")}),Ds=T(xs,{name:Q(Y),cmt:Q(Y),desc:Q(Y),src:Q(Y),link:zs,number:Q(gs),extensions:Bs,type:Q(Y),rtept:function(b,c){var d=V({},Ks,b,c);m(d)&&ys(c[c.length-1].flatCoordinates,b,d)}}),Ks=T(xs,{ele:Q(es),time:Q(ds)}),Fs=T(xs,{name:Q(Y),cmt:Q(Y),desc:Q(Y),src:Q(Y),link:zs,number:Q(gs),type:Q(Y),extensions:Bs,trkseg:function(b,c){var d=c[c.length-1];qp(Ls,b,c);d.ends.push(d.flatCoordinates.length)}}), +Ls=T(xs,{trkpt:function(b,c){var d=V({},Ms,b,c);m(d)&&ys(c[c.length-1].flatCoordinates,b,d)}}),Ms=T(xs,{ele:Q(es),time:Q(ds)}),Hs=T(xs,{ele:Q(es),time:Q(ds),magvar:Q(es),geoidheight:Q(es),name:Q(Y),cmt:Q(Y),desc:Q(Y),src:Q(Y),link:zs,sym:Q(Y),type:Q(Y),fix:Q(Y),sat:Q(gs),hdop:Q(es),vdop:Q(es),pdop:Q(es),ageofdgpsdata:Q(es),dgpsid:Q(gs),extensions:Bs}); +function Ns(b,c){null===c&&(c=[]);for(var d=0,e=c.length;d<e;++d){var f=c[d];if(m(b.a)){var g=f.get("extensionsNode_")||null;b.a(f,g)}f.set("extensionsNode_",void 0)}}ws.prototype.rh=function(b,c){if(!Ya(xs,b.namespaceURI))return null;var d=Is[b.localName];if(!m(d))return null;d=d(b,[Hr(this,b,c)]);if(!m(d))return null;Ns(this,[d]);return d};ws.prototype.Vb=function(b,c){if(!Ya(xs,b.namespaceURI))return[];if("gpx"==b.localName){var d=V([],Js,b,[Hr(this,b,c)]);if(m(d))return Ns(this,d),d}return[]}; +function Os(b,c,d){b.setAttribute("href",c);c=d[d.length-1].properties;rp({node:b},Ps,op,[c.linkText,c.linkType],d,Qs)}function Rs(b,c,d){var e=d[d.length-1],f=e.node.namespaceURI,g=e.properties;ep(b,null,"lat",c[1]);ep(b,null,"lon",c[0]);switch(e.geometryLayout){case "XYZM":0!==c[3]&&(g.time=c[3]);case "XYZ":0!==c[2]&&(g.ele=c[2]);break;case "XYM":0!==c[2]&&(g.time=c[2])}c=Ss[f];e=pp(g,c);rp({node:b,properties:g},Ts,op,e,d,c)} +var Qs=["text","type"],Ps=kp(xs,{text:U(js),type:U(js)}),Us=kp(xs,"name cmt desc src link number type rtept".split(" ")),Vs=kp(xs,{name:U(js),cmt:U(js),desc:U(js),src:U(js),link:U(Os),number:U(ls),type:U(js),rtept:lp(U(Rs))}),Ws=kp(xs,"name cmt desc src link number type trkseg".split(" ")),Zs=kp(xs,{name:U(js),cmt:U(js),desc:U(js),src:U(js),link:U(Os),number:U(ls),type:U(js),trkseg:lp(U(function(b,c,d){rp({node:b,geometryLayout:c.b,properties:{}},Xs,Ys,c.W(),d)}))}),Ys=mp("trkpt"),Xs=kp(xs,{trkpt:U(Rs)}), +Ss=kp(xs,"ele time magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid".split(" ")),Ts=kp(xs,{ele:U(ks),time:U(function(b,c){var d=new Date(1E3*c),d=d.getUTCFullYear()+"-"+Ma(d.getUTCMonth()+1)+"-"+Ma(d.getUTCDate())+"T"+Ma(d.getUTCHours())+":"+Ma(d.getUTCMinutes())+":"+Ma(d.getUTCSeconds())+"Z";b.appendChild(Io.createTextNode(d))}),magvar:U(ks),geoidheight:U(ks),name:U(js),cmt:U(js),desc:U(js),src:U(js),link:U(Os),sym:U(js),type:U(js),fix:U(js),sat:U(ls), +hdop:U(ks),vdop:U(ks),pdop:U(ks),ageofdgpsdata:U(ks),dgpsid:U(ls)}),$s={Point:"wpt",LineString:"rte",MultiLineString:"trk"};function at(b,c){var d=b.Z();if(m(d))return Lo(c[c.length-1].node.namespaceURI,$s[d.V()])} +var bt=kp(xs,{rte:U(function(b,c,d){var e=d[0],f=c.P();b={node:b,properties:f};c=c.Z();m(c)&&(c=Jr(c,!0,e),b.geometryLayout=c.b,f.rtept=c.W());e=Us[d[d.length-1].node.namespaceURI];f=pp(f,e);rp(b,Vs,op,f,d,e)}),trk:U(function(b,c,d){var e=d[0],f=c.P();b={node:b,properties:f};c=c.Z();m(c)&&(c=Jr(c,!0,e),f.trkseg=c.md());e=Ws[d[d.length-1].node.namespaceURI];f=pp(f,e);rp(b,Zs,op,f,d,e)}),wpt:U(function(b,c,d){var e=d[0],f=d[d.length-1];f.properties=c.P();c=c.Z();m(c)&&(c=Jr(c,!0,e),f.geometryLayout= +c.b,Rs(b,c.W(),d))})});ws.prototype.b=function(b,c){c=Ir(this,c);var d=Lo("http://www.topografix.com/GPX/1/1","gpx");rp({node:d},bt,at,b,[c]);return d};function ct(b){b=dt(b);return Ua(b,function(b){return b.c.substring(b.b,b.a)})}function et(b,c,d){this.c=b;this.b=c;this.a=d}function dt(b){for(var c=RegExp("\r\n|\r|\n","g"),d=0,e,f=[];e=c.exec(b);)d=new et(b,d,e.index),f.push(d),d=c.lastIndex;d<b.length&&(d=new et(b,d,b.length),f.push(d));return f};function ft(){this.defaultDataProjection=null}w(ft,Gr);l=ft.prototype;l.V=function(){return"text"};l.Fb=function(b,c){return this.xd(ia(b)?b:"",Ir(this,c))};l.ra=function(b,c){return this.Gf(ia(b)?b:"",Ir(this,c))};l.Qc=function(b,c){return this.zd(ia(b)?b:"",Ir(this,c))};l.Ga=function(){return this.defaultDataProjection};l.Dd=function(b,c){return this.Ke(b,Ir(this,c))};l.Gb=function(b,c){return this.Yh(b,Ir(this,c))};l.Wc=function(b,c){return this.Ed(b,Ir(this,c))};function gt(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ae("EPSG:4326");this.a=m(b.altitudeMode)?b.altitudeMode:"none"}w(gt,ft);var ht=/^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/,it=/^H.([A-Z]{3}).*?:(.*)/,jt=/^HFDTE(\d{2})(\d{2})(\d{2})/; +gt.prototype.xd=function(b,c){var d=this.a,e=ct(b),f={},g=[],h=2E3,k=0,n=1,p,q;p=0;for(q=e.length;p<q;++p){var r=e[p],t;if("B"==r.charAt(0)){if(t=ht.exec(r)){var r=parseInt(t[1],10),v=parseInt(t[2],10),B=parseInt(t[3],10),z=parseInt(t[4],10)+parseInt(t[5],10)/6E4;"S"==t[6]&&(z=-z);var E=parseInt(t[7],10)+parseInt(t[8],10)/6E4;"W"==t[9]&&(E=-E);g.push(E,z);"none"!=d&&g.push("gps"==d?parseInt(t[11],10):"barometric"==d?parseInt(t[12],10):0);g.push(Date.UTC(h,k,n,r,v,B)/1E3)}}else if("H"==r.charAt(0))if(t= +jt.exec(r))n=parseInt(t[1],10),k=parseInt(t[2],10)-1,h=2E3+parseInt(t[3],10);else if(t=it.exec(r))f[t[1]]=Ca(t[2]),jt.exec(r)}if(0===g.length)return null;e=new N(null);hn(e,"none"==d?"XYM":"XYZM",g);d=new Bn(Jr(e,!1,c));d.I(f);return d};gt.prototype.Gf=function(b,c){var d=this.xd(b,c);return null===d?[]:[d]};function kt(b,c){var d;b instanceof kt?(this.ec=m(c)?c:b.ec,lt(this,b.Wb),this.yc=b.yc,this.vb=b.vb,mt(this,b.Oc),this.qb=b.qb,nt(this,b.a.clone()),this.$b=b.$b):b&&(d=jo(String(b)))?(this.ec=!!c,lt(this,d[1]||"",!0),this.yc=ot(d[2]||""),this.vb=ot(d[3]||"",!0),mt(this,d[4]),this.qb=ot(d[5]||"",!0),nt(this,d[6]||"",!0),this.$b=ot(d[7]||"")):(this.ec=!!c,this.a=new pt(null,0,this.ec))}l=kt.prototype;l.Wb="";l.yc="";l.vb="";l.Oc=null;l.qb="";l.$b="";l.ec=!1; +l.toString=function(){var b=[],c=this.Wb;c&&b.push(qt(c,rt,!0),":");if(c=this.vb){b.push("//");var d=this.yc;d&&b.push(qt(d,rt,!0),"@");b.push(encodeURIComponent(String(c)).replace(/%25([0-9a-fA-F]{2})/g,"%$1"));c=this.Oc;null!=c&&b.push(":",String(c))}if(c=this.qb)this.vb&&"/"!=c.charAt(0)&&b.push("/"),b.push(qt(c,"/"==c.charAt(0)?st:tt,!0));(c=this.a.toString())&&b.push("?",c);(c=this.$b)&&b.push("#",qt(c,ut));return b.join("")};l.clone=function(){return new kt(this)}; +function lt(b,c,d){b.Wb=d?ot(c,!0):c;b.Wb&&(b.Wb=b.Wb.replace(/:$/,""))}function mt(b,c){if(c){c=Number(c);if(isNaN(c)||0>c)throw Error("Bad port number "+c);b.Oc=c}else b.Oc=null}function nt(b,c,d){c instanceof pt?(b.a=c,vt(b.a,b.ec)):(d||(c=qt(c,wt)),b.a=new pt(c,0,b.ec))}function xt(b){return b instanceof kt?b.clone():new kt(b,void 0)} +function yt(b,c){b instanceof kt||(b=xt(b));c instanceof kt||(c=xt(c));var d=b,e=c,f=d.clone(),g=!!e.Wb;g?lt(f,e.Wb):g=!!e.yc;g?f.yc=e.yc:g=!!e.vb;g?f.vb=e.vb:g=null!=e.Oc;var h=e.qb;if(g)mt(f,e.Oc);else if(g=!!e.qb)if("/"!=h.charAt(0)&&(d.vb&&!d.qb?h="/"+h:(d=f.qb.lastIndexOf("/"),-1!=d&&(h=f.qb.substr(0,d+1)+h))),d=h,".."==d||"."==d)h="";else if(-1!=d.indexOf("./")||-1!=d.indexOf("/.")){for(var h=0==d.lastIndexOf("/",0),d=d.split("/"),k=[],n=0;n<d.length;){var p=d[n++];"."==p?h&&n==d.length&&k.push(""): +".."==p?((1<k.length||1==k.length&&""!=k[0])&&k.pop(),h&&n==d.length&&k.push("")):(k.push(p),h=!0)}h=k.join("/")}else h=d;g?f.qb=h:g=""!==e.a.toString();g?nt(f,ot(e.a.toString())):g=!!e.$b;g&&(f.$b=e.$b);return f}function ot(b,c){return b?c?decodeURI(b):decodeURIComponent(b):""}function qt(b,c,d){return ia(b)?(b=encodeURI(b).replace(c,zt),d&&(b=b.replace(/%25([0-9a-fA-F]{2})/g,"%$1")),b):null}function zt(b){b=b.charCodeAt(0);return"%"+(b>>4&15).toString(16)+(b&15).toString(16)} +var rt=/[#\/\?@]/g,tt=/[\#\?:]/g,st=/[\#\?]/g,wt=/[\#\?@]/g,ut=/#/g;function pt(b,c,d){this.a=b||null;this.b=!!d}function At(b){b.pa||(b.pa=new wi,b.Aa=0,b.a&&lo(b.a,function(c,d){b.add(decodeURIComponent(c.replace(/\+/g," ")),d)}))}l=pt.prototype;l.pa=null;l.Aa=null;l.ac=function(){At(this);return this.Aa};l.add=function(b,c){At(this);this.a=null;b=Bt(this,b);var d=this.pa.get(b);d||this.pa.set(b,d=[]);d.push(c);this.Aa++;return this}; +l.remove=function(b){At(this);b=Bt(this,b);return yi(this.pa.b,b)?(this.a=null,this.Aa-=this.pa.get(b).length,this.pa.remove(b)):!1};l.clear=function(){this.pa=this.a=null;this.Aa=0};l.wa=function(){At(this);return 0==this.Aa};function Ct(b,c){At(b);c=Bt(b,c);return yi(b.pa.b,c)}l.O=function(){At(this);for(var b=this.pa.mb(),c=this.pa.O(),d=[],e=0;e<c.length;e++)for(var f=b[e],g=0;g<f.length;g++)d.push(c[e]);return d}; +l.mb=function(b){At(this);var c=[];if(ia(b))Ct(this,b)&&(c=ab(c,this.pa.get(Bt(this,b))));else{b=this.pa.mb();for(var d=0;d<b.length;d++)c=ab(c,b[d])}return c};l.set=function(b,c){At(this);this.a=null;b=Bt(this,b);Ct(this,b)&&(this.Aa-=this.pa.get(b).length);this.pa.set(b,[c]);this.Aa++;return this};l.get=function(b,c){var d=b?this.mb(b):[];return 0<d.length?String(d[0]):c};function Dt(b,c,d){b.remove(c);0<d.length&&(b.a=null,b.pa.set(Bt(b,c),bb(d)),b.Aa+=d.length)} +l.toString=function(){if(this.a)return this.a;if(!this.pa)return"";for(var b=[],c=this.pa.O(),d=0;d<c.length;d++)for(var e=c[d],f=encodeURIComponent(String(e)),e=this.mb(e),g=0;g<e.length;g++){var h=f;""!==e[g]&&(h+="="+encodeURIComponent(String(e[g])));b.push(h)}return this.a=b.join("&")};l.clone=function(){var b=new pt;b.a=this.a;this.pa&&(b.pa=this.pa.clone(),b.Aa=this.Aa);return b};function Bt(b,c){var d=String(c);b.b&&(d=d.toLowerCase());return d} +function vt(b,c){c&&!b.b&&(At(b),b.a=null,b.pa.forEach(function(b,c){var f=c.toLowerCase();c!=f&&(this.remove(c),Dt(this,f,b))},b));b.b=c};function Et(b){b=m(b)?b:{};this.f=b.font;this.g=b.rotation;this.b=b.scale;this.c=b.text;this.j=b.textAlign;this.l=b.textBaseline;this.a=m(b.fill)?b.fill:null;this.i=m(b.stroke)?b.stroke:null;this.B=m(b.offsetX)?b.offsetX:0;this.v=m(b.offsetY)?b.offsetY:0}l=Et.prototype;l.oj=function(){return this.f};l.Ej=function(){return this.B};l.Fj=function(){return this.v};l.cn=function(){return this.a};l.dn=function(){return this.g};l.en=function(){return this.b};l.fn=function(){return this.i};l.gn=function(){return this.c}; +l.Pj=function(){return this.j};l.Qj=function(){return this.l};l.no=function(b){this.f=b};l.mo=function(b){this.a=b};l.hn=function(b){this.g=b};l.jn=function(b){this.b=b};l.vo=function(b){this.i=b};l.wo=function(b){this.c=b};l.xo=function(b){this.j=b};l.yo=function(b){this.l=b};function Ft(b){function c(b){return ga(b)?b:ia(b)?(!(b in e)&&"#"+b in e&&(b="#"+b),c(e[b])):d}b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ae("EPSG:4326");var d=m(b.defaultStyle)?b.defaultStyle:Gt,e={};this.c=m(b.extractStyles)?b.extractStyles:!0;this.a=e;this.f=function(){var b=this.get("Style");if(m(b))return b;b=this.get("styleUrl");return m(b)?c(b):d}}w(Ft,Yr); +var Ht=["http://www.google.com/kml/ext/2.2"],It=[null,"http://earth.google.com/kml/2.0","http://earth.google.com/kml/2.1","http://earth.google.com/kml/2.2","http://www.opengis.net/kml/2.2"],Jt=[255,255,255,1],Kt=new Hl({color:Jt}),Lt=[20,2],Mt=[64,64],Nt=new Ck({anchor:Lt,anchorOrigin:"bottom-left",anchorXUnits:"pixels",anchorYUnits:"pixels",crossOrigin:"anonymous",rotation:0,scale:.5,size:Mt,src:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"}),Ot=new Dl({color:Jt,width:1}),Pt=new Et({font:"normal 16px Helvetica", +fill:Kt,stroke:Ot,scale:1}),Gt=[new Jl({fill:Kt,image:Nt,text:Pt,stroke:Ot,zIndex:0})],Qt={fraction:"fraction",pixels:"pixels"};function Rt(b){b=Mo(b,!1);if(b=/^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(b))return b=b[1],[parseInt(b.substr(6,2),16),parseInt(b.substr(4,2),16),parseInt(b.substr(2,2),16),parseInt(b.substr(0,2),16)/255]} +function St(b){b=Mo(b,!1);for(var c=[],d=/^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i,e;e=d.exec(b);)c.push(parseFloat(e[1]),parseFloat(e[2]),e[3]?parseFloat(e[3]):0),b=b.substr(e[0].length);return""!==b?void 0:c}function Tt(b){var c=Mo(b,!1);return null!=b.baseURI?yt(b.baseURI,Ca(c)).toString():Ca(c)}function Ut(b){b=es(b);if(m(b))return Math.sqrt(b)}function Vt(b,c){return V(null,Wt,b,c)} +function Xt(b,c){var d=V({o:[],Wh:[]},Yt,b,c);if(m(d)){var e=d.o,d=d.Wh,f,g;f=0;for(g=Math.min(e.length,d.length);f<g;++f)e[4*f+3]=d[f];d=new N(null);hn(d,"XYZM",e);return d}}function Zt(b,c){var d=V({},$t,b,c),e=V(null,au,b,c);if(m(e)){var f=new N(null);hn(f,"XYZ",e);f.I(d);return f}}function bu(b,c){var d=V({},$t,b,c),e=V(null,au,b,c);if(m(e)){var f=new F(null);Hf(f,"XYZ",e,[e.length]);f.I(d);return f}} +function cu(b,c){var d=V([],du,b,c);if(!m(d))return null;if(0===d.length)return new an(d);var e=!0,f=d[0].V(),g,h,k;h=1;for(k=d.length;h<k;++h)if(g=d[h],g.V()!=f){e=!1;break}if(e){if("Point"==f){g=d[0];e=g.b;f=g.o;h=1;for(k=d.length;h<k;++h)g=d[h],cb(f,g.o);g=new ln(null);af(g,e,f);g.s();eu(g,d);return g}return"LineString"==f?(g=new O(null),kn(g,d),eu(g,d),g):"Polygon"==f?(g=new P(null),nn(g,d),eu(g,d),g):"GeometryCollection"==f?new an(d):null}return new an(d)} +function fu(b,c){var d=V({},$t,b,c),e=V(null,au,b,c);if(null!=e){var f=new D(null);vf(f,"XYZ",e);f.I(d);return f}}function gu(b,c){var d=V({},$t,b,c),e=V([null],hu,b,c);if(null!=e&&null!==e[0]){var f=new F(null),g=e[0],h=[g.length],k,n;k=1;for(n=e.length;k<n;++k)cb(g,e[k]),h.push(g.length);Hf(f,"XYZ",g,h);f.I(d);return f}} +function iu(b,c){var d=V({},ju,b,c);if(!m(d))return null;var e=Ab(d,"fillStyle",Kt),f=d.fill;m(f)&&!f&&(e=null);var f=Ab(d,"imageStyle",Nt),g=Ab(d,"textStyle",Pt),h=Ab(d,"strokeStyle",Ot),d=d.outline;m(d)&&!d&&(h=null);return[new Jl({fill:e,image:f,stroke:h,text:g,zIndex:void 0})]} +function eu(b,c){var d=c.length,e=Array(c.length),f=Array(c.length),g,h,k,n;k=n=!1;for(h=0;h<d;++h)g=c[h],e[h]=g.get("extrude"),f[h]=g.get("altitudeMode"),k=k||m(e[h]),n=n||m(f[h]);k&&b.set("extrude",e);n&&b.set("altitudeMode",f)}function ku(b,c){qp(lu,b,c)} +var mu=T(It,{value:ip(Y)}),lu=T(It,{Data:function(b,c){var d=b.getAttribute("name");if(null!==d){var e=V(void 0,mu,b,c);m(e)&&(c[c.length-1][d]=e)}},SchemaData:function(b,c){qp(nu,b,c)}}),$t=T(It,{extrude:Q(bs),altitudeMode:Q(Y)}),Wt=T(It,{coordinates:ip(St)}),hu=T(It,{innerBoundaryIs:function(b,c){var d=V(void 0,ou,b,c);m(d)&&c[c.length-1].push(d)},outerBoundaryIs:function(b,c){var d=V(void 0,pu,b,c);m(d)&&(c[c.length-1][0]=d)}}),Yt=T(It,{when:function(b,c){var d=c[c.length-1].Wh,e=Mo(b,!1);if(e= +/^\s*(\d{4})($|-(\d{2})($|-(\d{2})($|T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?)))))\s*$/.exec(e)){var f=Date.UTC(parseInt(e[1],10),m(e[3])?parseInt(e[3],10)-1:0,m(e[5])?parseInt(e[5],10):1,m(e[7])?parseInt(e[7],10):0,m(e[8])?parseInt(e[8],10):0,m(e[9])?parseInt(e[9],10):0);if(m(e[10])&&"Z"!=e[10]){var g="-"==e[11]?-1:1,f=f+60*g*parseInt(e[12],10);m(e[13])&&(f+=3600*g*parseInt(e[13],10))}d.push(f)}else d.push(0)}},T(Ht,{coord:function(b,c){var d=c[c.length-1].o,e=Mo(b,!1);(e=/^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i.exec(e))? +d.push(parseFloat(e[1]),parseFloat(e[2]),parseFloat(e[3]),0):d.push(0,0,0,0)}})),au=T(It,{coordinates:ip(St)}),qu=T(It,{href:Q(Tt)},T(Ht,{x:Q(es),y:Q(es),w:Q(es),h:Q(es)})),ru=T(It,{Icon:Q(function(b,c){var d=V({},qu,b,c);return m(d)?d:null}),heading:Q(es),hotSpot:Q(function(b){var c=b.getAttribute("xunits"),d=b.getAttribute("yunits");return{x:parseFloat(b.getAttribute("x")),Rf:Qt[c],y:parseFloat(b.getAttribute("y")),Sf:Qt[d]}}),scale:Q(Ut)}),ou=T(It,{LinearRing:ip(Vt)}),su=T(It,{color:Q(Rt),scale:Q(Ut)}), +tu=T(It,{color:Q(Rt),width:Q(es)}),du=T(It,{LineString:hp(Zt),LinearRing:hp(bu),MultiGeometry:hp(cu),Point:hp(fu),Polygon:hp(gu)}),uu=T(Ht,{Track:hp(Xt)}),wu=T(It,{ExtendedData:ku,Link:function(b,c){qp(vu,b,c)},address:Q(Y),description:Q(Y),name:Q(Y),open:Q(bs),phoneNumber:Q(Y),visibility:Q(bs)}),vu=T(It,{href:Q(Tt)}),pu=T(It,{LinearRing:ip(Vt)}),xu=T(It,{Style:Q(iu),key:Q(Y),styleUrl:Q(function(b){var c=Ca(Mo(b,!1));return null!=b.baseURI?yt(b.baseURI,c).toString():c})}),zu=T(It,{ExtendedData:ku, +MultiGeometry:Q(cu,"geometry"),LineString:Q(Zt,"geometry"),LinearRing:Q(bu,"geometry"),Point:Q(fu,"geometry"),Polygon:Q(gu,"geometry"),Style:Q(iu),StyleMap:function(b,c){var d=V(void 0,yu,b,c);if(m(d)){var e=c[c.length-1];ga(d)?e.Style=d:ia(d)&&(e.styleUrl=d)}},address:Q(Y),description:Q(Y),name:Q(Y),open:Q(bs),phoneNumber:Q(Y),styleUrl:Q(Tt),visibility:Q(bs)},T(Ht,{MultiTrack:Q(function(b,c){var d=V([],uu,b,c);if(m(d)){var e=new O(null);kn(e,d);return e}},"geometry"),Track:Q(Xt,"geometry")})),Au= +T(It,{color:Q(Rt),fill:Q(bs),outline:Q(bs)}),nu=T(It,{SimpleData:function(b,c){var d=b.getAttribute("name");if(null!==d){var e=Y(b);c[c.length-1][d]=e}}}),ju=T(It,{IconStyle:function(b,c){var d=V({},ru,b,c);if(m(d)){var e=c[c.length-1],f=Ab(d,"Icon",{}),g;g=f.href;g=m(g)?g:"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png";var h,k,n,p=d.hotSpot;m(p)?(h=[p.x,p.y],k=p.Rf,n=p.Sf):"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"===g?(h=Lt,n=k="pixels"):/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(g)&& +(h=[.5,0],n=k="fraction");var q,p=f.x,r=f.y;m(p)&&m(r)&&(q=[p,r]);var t,p=f.w,f=f.h;m(p)&&m(f)&&(t=[p,f]);var v,f=d.heading;m(f)&&(v=Zb(f));d=d.scale;"https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png"==g&&(t=Mt);h=new Ck({anchor:h,anchorOrigin:"bottom-left",anchorXUnits:k,anchorYUnits:n,crossOrigin:"anonymous",offset:q,offsetOrigin:"bottom-left",rotation:v,scale:d,size:t,src:g});e.imageStyle=h}},LabelStyle:function(b,c){var d=V({},su,b,c);m(d)&&(c[c.length-1].textStyle=new Et({fill:new Hl({color:Ab(d, +"color",Jt)}),scale:d.scale}))},LineStyle:function(b,c){var d=V({},tu,b,c);m(d)&&(c[c.length-1].strokeStyle=new Dl({color:Ab(d,"color",Jt),width:Ab(d,"width",1)}))},PolyStyle:function(b,c){var d=V({},Au,b,c);if(m(d)){var e=c[c.length-1];e.fillStyle=new Hl({color:Ab(d,"color",Jt)});var f=d.fill;m(f)&&(e.fill=f);d=d.outline;m(d)&&(e.outline=d)}}}),yu=T(It,{Pair:function(b,c){var d=V({},xu,b,c);if(m(d)){var e=d.key;m(e)&&"normal"==e&&(e=d.styleUrl,m(e)&&(c[c.length-1]=e),d=d.Style,m(d)&&(c[c.length- +1]=d))}}});l=Ft.prototype;l.qh=function(b,c){Qo(b);var d=T(It,{Folder:gp(this.qh,this),Placemark:hp(this.If,this),Style:ra(this.Yn,this),StyleMap:ra(this.Xn,this)}),d=V([],d,b,c,this);if(m(d))return d};l.If=function(b,c){var d=V({geometry:null},zu,b,c);if(m(d)){var e=new Bn,f=b.getAttribute("id");null===f||e.Xb(f);f=c[0];null!=d.geometry&&Jr(d.geometry,!1,f);e.I(d);this.c&&e.uf(this.f);return e}}; +l.Yn=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=iu(b,c);m(e)&&(d=null!=b.baseURI?yt(b.baseURI,"#"+d).toString():"#"+d,this.a[d]=e)}};l.Xn=function(b,c){var d=b.getAttribute("id");if(null!==d){var e=V(void 0,yu,b,c);m(e)&&(d=null!=b.baseURI?yt(b.baseURI,"#"+d).toString():"#"+d,this.a[d]=e)}};l.rh=function(b,c){if(!Ya(It,b.namespaceURI))return null;var d=this.If(b,[Hr(this,b,c)]);return m(d)?d:null}; +l.Vb=function(b,c){if(!Ya(It,b.namespaceURI))return[];var d;d=Qo(b);if("Document"==d||"Folder"==d)return d=this.qh(b,[Hr(this,b,c)]),m(d)?d:[];if("Placemark"==d)return d=this.If(b,[Hr(this,b,c)]),m(d)?[d]:[];if("kml"==d){d=[];var e;for(e=b.firstElementChild;null!==e;e=e.nextElementSibling){var f=this.Vb(e,c);m(f)&&cb(d,f)}return d}return[]};l.Sn=function(b){if(To(b))return Bu(this,b);if(Wo(b))return Cu(this,b);if(ia(b))return b=fp(b),Bu(this,b)}; +function Bu(b,c){var d;for(d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType){var e=Cu(b,d);if(m(e))return e}}function Cu(b,c){var d;for(d=c.firstElementChild;null!==d;d=d.nextElementSibling)if(Ya(It,d.namespaceURI)&&"name"==d.localName)return Y(d);for(d=c.firstElementChild;null!==d;d=d.nextElementSibling){var e=Qo(d);if(Ya(It,d.namespaceURI)&&("Document"==e||"Folder"==e||"Placemark"==e||"kml"==e)&&(e=Cu(b,d),m(e)))return e}} +l.Tn=function(b){var c=[];To(b)?cb(c,Du(this,b)):Wo(b)?cb(c,Eu(this,b)):ia(b)&&(b=fp(b),cb(c,Du(this,b)));return c};function Du(b,c){var d,e=[];for(d=c.firstChild;null!==d;d=d.nextSibling)1==d.nodeType&&cb(e,Eu(b,d));return e} +function Eu(b,c){var d,e=[];for(d=c.firstElementChild;null!==d;d=d.nextElementSibling)if(Ya(It,d.namespaceURI)&&"NetworkLink"==d.localName){var f=V({},wu,d,[]);e.push(f)}for(d=c.firstElementChild;null!==d;d=d.nextElementSibling)f=Qo(d),!Ya(It,d.namespaceURI)||"Document"!=f&&"Folder"!=f&&"kml"!=f||cb(e,Eu(b,d));return e}function Fu(b,c){var d=wg(c),d=[255*(4==d.length?d[3]:1),d[2],d[1],d[0]],e;for(e=0;4>e;++e){var f=parseInt(d[e],10).toString(16);d[e]=1==f.length?"0"+f:f}js(b,d.join(""))} +function Gu(b,c,d){rp({node:b},Hu,Iu,[c],d)}function Ju(b,c,d){var e={node:b};null!=c.ha&&b.setAttribute("id",c.ha);b=c.P();var f=c.c;m(f)&&(f=f.call(c,0),null!==f&&0<f.length&&(b.Style=f[0],f=f[0].b,null===f||(b.name=f.c)));f=Ku[d[d.length-1].node.namespaceURI];b=pp(b,f);rp(e,Lu,op,b,d,f);b=d[0];c=c.Z();null!=c&&(c=Jr(c,!0,b));rp(e,Lu,Mu,[c],d)}function Nu(b,c,d){var e=c.o;b={node:b};b.layout=c.b;b.stride=c.H;rp(b,Ou,Pu,[e],d)} +function Qu(b,c,d){c=c.Ud();var e=c.shift();b={node:b};rp(b,Ru,Su,c,d);rp(b,Ru,Tu,[e],d)}function Uu(b,c){ks(b,c*c)} +var Vu=kp(It,["Document","Placemark"]),Yu=kp(It,{Document:U(function(b,c,d){rp({node:b},Wu,Xu,c,d)}),Placemark:U(Ju)}),Wu=kp(It,{Placemark:U(Ju)}),Zu={Point:"Point",LineString:"LineString",LinearRing:"LinearRing",Polygon:"Polygon",MultiPoint:"MultiGeometry",MultiLineString:"MultiGeometry",MultiPolygon:"MultiGeometry"},$u=kp(It,["href"],kp(Ht,["x","y","w","h"])),av=kp(It,{href:U(js)},kp(Ht,{x:U(ks),y:U(ks),w:U(ks),h:U(ks)})),bv=kp(It,["scale","heading","Icon","hotSpot"]),dv=kp(It,{Icon:U(function(b, +c,d){b={node:b};var e=$u[d[d.length-1].node.namespaceURI],f=pp(c,e);rp(b,av,op,f,d,e);e=$u[Ht[0]];f=pp(c,e);rp(b,av,cv,f,d,e)}),heading:U(ks),hotSpot:U(function(b,c){b.setAttribute("x",c.x);b.setAttribute("y",c.y);b.setAttribute("xunits",c.Rf);b.setAttribute("yunits",c.Sf)}),scale:U(Uu)}),ev=kp(It,["color","scale"]),fv=kp(It,{color:U(Fu),scale:U(Uu)}),gv=kp(It,["color","width"]),hv=kp(It,{color:U(Fu),width:U(ks)}),Hu=kp(It,{LinearRing:U(Nu)}),iv=kp(It,{LineString:U(Nu),Point:U(Nu),Polygon:U(Qu)}), +Ku=kp(It,"name open visibility address phoneNumber description styleUrl Style".split(" ")),Lu=kp(It,{MultiGeometry:U(function(b,c,d){b={node:b};var e=c.V(),f,g;"MultiPoint"==e?(f=c.pe(),g=jv):"MultiLineString"==e?(f=c.md(),g=kv):"MultiPolygon"==e&&(f=c.Wd(),g=lv);rp(b,iv,g,f,d)}),LineString:U(Nu),LinearRing:U(Nu),Point:U(Nu),Polygon:U(Qu),Style:U(function(b,c,d){b={node:b};var e={},f=c.f,g=c.c,h=c.i;c=c.b;null===h||(e.IconStyle=h);null===c||(e.LabelStyle=c);null===g||(e.LineStyle=g);null===f||(e.PolyStyle= +f);c=mv[d[d.length-1].node.namespaceURI];e=pp(e,c);rp(b,nv,op,e,d,c)}),address:U(js),description:U(js),name:U(js),open:U(is),phoneNumber:U(js),styleUrl:U(js),visibility:U(is)}),Ou=kp(It,{coordinates:U(function(b,c,d){d=d[d.length-1];var e=d.layout;d=d.stride;var f;"XY"==e||"XYM"==e?f=2:("XYZ"==e||"XYZM"==e)&&(f=3);var g,h=c.length,k="";if(0<h){k+=c[0];for(e=1;e<f;++e)k+=","+c[e];for(g=d;g<h;g+=d)for(k+=" "+c[g],e=1;e<f;++e)k+=","+c[g+e]}js(b,k)})}),Ru=kp(It,{outerBoundaryIs:U(Gu),innerBoundaryIs:U(Gu)}), +ov=kp(It,{color:U(Fu)}),mv=kp(It,["IconStyle","LabelStyle","LineStyle","PolyStyle"]),nv=kp(It,{IconStyle:U(function(b,c,d){b={node:b};var e={},f=c.fb(),g=c.Td(),h={href:c.a.i};if(null!==f){h.w=f[0];h.h=f[1];var k=c.zb(),n=c.Eb();null!==n&&null!==g&&0!==n[0]&&n[1]!==f[1]&&(h.x=n[0],h.y=g[1]-(n[1]+f[1]));null===k||0===k[0]||k[1]===f[1]||(e.hotSpot={x:k[0],Rf:"pixels",y:f[1]-k[1],Sf:"pixels"})}e.Icon=h;f=c.v;1!==f&&(e.scale=f);c=c.B;0!==c&&(e.heading=c);c=bv[d[d.length-1].node.namespaceURI];e=pp(e,c); +rp(b,dv,op,e,d,c)}),LabelStyle:U(function(b,c,d){b={node:b};var e={},f=c.a;null===f||(e.color=f.a);c=c.b;m(c)&&1!==c&&(e.scale=c);c=ev[d[d.length-1].node.namespaceURI];e=pp(e,c);rp(b,fv,op,e,d,c)}),LineStyle:U(function(b,c,d){b={node:b};var e=gv[d[d.length-1].node.namespaceURI];c=pp({color:c.a,width:c.b},e);rp(b,hv,op,c,d,e)}),PolyStyle:U(function(b,c,d){rp({node:b},ov,pv,[c.a],d)})});function cv(b,c,d){return Lo(Ht[0],"gx:"+d)} +function Xu(b,c){return Lo(c[c.length-1].node.namespaceURI,"Placemark")}function Mu(b,c){if(null!=b)return Lo(c[c.length-1].node.namespaceURI,Zu[b.V()])}var pv=mp("color"),Pu=mp("coordinates"),Su=mp("innerBoundaryIs"),jv=mp("Point"),kv=mp("LineString"),Iu=mp("LinearRing"),lv=mp("Polygon"),Tu=mp("outerBoundaryIs"); +Ft.prototype.b=function(b,c){c=Ir(this,c);var d=Lo(It[4],"kml");ep(d,"http://www.w3.org/2000/xmlns/","xmlns:gx",Ht[0]);ep(d,"http://www.w3.org/2000/xmlns/","xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");ep(d,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation","http://www.opengis.net/kml/2.2 https://developers.google.com/kml/schema/kml22gx.xsd");var e={node:d},f={};1<b.length?f.Document=b:1==b.length&&(f.Placemark=b[0]);var g=Vu[d.namespaceURI],f=pp(f,g);rp(e,Yu,op,f,[c],g); +return d};function qv(){this.defaultDataProjection=null;this.defaultDataProjection=Ae("EPSG:4326")}w(qv,Yr);function rv(b,c){c[c.length-1].Bd[b.getAttribute("k")]=b.getAttribute("v")} +var sv=[null],tv=T(sv,{nd:function(b,c){c[c.length-1].Jc.push(b.getAttribute("ref"))},tag:rv}),vv=T(sv,{node:function(b,c){var d=c[0],e=c[c.length-1],f=b.getAttribute("id"),g=[parseFloat(b.getAttribute("lon")),parseFloat(b.getAttribute("lat"))];e.Hg[f]=g;var h=V({Bd:{}},uv,b,c);xb(h.Bd)||(g=new D(g),Jr(g,!1,d),d=new Bn(g),d.Xb(f),d.I(h.Bd),e.features.push(d))},way:function(b,c){for(var d=c[0],e=b.getAttribute("id"),f=V({Jc:[],Bd:{}},tv,b,c),g=c[c.length-1],h=[],k=0,n=f.Jc.length;k<n;k++)cb(h,g.Hg[f.Jc[k]]); +f.Jc[0]==f.Jc[f.Jc.length-1]?(k=new F(null),Hf(k,"XY",h,[h.length])):(k=new N(null),hn(k,"XY",h));Jr(k,!1,d);d=new Bn(k);d.Xb(e);d.I(f.Bd);g.features.push(d)}}),uv=T(sv,{tag:rv});qv.prototype.Vb=function(b,c){var d=Hr(this,b,c);return"osm"==b.localName&&(d=V({Hg:{},features:[]},vv,b,[d]),m(d.features))?d.features:[]};function wv(b){return b.getAttributeNS("http://www.w3.org/1999/xlink","href")};function xv(){}xv.prototype.c=function(b){return To(b)?this.b(b):Wo(b)?this.a(b):ia(b)?(b=fp(b),this.b(b)):null};function yv(){}w(yv,xv);yv.prototype.b=function(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return this.a(b);return null};yv.prototype.a=function(b){b=V({},zv,b,[]);return m(b)?b:null}; +var Av=[null,"http://www.opengis.net/ows/1.1"],zv=T(Av,{ServiceIdentification:Q(function(b,c){return V({},Bv,b,c)}),ServiceProvider:Q(function(b,c){return V({},Cv,b,c)}),OperationsMetadata:Q(function(b,c){return V({},Dv,b,c)})}),Ev=T(Av,{DeliveryPoint:Q(Y),City:Q(Y),AdministrativeArea:Q(Y),PostalCode:Q(Y),Country:Q(Y),ElectronicMailAddress:Q(Y)}),Fv=T(Av,{Value:jp(function(b){return Y(b)})}),Gv=T(Av,{AllowedValues:Q(function(b,c){return V({},Fv,b,c)})}),Iv=T(Av,{Phone:Q(function(b,c){return V({}, +Hv,b,c)}),Address:Q(function(b,c){return V({},Ev,b,c)})}),Kv=T(Av,{HTTP:Q(function(b,c){return V({},Jv,b,c)})}),Jv=T(Av,{Get:jp(function(b,c){var d=wv(b);return m(d)?V({href:d},Lv,b,c):void 0}),Post:void 0}),Mv=T(Av,{DCP:Q(function(b,c){return V({},Kv,b,c)})}),Dv=T(Av,{Operation:function(b,c){var d=b.getAttribute("name"),e=V({},Mv,b,c);m(e)&&(c[c.length-1][d]=e)}}),Hv=T(Av,{Voice:Q(Y),Facsimile:Q(Y)}),Lv=T(Av,{Constraint:jp(function(b,c){var d=b.getAttribute("name");return m(d)?V({name:d},Gv,b,c): +void 0})}),Nv=T(Av,{IndividualName:Q(Y),PositionName:Q(Y),ContactInfo:Q(function(b,c){return V({},Iv,b,c)})}),Bv=T(Av,{Title:Q(Y),ServiceTypeVersion:Q(Y),ServiceType:Q(Y)}),Cv=T(Av,{ProviderName:Q(Y),ProviderSite:Q(wv),ServiceContact:Q(function(b,c){return V({},Nv,b,c)})});function Ov(b,c,d,e){var f;m(e)?f=m(void 0)?void 0:0:(e=[],f=0);var g,h;for(g=0;g<c;)for(h=b[g++],e[f++]=b[g++],e[f++]=h,h=2;h<d;++h)e[f++]=b[g++];e.length=f};function Pv(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ae("EPSG:4326");this.a=m(b.factor)?b.factor:1E5;this.b=m(b.geometryLayout)?b.geometryLayout:"XY"}w(Pv,ft);function Qv(b,c,d){d=m(d)?d:1E5;var e,f=Array(c);for(e=0;e<c;++e)f[e]=0;var g,h;g=0;for(h=b.length;g<h;)for(e=0;e<c;++e,++g){var k=b[g],n=k-f[e];f[e]=k;b[g]=n}return Rv(b,d)} +function Sv(b,c,d){var e=m(d)?d:1E5,f=Array(c);for(d=0;d<c;++d)f[d]=0;b=Tv(b,e);var g,e=0;for(g=b.length;e<g;)for(d=0;d<c;++d,++e)f[d]+=b[e],b[e]=f[d];return b}function Rv(b,c){var d=m(c)?c:1E5,e,f;e=0;for(f=b.length;e<f;++e)b[e]=Math.round(b[e]*d);d=0;for(e=b.length;d<e;++d)f=b[d],b[d]=0>f?~(f<<1):f<<1;d="";e=0;for(f=b.length;e<f;++e){for(var g=b[e],h=void 0,k="";32<=g;)h=(32|g&31)+63,k+=String.fromCharCode(h),g>>=5;h=g+63;k+=String.fromCharCode(h);d+=k}return d} +function Tv(b,c){var d=m(c)?c:1E5,e=[],f=0,g=0,h,k;h=0;for(k=b.length;h<k;++h){var n=b.charCodeAt(h)-63,f=f|(n&31)<<g;32>n?(e.push(f),g=f=0):g+=5}f=0;for(g=e.length;f<g;++f)h=e[f],e[f]=h&1?~(h>>1):h>>1;f=0;for(g=e.length;f<g;++f)e[f]/=d;return e}l=Pv.prototype;l.xd=function(b,c){var d=this.zd(b,c);return new Bn(d)};l.Gf=function(b,c){return[this.xd(b,c)]};l.zd=function(b,c){var d=$e(this.b),e=Sv(b,d,this.a);Ov(e,e.length,d,e);d=pf(e,0,e.length,d);return Jr(new N(d,this.b),!1,Ir(this,c))}; +l.Ke=function(b,c){var d=b.Z();return null!=d?this.Ed(d,c):""};l.Yh=function(b,c){return this.Ke(b[0],c)};l.Ed=function(b,c){b=Jr(b,!0,Ir(this,c));var d=b.o,e=b.H;Ov(d,d.length,e,d);return Qv(d,e,this.a)};function Uv(b){b=m(b)?b:{};this.defaultDataProjection=null;this.defaultDataProjection=Ae(null!=b.defaultDataProjection?b.defaultDataProjection:"EPSG:4326")}w(Uv,Kr);function Vv(b,c){var d=[],e,f,g,h;g=0;for(h=b.length;g<h;++g)e=b[g],0<g&&d.pop(),0<=e?f=c[e]:f=c[~e].slice().reverse(),d.push.apply(d,f);e=0;for(f=d.length;e<f;++e)d[e]=d[e].slice();return d}function Wv(b,c,d,e,f){b=b.geometries;var g=[],h,k;h=0;for(k=b.length;h<k;++h)g[h]=Xv(b[h],c,d,e,f);return g} +function Xv(b,c,d,e,f){var g=b.type,h=Yv[g];c="Point"===g||"MultiPoint"===g?h(b,d,e):h(b,c);d=new Bn;d.Sa(Jr(c,!1,f));m(b.id)&&d.Xb(b.id);m(b.properties)&&d.I(b.properties);return d} +Uv.prototype.Ff=function(b,c){if("Topology"==b.type){var d,e=null,f=null;m(b.transform)&&(d=b.transform,e=d.scale,f=d.translate);var g=b.arcs;if(m(d)){d=e;var h=f,k,n;k=0;for(n=g.length;k<n;++k)for(var p=g[k],q=d,r=h,t=0,v=0,B=void 0,z=void 0,E=void 0,z=0,E=p.length;z<E;++z)B=p[z],t+=B[0],v+=B[1],B[0]=t,B[1]=v,Zv(B,q,r)}d=[];h=sb(b.objects);k=0;for(n=h.length;k<n;++k)"GeometryCollection"===h[k].type?(p=h[k],d.push.apply(d,Wv(p,g,e,f,c))):(p=h[k],d.push(Xv(p,g,e,f,c)));return d}return[]}; +function Zv(b,c,d){b[0]=b[0]*c[0]+d[0];b[1]=b[1]*c[1]+d[1]}Uv.prototype.Ga=function(){return this.defaultDataProjection}; +var Yv={Point:function(b,c,d){b=b.coordinates;null===c||null===d||Zv(b,c,d);return new D(b)},LineString:function(b,c){var d=Vv(b.arcs,c);return new N(d)},Polygon:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=Vv(b.arcs[e],c);return new F(d)},MultiPoint:function(b,c,d){b=b.coordinates;var e,f;if(null!==c&&null!==d)for(e=0,f=b.length;e<f;++e)Zv(b[e],c,d);return new ln(b)},MultiLineString:function(b,c){var d=[],e,f;e=0;for(f=b.arcs.length;e<f;++e)d[e]=Vv(b.arcs[e],c);return new O(d)}, +MultiPolygon:function(b,c){var d=[],e,f,g,h,k,n;k=0;for(n=b.arcs.length;k<n;++k){e=b.arcs[k];f=[];g=0;for(h=e.length;g<h;++g)f[g]=Vv(e[g],c);d[k]=f}return new P(d)}};function $v(b){b=m(b)?b:{};this.g=b.featureType;this.c=b.featureNS;this.a=m(b.gmlFormat)?b.gmlFormat:new ms;this.f=m(b.schemaLocation)?b.schemaLocation:"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";this.defaultDataProjection=null}w($v,Yr);$v.prototype.Vb=function(b,c){var d={featureType:this.g,featureNS:this.c};Gb(d,Hr(this,b,m(c)?c:{}));d=[d];this.a.a["http://www.opengis.net/gml"].featureMember=hp(as.prototype.yd);d=V([],this.a.a,b,d,this.a);m(d)||(d=[]);return d}; +$v.prototype.j=function(b){if(To(b))return aw(b);if(Wo(b))return V({},bw,b,[]);if(ia(b))return b=fp(b),aw(b)};$v.prototype.i=function(b){if(To(b))return cw(this,b);if(Wo(b))return dw(this,b);if(ia(b))return b=fp(b),cw(this,b)};function cw(b,c){for(var d=c.firstChild;null!==d;d=d.nextSibling)if(1==d.nodeType)return dw(b,d)}var ew={"http://www.opengis.net/gml":{boundedBy:Q(as.prototype.Ce,"bounds")}}; +function dw(b,c){var d={},e=hs(c.getAttribute("numberOfFeatures"));d.numberOfFeatures=e;return V(d,ew,c,[],b.a)} +var fw={"http://www.opengis.net/wfs":{totalInserted:Q(gs),totalUpdated:Q(gs),totalDeleted:Q(gs)}},gw={"http://www.opengis.net/ogc":{FeatureId:hp(function(b){return b.getAttribute("fid")})}},hw={"http://www.opengis.net/wfs":{Feature:function(b,c){qp(gw,b,c)}}},bw={"http://www.opengis.net/wfs":{TransactionSummary:Q(function(b,c){return V({},fw,b,c)},"transactionSummary"),InsertResults:Q(function(b,c){return V([],hw,b,c)},"insertIds")}}; +function aw(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return V({},bw,b,[])}var iw={"http://www.opengis.net/wfs":{PropertyName:U(js)}};function jw(b,c){var d=Lo("http://www.opengis.net/ogc","Filter"),e=Lo("http://www.opengis.net/ogc","FeatureId");d.appendChild(e);e.setAttribute("fid",c);b.appendChild(d)} +var kw={"http://www.opengis.net/wfs":{Insert:U(function(b,c,d){var e=d[d.length-1],e=Lo(e.featureNS,e.featureType);b.appendChild(e);ms.prototype.Xh(e,c,d)}),Update:U(function(b,c,d){var e=d[d.length-1],f=e.featureType,g=e.featurePrefix,g=m(g)?g:"feature",h=e.featureNS;b.setAttribute("typeName",g+":"+f);ep(b,"http://www.w3.org/2000/xmlns/","xmlns:"+g,h);f=c.ha;if(m(f)){for(var g=c.O(),h=[],k=0,n=g.length;k<n;k++){var p=c.get(g[k]);m(p)&&h.push({name:g[k],value:p})}rp({node:b,srsName:e.srsName},kw, +mp("Property"),h,d);jw(b,f)}}),Delete:U(function(b,c,d){var e=d[d.length-1];d=e.featureType;var f=e.featurePrefix,f=m(f)?f:"feature",e=e.featureNS;b.setAttribute("typeName",f+":"+d);ep(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,e);c=c.ha;m(c)&&jw(b,c)}),Property:U(function(b,c,d){var e=Lo("http://www.opengis.net/wfs","Name");b.appendChild(e);js(e,c.name);null!=c.value&&(e=Lo("http://www.opengis.net/wfs","Value"),b.appendChild(e),c.value instanceof Xe?ms.prototype.Me(e,c.value,d):js(e,c.value))}), +Native:U(function(b,c){m(c.Lo)&&b.setAttribute("vendorId",c.Lo);m(c.ko)&&b.setAttribute("safeToIgnore",c.ko);m(c.value)&&js(b,c.value)})}},lw={"http://www.opengis.net/wfs":{Query:U(function(b,c,d){var e=d[d.length-1],f=e.featurePrefix,g=e.featureNS,h=e.propertyNames,k=e.srsName;b.setAttribute("typeName",(m(f)?f+":":"")+c);m(k)&&b.setAttribute("srsName",k);m(g)&&ep(b,"http://www.w3.org/2000/xmlns/","xmlns:"+f,g);c=Cb(e);c.node=b;rp(c,iw,mp("PropertyName"),h,d);e=e.bbox;m(e)&&(h=Lo("http://www.opengis.net/ogc", +"Filter"),c=d[d.length-1].geometryName,f=Lo("http://www.opengis.net/ogc","BBOX"),h.appendChild(f),g=Lo("http://www.opengis.net/ogc","PropertyName"),js(g,c),f.appendChild(g),ms.prototype.Me(f,e,d),b.appendChild(h))})}}; +$v.prototype.l=function(b){var c=Lo("http://www.opengis.net/wfs","GetFeature");c.setAttribute("service","WFS");c.setAttribute("version","1.1.0");m(b)&&(m(b.handle)&&c.setAttribute("handle",b.handle),m(b.outputFormat)&&c.setAttribute("outputFormat",b.outputFormat),m(b.maxFeatures)&&c.setAttribute("maxFeatures",b.maxFeatures),m(b.resultType)&&c.setAttribute("resultType",b.resultType),m(b.Eo)&&c.setAttribute("startIndex",b.Eo),m(b.count)&&c.setAttribute("count",b.count));ep(c,"http://www.w3.org/2001/XMLSchema-instance", +"xsi:schemaLocation",this.f);var d=b.featureTypes;b=[{node:c,srsName:b.srsName,featureNS:m(b.featureNS)?b.featureNS:this.c,featurePrefix:b.featurePrefix,geometryName:b.geometryName,bbox:b.bbox,propertyNames:m(b.propertyNames)?b.propertyNames:[]}];var e=Cb(b[b.length-1]);e.node=c;rp(e,lw,mp("Query"),d,b);return c}; +$v.prototype.A=function(b,c,d,e){var f=[],g=Lo("http://www.opengis.net/wfs","Transaction");g.setAttribute("service","WFS");g.setAttribute("version","1.1.0");var h,k;m(e)&&(h=m(e.gmlOptions)?e.gmlOptions:{},m(e.handle)&&g.setAttribute("handle",e.handle));ep(g,"http://www.w3.org/2001/XMLSchema-instance","xsi:schemaLocation",this.f);null!=b&&(k={node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},Gb(k,h),rp(k,kw,mp("Insert"),b,f));null!=c&&(k={node:g,featureNS:e.featureNS, +featureType:e.featureType,featurePrefix:e.featurePrefix},Gb(k,h),rp(k,kw,mp("Update"),c,f));null!=d&&rp({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},kw,mp("Delete"),d,f);m(e.nativeElements)&&rp({node:g,featureNS:e.featureNS,featureType:e.featureType,featurePrefix:e.featurePrefix},kw,mp("Native"),e.nativeElements,f);return g};$v.prototype.Jf=function(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return this.Fe(b);return null}; +$v.prototype.Fe=function(b){if(null!=b.firstElementChild&&null!=b.firstElementChild.firstElementChild)for(b=b.firstElementChild.firstElementChild,b=b.firstElementChild;null!==b;b=b.nextElementSibling)if(0!==b.childNodes.length&&(1!==b.childNodes.length||3!==b.firstChild.nodeType)){var c=[{}];this.a.Ce(b,c);return Ae(c.pop().srsName)}return null};function mw(b){b=m(b)?b:{};this.defaultDataProjection=null;this.a=m(b.splitCollection)?b.splitCollection:!1}w(mw,ft);function nw(b){b=b.W();return 0==b.length?"":b[0]+" "+b[1]}function ow(b){b=b.W();for(var c=[],d=0,e=b.length;d<e;++d)c.push(b[d][0]+" "+b[d][1]);return c.join(",")}function pw(b){var c=[];b=b.Ud();for(var d=0,e=b.length;d<e;++d)c.push("("+ow(b[d])+")");return c.join(",")}function qw(b){var c=b.V();b=(0,rw[c])(b);c=c.toUpperCase();return 0===b.length?c+" EMPTY":c+"("+b+")"} +var rw={Point:nw,LineString:ow,Polygon:pw,MultiPoint:function(b){var c=[];b=b.pe();for(var d=0,e=b.length;d<e;++d)c.push("("+nw(b[d])+")");return c.join(",")},MultiLineString:function(b){var c=[];b=b.md();for(var d=0,e=b.length;d<e;++d)c.push("("+ow(b[d])+")");return c.join(",")},MultiPolygon:function(b){var c=[];b=b.Wd();for(var d=0,e=b.length;d<e;++d)c.push("("+pw(b[d])+")");return c.join(",")},GeometryCollection:function(b){var c=[];b=b.mg();for(var d=0,e=b.length;d<e;++d)c.push(qw(b[d]));return c.join(",")}}; +l=mw.prototype;l.xd=function(b,c){var d=this.zd(b,c);if(m(d)){var e=new Bn;e.Sa(d);return e}return null};l.Gf=function(b,c){var d=[],e=this.zd(b,c);this.a&&"GeometryCollection"==e.V()?d=e.f:d=[e];for(var f=[],g=0,h=d.length;g<h;++g)e=new Bn,e.Sa(d[g]),f.push(e);return f};l.zd=function(b,c){var d;d=new sw(new tw(b));d.a=uw(d.b);d=vw(d);return m(d)?Jr(d,!1,c):null};l.Ke=function(b,c){var d=b.Z();return m(d)?this.Ed(d,c):""}; +l.Yh=function(b,c){if(1==b.length)return this.Ke(b[0],c);for(var d=[],e=0,f=b.length;e<f;++e)d.push(b[e].Z());d=new an(d);return this.Ed(d,c)};l.Ed=function(b,c){return qw(Jr(b,!0,c))};function tw(b){this.b=b;this.a=-1}function ww(b,c){var d=m(c)?c:!1;return"0"<=b&&"9">=b||"."==b&&!d} +function uw(b){var c=b.b.charAt(++b.a),d={position:b.a,value:c};if("("==c)d.type=2;else if(","==c)d.type=5;else if(")"==c)d.type=3;else if(ww(c)||"-"==c){d.type=4;var e,c=b.a,f=!1,g=!1;do{if("."==e)f=!0;else if("e"==e||"E"==e)g=!0;e=b.b.charAt(++b.a)}while(ww(e,f)||!g&&("e"==e||"E"==e)||g&&("-"==e||"+"==e));b=parseFloat(b.b.substring(c,b.a--));d.value=b}else if("a"<=c&&"z">=c||"A"<=c&&"Z">=c){d.type=1;c=b.a;do e=b.b.charAt(++b.a);while("a"<=e&&"z">=e||"A"<=e&&"Z">=e);b=b.b.substring(c,b.a--).toUpperCase(); +d.value=b}else{if(" "==c||"\t"==c||"\r"==c||"\n"==c)return uw(b);if(""===c)d.type=6;else throw Error("Unexpected character: "+c);}return d}function sw(b){this.b=b}l=sw.prototype;l.match=function(b){if(b=this.a.type==b)this.a=uw(this.b);return b}; +function vw(b){var c=b.a;if(b.match(1)){var d=c.value;if("GEOMETRYCOLLECTION"==d){a:{if(b.match(2)){c=[];do c.push(vw(b));while(b.match(5));if(b.match(3)){b=c;break a}}else if(xw(b)){b=[];break a}throw Error(yw(b));}return new an(b)}var e=zw[d],c=Aw[d];if(!m(e)||!m(c))throw Error("Invalid geometry type: "+d);b=e.call(b);return new c(b)}throw Error(yw(b));}l.Cf=function(){if(this.match(2)){var b=Bw(this);if(this.match(3))return b}else if(xw(this))return null;throw Error(yw(this));}; +l.Bf=function(){if(this.match(2)){var b=Cw(this);if(this.match(3))return b}else if(xw(this))return[];throw Error(yw(this));};l.Df=function(){if(this.match(2)){var b=Dw(this);if(this.match(3))return b}else if(xw(this))return[];throw Error(yw(this));};l.zn=function(){if(this.match(2)){var b;if(2==this.a.type)for(b=[this.Cf()];this.match(5);)b.push(this.Cf());else b=Cw(this);if(this.match(3))return b}else if(xw(this))return[];throw Error(yw(this));}; +l.yn=function(){if(this.match(2)){var b=Dw(this);if(this.match(3))return b}else if(xw(this))return[];throw Error(yw(this));};l.An=function(){if(this.match(2)){for(var b=[this.Df()];this.match(5);)b.push(this.Df());if(this.match(3))return b}else if(xw(this))return[];throw Error(yw(this));};function Bw(b){for(var c=[],d=0;2>d;++d){var e=b.a;if(b.match(4))c.push(e.value);else break}if(2==c.length)return c;throw Error(yw(b));}function Cw(b){for(var c=[Bw(b)];b.match(5);)c.push(Bw(b));return c} +function Dw(b){for(var c=[b.Bf()];b.match(5);)c.push(b.Bf());return c}function xw(b){var c=1==b.a.type&&"EMPTY"==b.a.value;c&&(b.a=uw(b.b));return c}function yw(b){return"Unexpected `"+b.a.value+"` at position "+b.a.position+" in `"+b.b.b+"`"}var Aw={POINT:D,LINESTRING:N,POLYGON:F,MULTIPOINT:ln,MULTILINESTRING:O,MULTIPOLYGON:P},zw={POINT:sw.prototype.Cf,LINESTRING:sw.prototype.Bf,POLYGON:sw.prototype.Df,MULTIPOINT:sw.prototype.zn,MULTILINESTRING:sw.prototype.yn,MULTIPOLYGON:sw.prototype.An};function Ew(){this.version=void 0}w(Ew,xv);Ew.prototype.b=function(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return this.a(b);return null};Ew.prototype.a=function(b){this.version=Ca(b.getAttribute("version"));b=V({version:this.version},Fw,b,[]);return m(b)?b:null};function Gw(b,c){return V({},Hw,b,c)}function Iw(b,c){return V({},Jw,b,c)}function Kw(b,c){var d=Gw(b,c);if(m(d)){var e=[hs(b.getAttribute("width")),hs(b.getAttribute("height"))];d.size=e;return d}} +function Lw(b,c){return V([],Mw,b,c)} +var Nw=[null,"http://www.opengis.net/wms"],Fw=T(Nw,{Service:Q(function(b,c){return V({},Ow,b,c)}),Capability:Q(function(b,c){return V({},Pw,b,c)})}),Pw=T(Nw,{Request:Q(function(b,c){return V({},Qw,b,c)}),Exception:Q(function(b,c){return V([],Rw,b,c)}),Layer:Q(function(b,c){return V({},Sw,b,c)})}),Ow=T(Nw,{Name:Q(Y),Title:Q(Y),Abstract:Q(Y),KeywordList:Q(Lw),OnlineResource:Q(wv),ContactInformation:Q(function(b,c){return V({},Tw,b,c)}),Fees:Q(Y),AccessConstraints:Q(Y),LayerLimit:Q(gs),MaxWidth:Q(gs), +MaxHeight:Q(gs)}),Tw=T(Nw,{ContactPersonPrimary:Q(function(b,c){return V({},Uw,b,c)}),ContactPosition:Q(Y),ContactAddress:Q(function(b,c){return V({},Vw,b,c)}),ContactVoiceTelephone:Q(Y),ContactFacsimileTelephone:Q(Y),ContactElectronicMailAddress:Q(Y)}),Uw=T(Nw,{ContactPerson:Q(Y),ContactOrganization:Q(Y)}),Vw=T(Nw,{AddressType:Q(Y),Address:Q(Y),City:Q(Y),StateOrProvince:Q(Y),PostCode:Q(Y),Country:Q(Y)}),Rw=T(Nw,{Format:hp(Y)}),Sw=T(Nw,{Name:Q(Y),Title:Q(Y),Abstract:Q(Y),KeywordList:Q(Lw),CRS:jp(Y), +EX_GeographicBoundingBox:Q(function(b,c){var d=V({},Ww,b,c);if(m(d)){var e=d.westBoundLongitude,f=d.southBoundLatitude,g=d.eastBoundLongitude,d=d.northBoundLatitude;return m(e)&&m(f)&&m(g)&&m(d)?[e,f,g,d]:void 0}}),BoundingBox:jp(function(b){var c=[fs(b.getAttribute("minx")),fs(b.getAttribute("miny")),fs(b.getAttribute("maxx")),fs(b.getAttribute("maxy"))],d=[fs(b.getAttribute("resx")),fs(b.getAttribute("resy"))];return{crs:b.getAttribute("CRS"),extent:c,res:d}}),Dimension:jp(function(b){return{name:b.getAttribute("name"), +units:b.getAttribute("units"),unitSymbol:b.getAttribute("unitSymbol"),"default":b.getAttribute("default"),multipleValues:cs(b.getAttribute("multipleValues")),nearestValue:cs(b.getAttribute("nearestValue")),current:cs(b.getAttribute("current")),values:Y(b)}}),Attribution:Q(function(b,c){return V({},Xw,b,c)}),AuthorityURL:jp(function(b,c){var d=Gw(b,c);if(m(d))return d.name=b.getAttribute("name"),d}),Identifier:jp(Y),MetadataURL:jp(function(b,c){var d=Gw(b,c);if(m(d))return d.type=b.getAttribute("type"), +d}),DataURL:jp(Gw),FeatureListURL:jp(Gw),Style:jp(function(b,c){return V({},Yw,b,c)}),MinScaleDenominator:Q(es),MaxScaleDenominator:Q(es),Layer:jp(function(b,c){var d=c[c.length-1],e=V({},Sw,b,c);if(m(e)){var f=cs(b.getAttribute("queryable"));m(f)||(f=d.queryable);e.queryable=m(f)?f:!1;f=hs(b.getAttribute("cascaded"));m(f)||(f=d.cascaded);e.cascaded=f;f=cs(b.getAttribute("opaque"));m(f)||(f=d.opaque);e.opaque=m(f)?f:!1;f=cs(b.getAttribute("noSubsets"));m(f)||(f=d.noSubsets);e.noSubsets=m(f)?f:!1; +f=fs(b.getAttribute("fixedWidth"));m(f)||(f=d.fixedWidth);e.fixedWidth=f;f=fs(b.getAttribute("fixedHeight"));m(f)||(f=d.fixedHeight);e.fixedHeight=f;Sa(["Style","CRS","AuthorityURL"],function(b){var c=d[b];if(m(c)){var f=Bb(e,b),f=f.concat(c);e[b]=f}});Sa("EX_GeographicBoundingBox BoundingBox Dimension Attribution MinScaleDenominator MaxScaleDenominator".split(" "),function(b){m(e[b])||(e[b]=d[b])});return e}})}),Xw=T(Nw,{Title:Q(Y),OnlineResource:Q(wv),LogoURL:Q(Kw)}),Ww=T(Nw,{westBoundLongitude:Q(es), +eastBoundLongitude:Q(es),southBoundLatitude:Q(es),northBoundLatitude:Q(es)}),Qw=T(Nw,{GetCapabilities:Q(Iw),GetMap:Q(Iw),GetFeatureInfo:Q(Iw)}),Jw=T(Nw,{Format:jp(Y),DCPType:jp(function(b,c){return V({},Zw,b,c)})}),Zw=T(Nw,{HTTP:Q(function(b,c){return V({},$w,b,c)})}),$w=T(Nw,{Get:Q(Gw),Post:Q(Gw)}),Yw=T(Nw,{Name:Q(Y),Title:Q(Y),Abstract:Q(Y),LegendURL:jp(Kw),StyleSheetURL:Q(Gw),StyleURL:Q(Gw)}),Hw=T(Nw,{Format:Q(Y),OnlineResource:Q(wv)}),Mw=T(Nw,{Keyword:hp(Y)});function ax(){this.c="http://mapserver.gis.umn.edu/mapserver";this.a=new vs;this.defaultDataProjection=null}w(ax,Yr); +function bx(b,c,d){c.namespaceURI=b.c;var e=Qo(c),f=[];if(0===c.childNodes.length)return f;"msGMLOutput"==e&&Sa(c.childNodes,function(b){if(1===b.nodeType){var c=d[0],e=b.localName,n=RegExp,p;p="_layer".replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08");n=new n(p,"");e=e.replace(n,"")+"_feature";c.featureType=e;c.featureNS=this.c;n={};n[e]=hp(this.a.Ef,this.a);c=T([c.featureNS,null],n);b.namespaceURI=this.c;b=V([],c,b,d,this.a);m(b)&&cb(f,b)}},b);"FeatureCollection"==e&&(b=V([], +b.a.a,c,[{}],b.a),m(b)&&(f=b));return f}ax.prototype.Vb=function(b,c){var d={featureType:this.featureType,featureNS:this.featureNS};m(c)&&Gb(d,Hr(this,b,c));return bx(this,b,[d])};function cx(){this.f=new yv}w(cx,xv);cx.prototype.b=function(b){for(b=b.firstChild;null!==b;b=b.nextSibling)if(1==b.nodeType)return this.a(b);return null};cx.prototype.a=function(b){this.version=Ca(b.getAttribute("version"));var c=this.f.a(b);if(!m(c))return null;c.version=this.version;c=V(c,dx,b,[]);return m(c)?c:null};function ex(b){var c=Y(b).split(" ");if(m(c)&&2==c.length)return b=+c[0],c=+c[1],isNaN(b)||isNaN(c)?void 0:[b,c]} +var fx=[null,"http://www.opengis.net/wmts/1.0"],gx=[null,"http://www.opengis.net/ows/1.1"],dx=T(fx,{Contents:Q(function(b,c){return V({},hx,b,c)})}),hx=T(fx,{Layer:jp(function(b,c){return V({},ix,b,c)}),TileMatrixSet:jp(function(b,c){return V({},jx,b,c)})}),ix=T(fx,{Style:jp(function(b,c){var d=V({},kx,b,c);if(m(d)){var e="true"===b.getAttribute("isDefault");d.isDefault=e;return d}}),Format:jp(Y),TileMatrixSetLink:jp(function(b,c){return V({},lx,b,c)}),ResourceURL:jp(function(b){var c=b.getAttribute("format"), +d=b.getAttribute("template");b=b.getAttribute("resourceType");var e={};m(c)&&(e.format=c);m(d)&&(e.template=d);m(b)&&(e.resourceType=b);return e})},T(gx,{Title:Q(Y),Abstract:Q(Y),WGS84BoundingBox:Q(function(b,c){var d=V([],mx,b,c);return 2!=d.length?void 0:Kd(d)}),Identifier:Q(Y)})),kx=T(fx,{LegendURL:jp(function(b){var c={};c.format=b.getAttribute("format");c.href=wv(b);return c})},T(gx,{Title:Q(Y),Identifier:Q(Y)})),lx=T(fx,{TileMatrixSet:Q(Y)}),mx=T(gx,{LowerCorner:hp(ex),UpperCorner:hp(ex)}), +jx=T(fx,{WellKnownScaleSet:Q(Y),TileMatrix:jp(function(b,c){return V({},nx,b,c)})},T(gx,{SupportedCRS:Q(Y),Identifier:Q(Y)})),nx=T(fx,{TopLeftCorner:Q(ex),ScaleDenominator:Q(es),TileWidth:Q(gs),TileHeight:Q(gs),MatrixWidth:Q(gs),MatrixHeight:Q(gs)},T(gx,{Identifier:Q(Y)}));var ox=new ve(6378137);function px(b){gd.call(this);b=m(b)?b:{};this.b=null;this.f=Ue;this.c=void 0;x(this,id("projection"),this.fl,!1,this);x(this,id("tracking"),this.gl,!1,this);m(b.projection)&&this.Lg(Ae(b.projection));m(b.trackingOptions)&&this.Oh(b.trackingOptions);this.le(m(b.tracking)?b.tracking:!1)}w(px,gd);l=px.prototype;l.X=function(){this.le(!1);px.aa.X.call(this)};l.fl=function(){var b=this.Jg();null!=b&&(this.f=Ee(Ae("EPSG:4326"),b),null===this.b||this.set("position",this.f(this.b)))}; +l.gl=function(){if(gj){var b=this.Kg();b&&!m(this.c)?this.c=ba.navigator.geolocation.watchPosition(ra(this.Hn,this),ra(this.In,this),this.vg()):!b&&m(this.c)&&(ba.navigator.geolocation.clearWatch(this.c),this.c=void 0)}}; +l.Hn=function(b){b=b.coords;this.set("accuracy",b.accuracy);this.set("altitude",null===b.altitude?void 0:b.altitude);this.set("altitudeAccuracy",null===b.altitudeAccuracy?void 0:b.altitudeAccuracy);this.set("heading",null===b.heading?void 0:Zb(b.heading));null===this.b?this.b=[b.longitude,b.latitude]:(this.b[0]=b.longitude,this.b[1]=b.latitude);var c=this.f(this.b);this.set("position",c);this.set("speed",null===b.speed?void 0:b.speed);b=Kf(ox,this.b,b.accuracy);b.va(this.f);this.set("accuracyGeometry", +b);this.s()};l.In=function(b){b.type="error";this.le(!1);C(this,b)};l.cj=function(){return this.get("accuracy")};l.dj=function(){return this.get("accuracyGeometry")||null};l.fj=function(){return this.get("altitude")};l.gj=function(){return this.get("altitudeAccuracy")};l.dl=function(){return this.get("heading")};l.el=function(){return this.get("position")};l.Jg=function(){return this.get("projection")};l.Nj=function(){return this.get("speed")};l.Kg=function(){return this.get("tracking")};l.vg=function(){return this.get("trackingOptions")}; +l.Lg=function(b){this.set("projection",b)};l.le=function(b){this.set("tracking",b)};l.Oh=function(b){this.set("trackingOptions",b)};function qx(b,c,d){for(var e=[],f=b(0),g=b(1),h=c(f),k=c(g),n=[g,f],p=[k,h],q=[1,0],r={},t=1E5,v,B,z,E,A;0<--t&&0<q.length;)z=q.pop(),f=n.pop(),h=p.pop(),g=z.toString(),g in r||(e.push(h[0],h[1]),r[g]=!0),E=q.pop(),g=n.pop(),k=p.pop(),A=(z+E)/2,v=b(A),B=c(v),ef(B[0],B[1],h[0],h[1],k[0],k[1])<d?(e.push(k[0],k[1]),g=E.toString(),r[g]=!0):(q.push(E,A,A,z),p.push(k,B,B,h),n.push(g,v,v,f));return e}function rx(b,c,d,e,f){var g=Ae("EPSG:4326");return qx(function(e){return[b,c+(d-c)*e]},Te(g,e),f)} +function sx(b,c,d,e,f){var g=Ae("EPSG:4326");return qx(function(e){return[c+(d-c)*e,b]},Te(g,e),f)};function tx(b){b=m(b)?b:{};this.l=this.j=null;this.f=this.c=Infinity;this.i=this.g=-Infinity;this.fa=m(b.targetSize)?b.targetSize:100;this.u=m(b.maxLines)?b.maxLines:100;this.a=[];this.b=[];this.G=m(b.strokeStyle)?b.strokeStyle:ux;this.A=this.B=void 0;this.v=null;this.setMap(m(b.map)?b.map:null)}var ux=new Dl({color:"rgba(0,0,0,0.2)"}),vx=[90,45,30,20,10,5,2,1,.5,.2,.1,.05,.01,.005,.002,.001]; +function wx(b,c,d,e,f){var g=f;c=rx(c,b.g,b.c,b.l,d);g=m(b.a[g])?b.a[g]:new N(null);hn(g,"XY",c);ie(g.R(),e)&&(b.a[f++]=g);return f}function xx(b,c,d,e,f){var g=f;c=sx(c,b.i,b.f,b.l,d);g=m(b.b[g])?b.b[g]:new N(null);hn(g,"XY",c);ie(g.R(),e)&&(b.b[f++]=g);return f}l=tx.prototype;l.hl=function(){return this.j};l.Cj=function(){return this.a};l.Hj=function(){return this.b}; +l.Ag=function(b){var c=b.vectorContext,d=b.frameState;b=d.extent;var e=d.viewState,f=e.center,g=e.projection,e=e.resolution,d=d.pixelRatio,d=e*e/(4*d*d);if(null===this.l||!Se(this.l,g)){var h=g.R(),k=g.j,n=k[2],p=k[1],q=k[0];this.c=k[3];this.f=n;this.g=p;this.i=q;k=Ae("EPSG:4326");this.B=Te(k,g);this.A=Te(g,k);this.v=this.A(ee(h));this.l=g}for(var g=this.v[0],h=this.v[1],k=-1,r,p=Math.pow(this.fa*e,2),q=[],t=[],e=0,n=vx.length;e<n;++e){r=vx[e]/2;q[0]=g-r;q[1]=h-r;t[0]=g+r;t[1]=h+r;this.B(q,q);this.B(t, +t);r=Math.pow(t[0]-q[0],2)+Math.pow(t[1]-q[1],2);if(r<=p)break;k=vx[e]}e=k;if(-1==e)this.a.length=this.b.length=0;else{g=this.A(f);f=g[0];g=g[1];h=this.u;f=Math.floor(f/e)*e;p=Wb(f,this.i,this.f);n=wx(this,p,d,b,0);for(k=0;p!=this.i&&k++<h;)p=Math.max(p-e,this.i),n=wx(this,p,d,b,n);p=Wb(f,this.i,this.f);for(k=0;p!=this.f&&k++<h;)p=Math.min(p+e,this.f),n=wx(this,p,d,b,n);this.a.length=n;g=Math.floor(g/e)*e;f=Wb(g,this.g,this.c);n=xx(this,f,d,b,0);for(k=0;f!=this.g&&k++<h;)f=Math.max(f-e,this.g),n= +xx(this,f,d,b,n);f=Wb(g,this.g,this.c);for(k=0;f!=this.c&&k++<h;)f=Math.min(f+e,this.c),n=xx(this,f,d,b,n);this.b.length=n}c.Ha(null,this.G);b=0;for(d=this.a.length;b<d;++b)f=this.a[b],c.Hb(f,null);b=0;for(d=this.b.length;b<d;++b)f=this.b[b],c.Hb(f,null)};l.setMap=function(b){null!==this.j&&(this.j.J("postcompose",this.Ag,this),this.j.render());null!==b&&(b.D("postcompose",this.Ag,this),b.render());this.j=b};function yx(b,c,d,e,f,g,h){mk.call(this,b,c,d,0,e);this.l=f;this.b=new Image;null!==g&&(this.b.crossOrigin=g);this.f={};this.c=null;this.state=0;this.j=h}w(yx,mk);yx.prototype.a=function(b){if(m(b)){var c=ma(b);if(c in this.f)return this.f[c];b=xb(this.f)?this.b:this.b.cloneNode(!1);return this.f[c]=b}return this.b};yx.prototype.B=function(){this.state=3;Sa(this.c,Xc);this.c=null;nk(this)}; +yx.prototype.v=function(){m(this.resolution)||(this.resolution=ge(this.extent)/this.b.height);this.state=2;Sa(this.c,Xc);this.c=null;nk(this)};yx.prototype.load=function(){0==this.state&&(this.state=1,nk(this),this.c=[Vc(this.b,"error",this.B,!1,this),Vc(this.b,"load",this.v,!1,this)],this.j(this,this.l))};function zx(b,c,d,e,f){Bh.call(this,b,c);this.j=d;this.b=new Image;null!==e&&(this.b.crossOrigin=e);this.c={};this.g=null;this.l=f}w(zx,Bh);l=zx.prototype;l.X=function(){1==this.state&&Ax(this);zx.aa.X.call(this)};l.Ta=function(b){if(m(b)){var c=ma(b);if(c in this.c)return this.c[c];b=xb(this.c)?this.b:this.b.cloneNode(!1);return this.c[c]=b}return this.b};l.pb=function(){return this.j};l.il=function(){this.state=3;Ax(this);Ch(this)}; +l.jl=function(){this.state=this.b.naturalWidth&&this.b.naturalHeight?2:4;Ax(this);Ch(this)};l.load=function(){0==this.state&&(this.state=1,Ch(this),this.g=[Vc(this.b,"error",this.il,!1,this),Vc(this.b,"load",this.jl,!1,this)],this.l(this,this.j))};function Ax(b){Sa(b.g,Xc);b.g=null};function Bx(b,c,d){return function(e,f,g){return d(b,c,e,f,g)}}function Cx(){};function Dx(b,c){ad.call(this);this.a=new or(this);var d=b;c&&(d=Hg(b));this.a.Ra(d,"dragenter",this.nn);d!=b&&this.a.Ra(d,"dragover",this.pn);this.a.Ra(b,"dragover",this.qn);this.a.Ra(b,"drop",this.rn)}w(Dx,ad);l=Dx.prototype;l.kd=!1;l.X=function(){Dx.aa.X.call(this);this.a.jd()};l.nn=function(b){var c=b.a.dataTransfer;(this.kd=!(!c||!(c.types&&(Ya(c.types,"Files")||Ya(c.types,"public.file-url"))||c.files&&0<c.files.length)))&&b.preventDefault()}; +l.pn=function(b){this.kd&&(b.preventDefault(),b.a.dataTransfer.dropEffect="none")};l.qn=function(b){this.kd&&(b.preventDefault(),b.ob(),b=b.a.dataTransfer,b.effectAllowed="all",b.dropEffect="copy")};l.rn=function(b){this.kd&&(b.preventDefault(),b.ob(),b=new xc(b.a),b.type="drop",C(this,b))};/* Portions of this code are from MochiKit, received by The Closure Authors under the MIT license. All other code is Copyright 2005-2009 The Closure Authors. All Rights Reserved. */ -function yv(b,c){this.e=[];this.p=b;this.j=c||null;this.d=this.a=!1;this.b=void 0;this.i=this.q=this.g=!1;this.f=0;this.c=null;this.o=0}yv.prototype.cancel=function(b){if(this.a)this.b instanceof yv&&this.b.cancel();else{if(this.c){var c=this.c;delete this.c;b?c.cancel(b):(c.o--,0>=c.o&&c.cancel())}this.p?this.p.call(this.j,this):this.i=!0;this.a||(b=new zv,Av(this),Bv(this,!1,b))}};yv.prototype.n=function(b,c){this.g=!1;Bv(this,b,c)};function Bv(b,c,d){b.a=!0;b.b=d;b.d=!c;Cv(b)} -function Av(b){if(b.a){if(!b.i)throw new Dv;b.i=!1}}function Ev(b,c,d,e){b.e.push([c,d,e]);b.a&&Cv(b)}yv.prototype.then=function(b,c,d){var e,f,g=new kv(function(b,c){e=b;f=c});Ev(this,e,function(b){b instanceof zv?g.cancel():f(b)});return g.then(b,c,d)};bv(yv);function Fv(b){return Wa(b.e,function(b){return ka(b[1])})} -function Cv(b){if(b.f&&b.a&&Fv(b)){var c=b.f,d=Gv[c];d&&(ba.clearTimeout(d.X),delete Gv[c]);b.f=0}b.c&&(b.c.o--,delete b.c);for(var c=b.b,e=d=!1;b.e.length&&!b.g;){var f=b.e.shift(),g=f[0],h=f[1],f=f[2];if(g=b.d?h:g)try{var k=g.call(f||b.j,c);m(k)&&(b.d=b.d&&(k==c||k instanceof Error),b.b=c=k);cv(c)&&(e=!0,b.g=!0)}catch(n){c=n,b.d=!0,Fv(b)||(d=!0)}}b.b=c;e&&(k=sa(b.n,b,!0),e=sa(b.n,b,!1),c instanceof yv?(Ev(c,k,e),c.q=!0):c.then(k,e));d&&(c=new Hv(c),Gv[c.X]=c,b.f=c.X)} -function Dv(){xa.call(this)}u(Dv,xa);Dv.prototype.message="Deferred has already fired";Dv.prototype.name="AlreadyCalledError";function zv(){xa.call(this)}u(zv,xa);zv.prototype.message="Deferred was canceled";zv.prototype.name="CanceledError";function Hv(b){this.X=ba.setTimeout(sa(this.c,this),0);this.a=b}Hv.prototype.c=function(){delete Gv[this.X];throw this.a;};var Gv={};function Iv(b,c){m(b.name)?(this.name=b.name,this.code=wb[b.name]):(this.code=b.code,this.name=Jv(b.code));xa.call(this,za("%s %s",this.name,c))}u(Iv,xa);function Jv(b){var c=ub(function(c){return b==c});if(!m(c))throw Error("Invalid code: "+b);return c}var wb={AbortError:3,EncodingError:5,InvalidModificationError:9,InvalidStateError:7,NotFoundError:1,NotReadableError:4,NoModificationAllowedError:6,PathExistsError:12,QuotaExceededError:10,SecurityError:2,SyntaxError:8,TypeMismatchError:11};function Kv(b,c){uc.call(this,b.type,c)}u(Kv,uc);function Lv(){jd.call(this);this.bb=new FileReader;this.bb.onloadstart=sa(this.a,this);this.bb.onprogress=sa(this.a,this);this.bb.onload=sa(this.a,this);this.bb.onabort=sa(this.a,this);this.bb.onerror=sa(this.a,this);this.bb.onloadend=sa(this.a,this)}u(Lv,jd);Lv.prototype.getError=function(){return this.bb.error&&new Iv(this.bb.error,"reading file")};Lv.prototype.a=function(b){this.dispatchEvent(new Kv(b,this))};Lv.prototype.M=function(){Lv.S.M.call(this);delete this.bb}; -function Mv(b){var c=new yv;b.La("loadend",ta(function(b,c){var f=c.bb.result,g=c.getError();null==f||g?(Av(b),Bv(b,!1,g)):(Av(b),Bv(b,!0,f));c.hc()},c,b));return c};function Nv(b){b=m(b)?b:{};ok.call(this,{handleEvent:dd});this.f=m(b.formatConstructors)?b.formatConstructors:[];this.j=m(b.projection)?Ee(b.projection):null;this.e=null;this.d=void 0}u(Nv,ok);Nv.prototype.M=function(){m(this.d)&&Yc(this.d);Nv.S.M.call(this)};Nv.prototype.g=function(b){b=b.a.dataTransfer.files;var c,d,e;c=0;for(d=b.length;c<d;++c){var f=e=b[c],g=new Lv,h=Mv(g);g.bb.readAsText(f,"");Ev(h,ta(this.i,e),null,this)}}; -Nv.prototype.i=function(b,c){var d=this.n,e=this.j;null===e&&(e=d.a().q);var d=this.f,f=[],g,h;g=0;for(h=d.length;g<h;++g){var k=new d[g],n;try{n=k.ja(c)}catch(p){n=null}if(null!==n){var k=k.Ba(c),k=Ve(k,e),q,r;q=0;for(r=n.length;q<r;++q){var s=n[q],v=s.N();null!=v&&v.ma(k);f.push(s)}}}this.dispatchEvent(new Ov(Pv,this,b,f,e))}; -Nv.prototype.setMap=function(b){m(this.d)&&(Yc(this.d),this.d=void 0);null!==this.e&&(tc(this.e),this.e=null);Nv.S.setMap.call(this,b);null!==b&&(this.e=new av(b.b),this.d=z(this.e,"drop",this.g,!1,this))};var Pv="addfeatures";function Ov(b,c,d,e,f){uc.call(this,b,c);this.features=e;this.file=d;this.projection=f}u(Ov,uc);function Qv(b,c){this.x=b;this.y=c}u(Qv,vf);Qv.prototype.clone=function(){return new Qv(this.x,this.y)};Qv.prototype.scale=vf.prototype.scale;Qv.prototype.add=function(b){this.x+=b.x;this.y+=b.y;return this};Qv.prototype.rotate=function(b){var c=Math.cos(b);b=Math.sin(b);var d=this.y*c+this.x*b;this.x=this.x*c-this.y*b;this.y=d;return this};function Rv(b){b=m(b)?b:{};Bk.call(this,{handleDownEvent:Sv,handleDragEvent:Tv,handleUpEvent:Uv});this.i=m(b.condition)?b.condition:yk;this.d=this.f=void 0;this.g=0}u(Rv,Bk);function Tv(b){if(Ak(b)){var c=b.map,d=c.e();b=b.pixel;b=new Qv(b[0]-d[0]/2,d[1]/2-b[1]);d=Math.atan2(b.y,b.x);b=Math.sqrt(b.x*b.x+b.y*b.y);var e=c.a(),f=$e(e);c.render();m(this.f)&&pk(c,e,f.rotation-(d-this.f));this.f=d;m(this.d)&&rk(c,e,f.resolution/b*this.d);m(this.d)&&(this.g=this.d/b);this.d=b}} -function Uv(b){if(!Ak(b))return!0;b=b.map;var c=b.a();bf(c,-1);var d=$e(c),e=this.g-1,f=d.rotation,f=c.constrainRotation(f,0);pk(b,c,f,void 0,void 0);d=d.resolution;d=c.constrainResolution(d,0,e);rk(b,c,d,void 0,400);this.g=0;return!1}function Sv(b){return Ak(b)&&this.i(b)?(bf(b.map.a(),1),this.d=this.f=void 0,!0):!1};var Vv; -(function(){var b={Ze:{}};(function(){function c(b,d){if(!(this instanceof c))return new c(b,d);this.$d=Math.max(4,b||9);this.Qe=Math.max(2,Math.ceil(.4*this.$d));d&&this.Og(d);this.clear()}function d(b,c){b.bbox=e(b,0,b.children.length,c)}function e(b,c,d,e){for(var g=[Infinity,Infinity,-Infinity,-Infinity],h;c<d;c++)h=b.children[c],f(g,b.ua?e(h):h.bbox);return g}function f(b,c){b[0]=Math.min(b[0],c[0]);b[1]=Math.min(b[1],c[1]);b[2]=Math.max(b[2],c[2]);b[3]=Math.max(b[3],c[3])}function g(b,c){return b.bbox[0]- -c.bbox[0]}function h(b,c){return b.bbox[1]-c.bbox[1]}function k(b){return(b[2]-b[0])*(b[3]-b[1])}function n(b){return b[2]-b[0]+(b[3]-b[1])}function p(b,c){return b[0]<=c[0]&&b[1]<=c[1]&&c[2]<=b[2]&&c[3]<=b[3]}function q(b,c){return c[0]<=b[2]&&c[1]<=b[3]&&c[2]>=b[0]&&c[3]>=b[1]}function r(b,c,d,e,f){for(var g=[c,d],h;g.length;)d=g.pop(),c=g.pop(),d-c<=e||(h=c+Math.ceil((d-c)/e/2)*e,s(b,c,d,h,f),g.push(c,h,h,d))}function s(b,c,d,e,f){for(var g,h,k,n,p;d>c;){600<d-c&&(g=d-c+1,h=e-c+1,k=Math.log(g), -n=.5*Math.exp(2*k/3),p=.5*Math.sqrt(k*n*(g-n)/g)*(0>h-g/2?-1:1),k=Math.max(c,Math.floor(e-h*n/g+p)),h=Math.min(d,Math.floor(e+(g-h)*n/g+p)),s(b,k,h,e,f));g=b[e];h=c;n=d;v(b,c,e);for(0<f(b[d],g)&&v(b,c,d);h<n;){v(b,h,n);h++;for(n--;0>f(b[h],g);)h++;for(;0<f(b[n],g);)n--}0===f(b[c],g)?v(b,c,n):(n++,v(b,n,d));n<=e&&(c=n+1);e<=n&&(d=n-1)}}function v(b,c,d){var e=b[c];b[c]=b[d];b[d]=e}c.prototype={all:function(){return this.Me(this.data,[])},search:function(b){var c=this.data,d=[],e=this.Ca;if(!q(b,c.bbox))return d; -for(var f=[],g,h,k,n;c;){g=0;for(h=c.children.length;g<h;g++)k=c.children[g],n=c.ua?e(k):k.bbox,q(b,n)&&(c.ua?d.push(k):p(b,n)?this.Me(k,d):f.push(k));c=f.pop()}return d},load:function(b){if(!b||!b.length)return this;if(b.length<this.Qe){for(var c=0,d=b.length;c<d;c++)this.na(b[c]);return this}b=this.Oe(b.slice(),0,b.length-1,0);this.data.children.length?this.data.height===b.height?this.Re(this.data,b):(this.data.height<b.height&&(c=this.data,this.data=b,b=c),this.Pe(b,this.data.height-b.height-1, -!0)):this.data=b;return this},na:function(b){b&&this.Pe(b,this.data.height-1);return this},clear:function(){this.data={children:[],height:1,bbox:[Infinity,Infinity,-Infinity,-Infinity],ua:!0};return this},remove:function(b){if(!b)return this;for(var c=this.data,d=this.Ca(b),e=[],f=[],g,h,k,n;c||e.length;){c||(c=e.pop(),h=e[e.length-1],g=f.pop(),n=!0);if(c.ua&&(k=c.children.indexOf(b),-1!==k)){c.children.splice(k,1);e.push(c);this.Ng(e);break}n||c.ua||!p(c.bbox,d)?h?(g++,c=h.children[g],n=!1):c=null: -(e.push(c),f.push(g),g=0,h=c,c=c.children[0])}return this},Ca:function(b){return b},ce:function(b,c){return b[0]-c[0]},de:function(b,c){return b[1]-c[1]},toJSON:function(){return this.data},Me:function(b,c){for(var d=[];b;)b.ua?c.push.apply(c,b.children):d.push.apply(d,b.children),b=d.pop();return c},Oe:function(b,c,e,f){var g=e-c+1,h=this.$d,k;if(g<=h)return k={children:b.slice(c,e+1),height:1,bbox:null,ua:!0},d(k,this.Ca),k;f||(f=Math.ceil(Math.log(g)/Math.log(h)),h=Math.ceil(g/Math.pow(h,f-1))); -k={children:[],height:f,bbox:null};var g=Math.ceil(g/h),h=g*Math.ceil(Math.sqrt(h)),n,p,q;for(r(b,c,e,h,this.ce);c<=e;c+=h)for(p=Math.min(c+h-1,e),r(b,c,p,g,this.de),n=c;n<=p;n+=g)q=Math.min(n+g-1,p),k.children.push(this.Oe(b,n,q,f-1));d(k,this.Ca);return k},Mg:function(b,c,d,e){for(var f,g,h,n,p,q,r,s;;){e.push(c);if(c.ua||e.length-1===d)break;r=s=Infinity;f=0;for(g=c.children.length;f<g;f++){h=c.children[f];p=k(h.bbox);q=b;var v=h.bbox;q=(Math.max(v[2],q[2])-Math.min(v[0],q[0]))*(Math.max(v[3], -q[3])-Math.min(v[1],q[1]))-p;q<s?(s=q,r=p<r?p:r,n=h):q===s&&p<r&&(r=p,n=h)}c=n}return c},Pe:function(b,c,d){var e=this.Ca;d=d?b.bbox:e(b);var e=[],g=this.Mg(d,this.data,c,e);g.children.push(b);for(f(g.bbox,d);0<=c;)if(e[c].children.length>this.$d)this.Pg(e,c),c--;else break;this.Jg(d,e,c)},Pg:function(b,c){var e=b[c],f=e.children.length,g=this.Qe;this.Kg(e,g,f);f={children:e.children.splice(this.Lg(e,g,f)),height:e.height};e.ua&&(f.ua=!0);d(e,this.Ca);d(f,this.Ca);c?b[c-1].children.push(f):this.Re(e, -f)},Re:function(b,c){this.data={children:[b,c],height:b.height+1};d(this.data,this.Ca)},Lg:function(b,c,d){var f,g,h,n,p,q,r;p=q=Infinity;for(f=c;f<=d-c;f++){g=e(b,0,f,this.Ca);h=e(b,f,d,this.Ca);var s=g,v=h;n=Math.max(s[0],v[0]);var ac=Math.max(s[1],v[1]),Sb=Math.min(s[2],v[2]),s=Math.min(s[3],v[3]);n=Math.max(0,Sb-n)*Math.max(0,s-ac);g=k(g)+k(h);n<p?(p=n,r=f,q=g<q?g:q):n===p&&g<q&&(q=g,r=f)}return r},Kg:function(b,c,d){var e=b.ua?this.ce:g,f=b.ua?this.de:h,k=this.Ne(b,c,d,e);c=this.Ne(b,c,d,f); -k<c&&b.children.sort(e)},Ne:function(b,c,d,g){b.children.sort(g);g=this.Ca;var h=e(b,0,c,g),k=e(b,d-c,d,g),p=n(h)+n(k),q,r;for(q=c;q<d-c;q++)r=b.children[q],f(h,b.ua?g(r):r.bbox),p+=n(h);for(q=d-c-1;q>=c;q--)r=b.children[q],f(k,b.ua?g(r):r.bbox),p+=n(k);return p},Jg:function(b,c,d){for(;0<=d;d--)f(c[d].bbox,b)},Ng:function(b){for(var c=b.length-1,e;0<=c;c--)0===b[c].children.length?0<c?(e=b[c-1].children,e.splice(e.indexOf(b[c]),1)):this.clear():d(b[c],this.Ca)},Og:function(b){var c=["return a"," - b", -";"];this.ce=new Function("a","b",c.join(b[0]));this.de=new Function("a","b",c.join(b[1]));this.Ca=new Function("a","return [a"+b.join(", a")+"];")}};"function"===typeof define&&define.Ul?define(function(){return c}):"undefined"!==typeof b?b.Ze=c:"undefined"!==typeof self?self.a=c:window.a=c})();Vv=b.Ze})();function Wv(b){this.a=Vv(b);this.c={}}l=Wv.prototype;l.na=function(b,c){var d=[b[0],b[1],b[2],b[3],c];this.a.na(d);Ab(this.c,ma(c).toString(),d)};l.load=function(b,c){for(var d=Array(c.length),e=0,f=c.length;e<f;e++){var g=b[e],h=c[e],g=[g[0],g[1],g[2],g[3],h];d[e]=g;Ab(this.c,ma(h).toString(),g)}this.a.load(d)};l.remove=function(b){b=ma(b).toString();var c=x(this.c,b);zb(this.c,b);return null!==this.a.remove(c)};l.update=function(b,c){this.remove(c);this.na(b,c)}; -function Xv(b){b=b.a.all();return Va(b,function(b){return b[4]})}function Yv(b,c){var d=b.a.search(c);return Va(d,function(b){return b[4]})}l.forEach=function(b,c){return Zv(Xv(this),b,c)};function $v(b,c,d,e){return Zv(Yv(b,c),d,e)}function Zv(b,c,d){for(var e,f=0,g=b.length;f<g&&!(e=c.call(d,b[f]));f++);return e}l.ia=function(){return xb(this.c)};l.clear=function(){this.a.clear();this.c={}};l.D=function(){return this.a.data.bbox};function aw(b){b=m(b)?b:{};qj.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,state:m(b.state)?b.state:void 0});this.b=new Wv;this.d={};this.f={};this.o={};this.i={};m(b.features)&&this.fb(b.features)}u(aw,qj);l=aw.prototype;l.Pa=function(b){var c=ma(b).toString();bw(this,c,b);var d=b.N();null!=d?(d=d.D(),this.b.na(d,b)):this.d[c]=b;cw(this,c,b);this.dispatchEvent(new dw("addfeature",b));this.l()}; -function bw(b,c,d){b.i[c]=[z(d,"change",b.Af,!1,b),z(d,"propertychange",b.Af,!1,b)]}function cw(b,c,d){var e=d.X;m(e)?b.f[e.toString()]=d:b.o[c]=d}l.ya=function(b){this.fb(b);this.l()};l.fb=function(b){var c,d,e,f,g=[],h=[];d=0;for(e=b.length;d<e;d++){f=b[d];c=ma(f).toString();bw(this,c,f);var k=f.N();null!=k?(c=k.D(),g.push(c),h.push(f)):this.d[c]=f}this.b.load(g,h);d=0;for(e=b.length;d<e;d++)f=b[d],c=ma(f).toString(),cw(this,c,f),this.dispatchEvent(new dw("addfeature",f))}; -l.clear=function(b){if(b){for(var c in this.i)Ta(this.i[c],Yc);this.i={};this.f={};this.o={}}else b=this.Xf,this.b.forEach(b,this),ob(this.d,b,this);this.b.clear();this.d={};this.dispatchEvent(new dw("clear"));this.l()};l.Xa=function(b,c){return this.b.forEach(b,c)};function ew(b,c,d){b.ra([c[0],c[1],c[0],c[1]],function(b){if(b.N().Jb(c[0],c[1]))return d.call(void 0,b)})}l.ra=function(b,c,d){return $v(this.b,b,c,d)};l.Db=function(b,c,d,e){return this.ra(b,d,e)}; -l.Fa=function(b,c,d){return this.ra(b,function(e){if(e.N().ha(b)&&(e=c.call(d,e)))return e})};l.va=function(){var b=Xv(this.b);xb(this.d)||db(b,rb(this.d));return b};l.Ia=function(b){var c=[];ew(this,b,function(b){c.push(b)});return c};l.Ya=function(b){var c=b[0],d=b[1],e=null,f=[NaN,NaN],g=Infinity,h=[-Infinity,-Infinity,Infinity,Infinity];$v(this.b,h,function(b){var n=b.N(),p=g;g=n.Va(c,d,f,g);g<p&&(e=b,b=Math.sqrt(g),h[0]=c-b,h[1]=d-b,h[2]=c+b,h[3]=d+b)});return e};l.D=function(){return this.b.D()}; -l.Ha=function(b){b=this.f[b.toString()];return m(b)?b:null};l.Af=function(b){b=b.target;var c=ma(b).toString(),d=b.N();null!=d?(d=d.D(),c in this.d?(delete this.d[c],this.b.na(d,b)):this.b.update(d,b)):c in this.d||(this.b.remove(b),this.d[c]=b);d=b.X;m(d)?(d=d.toString(),c in this.o?(delete this.o[c],this.f[d]=b):this.f[d]!==b&&(fw(this,b),this.f[d]=b)):c in this.o||(fw(this,b),this.o[c]=b);this.l();this.dispatchEvent(new dw("changefeature",b))};l.ia=function(){return this.b.ia()&&xb(this.d)}; -l.Hb=ca;l.$a=function(b){var c=ma(b).toString();c in this.d?delete this.d[c]:this.b.remove(b);this.Xf(b);this.l()};l.Xf=function(b){var c=ma(b).toString();Ta(this.i[c],Yc);delete this.i[c];var d=b.X;m(d)?delete this.f[d.toString()]:delete this.o[c];this.dispatchEvent(new dw("removefeature",b))};function fw(b,c){for(var d in b.f)if(b.f[d]===c){delete b.f[d];break}}function dw(b,c){uc.call(this,b);this.feature=c}u(dw,uc);function gw(b,c){uc.call(this,b);this.feature=c}u(gw,uc); -function hw(b){Bk.call(this,{handleDownEvent:iw,handleEvent:jw,handleUpEvent:kw});this.Q=null;this.ga=m(b.source)?b.source:null;this.ba=m(b.features)?b.features:null;this.Ab=m(b.snapTolerance)?b.snapTolerance:12;this.Ta=m(b.minPointsPerRing)?b.minPointsPerRing:3;var c=this.F=b.type,d;if("Point"===c||"MultiPoint"===c)d=lw;else if("LineString"===c||"MultiLineString"===c)d=mw;else if("Polygon"===c||"MultiPolygon"===c)d=nw;this.d=d;this.f=this.r=this.j=this.g=this.i=null;this.J=new sp({style:m(b.style)? -b.style:ow()});this.ca=b.geometryName;this.eb=m(b.condition)?b.condition:xk;z(this,xd("active"),this.ea,!1,this)}u(hw,Bk);function ow(){var b=Xl();return function(c){return b[c.N().H()]}}hw.prototype.setMap=function(b){hw.S.setMap.call(this,b);this.ea()}; -function jw(b){var c;c=b.map;if(Pf(document,c.b)&&"none"!=c.b.style.display){var d=c.e();null==d||0>=d[0]||0>=d[1]?c=!1:(c=c.a(),c=null!==c&&af(c)?!0:!1)}else c=!1;if(!c)return!0;c=!0;b.type===nj?c=pw(this,b):b.type===hj&&(c=!1);return Ck.call(this,b)&&c}function iw(b){return this.eb(b)?(this.Q=b.pixel,!0):!1} -function kw(b){var c=this.Q,d=b.pixel,e=c[0]-d[0],c=c[1]-d[1],d=!0;4>=e*e+c*c&&(pw(this,b),null===this.i?qw(this,b):this.d===lw||rw(this,b)?this.V():(b=b.coordinate,e=this.g.N(),this.d===mw?(this.i=b.slice(),c=e.K(),c.push(b.slice()),e.U(c)):this.d===nw&&(this.f[0].push(b.slice()),e.U(this.f)),sw(this)),d=!1);return d} -function pw(b,c){if(b.d===lw&&null===b.i)qw(b,c);else if(null===b.i){var d=c.coordinate.slice();null===b.j?(b.j=new R(new jl(d)),sw(b)):b.j.N().U(d)}else{var d=c.coordinate,e=b.g.N(),f,g;b.d===lw?(g=e.K(),g[0]=d[0],g[1]=d[1],e.U(g)):(b.d===mw?f=e.K():b.d===nw&&(f=b.f[0]),rw(b,c)&&(d=b.i.slice()),b.j.N().U(d),g=f[f.length-1],g[0]=d[0],g[1]=d[1],b.d===mw?e.U(f):b.d===nw&&(b.r.N().U(f),e.U(b.f)));sw(b)}return!0} -function rw(b,c){var d=!1;if(null!==b.g){var e=b.g.N(),f=!1,g=[b.i];b.d===mw?f=2<e.K().length:b.d===nw&&(f=e.K()[0].length>b.Ta,g=[b.f[0][0],b.f[0][b.f[0].length-2]]);if(f)for(var e=c.map,f=0,h=g.length;f<h;f++){var k=g[f],n=e.f(k),p=c.pixel,d=p[0]-n[0],n=p[1]-n[1];if(d=Math.sqrt(d*d+n*n)<=b.Ab){b.i=k;break}}}return d} -function qw(b,c){var d=c.coordinate;b.i=d;var e;b.d===lw?e=new jl(d.slice()):b.d===mw?e=new M([d.slice(),d.slice()]):b.d===nw&&(b.r=new R(new M([d.slice(),d.slice()])),b.f=[[d.slice(),d.slice()]],e=new H(b.f));b.g=new R;m(b.ca)&&b.g.e(b.ca);b.g.Ma(e);sw(b);b.dispatchEvent(new gw("drawstart",b.g))} -hw.prototype.V=function(){var b=tw(this),c,d=b.N();this.d===lw?c=d.K():this.d===mw?(c=d.K(),c.pop(),d.U(c)):this.d===nw&&(this.f[0].pop(),this.f[0].push(this.f[0][0]),d.U(this.f),c=d.K());"MultiPoint"===this.F?b.Ma(new un([c])):"MultiLineString"===this.F?b.Ma(new rn([c])):"MultiPolygon"===this.F&&b.Ma(new vn([c]));null===this.ba||this.ba.push(b);null===this.ga||this.ga.Pa(b);this.dispatchEvent(new gw("drawend",b))}; -function tw(b){b.i=null;var c=b.g;null!==c&&(b.g=null,b.j=null,b.r=null,b.J.a.clear());return c}hw.prototype.q=cd;function sw(b){var c=[];null===b.g||c.push(b.g);null===b.r||c.push(b.r);null===b.j||c.push(b.j);b.J.Mc(new B(c))}hw.prototype.ea=function(){var b=this.n,c=this.a();null!==b&&c||tw(this);this.J.setMap(c?b:null)};var lw="Point",mw="LineString",nw="Polygon";function uw(b){Bk.call(this,{handleDownEvent:vw,handleDragEvent:ww,handleEvent:xw,handleUpEvent:yw});this.ba=m(b.deleteCondition)?b.deleteCondition:id(xk,wk);this.V=this.f=null;this.Q=[0,0];this.d=new Wv;this.i=m(b.pixelTolerance)?b.pixelTolerance:10;this.J=!1;this.g=null;this.j=new sp({style:m(b.style)?b.style:zw()});this.F={Point:this.Fl,LineString:this.jg,LinearRing:this.jg,Polygon:this.Hl,MultiPoint:this.Cl,MultiLineString:this.Bl,MultiPolygon:this.El,GeometryCollection:this.Al};this.r=b.features; -this.r.forEach(this.wf,this);z(this.r,"add",this.Wh,!1,this);z(this.r,"remove",this.Xh,!1,this)}u(uw,Bk);l=uw.prototype;l.wf=function(b){var c=b.N();m(this.F[c.H()])&&this.F[c.H()].call(this,b,c);b=this.n;null===b||Aw(this,this.Q,b)};l.setMap=function(b){this.j.setMap(b);uw.S.setMap.call(this,b)};l.Wh=function(b){this.wf(b.element)}; -l.Xh=function(b){var c=b.element;b=this.d;var d,e=[];$v(b,c.N().D(),function(b){c===b.feature&&e.push(b)});for(d=e.length-1;0<=d;--d)b.remove(e[d]);null!==this.f&&0===this.r.Ib()&&(this.j.wd(this.f),this.f=null)};l.Fl=function(b,c){var d=c.K(),d={feature:b,geometry:c,fa:[d,d]};this.d.na(c.D(),d)};l.Cl=function(b,c){var d=c.K(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:b,geometry:c,depth:[f],index:f,fa:[e,e]},this.d.na(c.D(),e)}; -l.jg=function(b,c){var d=c.K(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:b,geometry:c,index:e,fa:g},this.d.na(Td(g),h)};l.Bl=function(b,c){var d=c.K(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,geometry:c,depth:[h],index:f,fa:n},this.d.na(Td(n),p)}; -l.Hl=function(b,c){var d=c.K(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,geometry:c,depth:[h],index:f,fa:n},this.d.na(Td(n),p)};l.El=function(b,c){var d=c.K(),e,f,g,h,k,n,p,q,r,s;n=0;for(p=d.length;n<p;++n)for(q=d[n],h=0,k=q.length;h<k;++h)for(e=q[h],f=0,g=e.length-1;f<g;++f)r=e.slice(f,f+2),s={feature:b,geometry:c,depth:[h,n],index:f,fa:r},this.d.na(Td(r),s)}; -l.Al=function(b,c){var d,e=c.d;for(d=0;d<e.length;++d)this.F[e[d].H()].call(this,b,e[d])};function Bw(b,c){var d=b.f;null===d?(d=new R(new jl(c)),b.f=d,b.j.rf(d)):d.N().U(c)} -function vw(b){Aw(this,b.pixel,b.map);this.g=[];var c=this.f;if(null!==c){b=[];for(var c=c.N().K(),d=Td([c]),d=Yv(this.d,d),e=0,f=d.length;e<f;++e){var g=d[e],h=g.fa;Cd(h[0],c)?this.g.push([g,0]):Cd(h[1],c)?this.g.push([g,1]):ma(h)in this.V&&b.push([g,c])}for(e=b.length-1;0<=e;--e)this.ui.apply(this,b[e])}return null!==this.f} -function ww(b){b=b.coordinate;for(var c=0,d=this.g.length;c<d;++c){for(var e=this.g[c],f=e[0],g=f.depth,h=f.geometry,k=h.K(),n=f.fa,e=e[1];b.length<h.s;)b.push(0);switch(h.H()){case "Point":k=b;n[0]=n[1]=b;break;case "MultiPoint":k[f.index]=b;n[0]=n[1]=b;break;case "LineString":k[f.index+e]=b;n[e]=b;break;case "MultiLineString":k[g[0]][f.index+e]=b;n[e]=b;break;case "Polygon":k[g[0]][f.index+e]=b;n[e]=b;break;case "MultiPolygon":k[g[1]][g[0]][f.index+e]=b,n[e]=b}h.U(k);Bw(this,b)}} -function yw(){for(var b,c=this.g.length-1;0<=c;--c)b=this.g[c][0],this.d.update(Td(b.fa),b);return!1} -function xw(b){var c,d=b.map.a();cb(d.j)[1]||b.type!=nj||(this.Q=b.pixel,Aw(this,b.pixel,b.map));if(null!==this.f&&this.J&&this.ba(b)){this.f.N();c=this.g;var d={},e=!1,f,g,h,k,n,p,q,r,s;for(n=c.length-1;0<=n;--n)if(h=c[n],r=h[0],k=r.geometry,g=k.K(),s=ma(r.feature),f=q=p=void 0,0===h[1]?(q=r,p=r.index):1==h[1]&&(f=r,p=r.index+1),s in d||(d[s]=[f,q,p]),h=d[s],m(f)&&(h[0]=f),m(q)&&(h[1]=q),m(h[0])&&m(h[1])){f=g;e=!1;q=p-1;switch(k.H()){case "MultiLineString":g[r.depth[0]].splice(p,1);e=!0;break;case "LineString":g.splice(p, -1);e=!0;break;case "MultiPolygon":f=f[r.depth[1]];case "Polygon":f=f[r.depth[0]],4<f.length&&(p==f.length-1&&(p=0),f.splice(p,1),e=!0,0===p&&(f.pop(),f.push(f[0]),q=f.length-1))}e&&(this.d.remove(h[0]),this.d.remove(h[1]),k.U(g),g={depth:r.depth,feature:r.feature,geometry:r.geometry,index:q,fa:[h[0].fa[0],h[1].fa[1]]},this.d.na(Td(g.fa),g),Cw(this,k,p,r.depth,-1),this.j.wd(this.f),this.f=null)}c=e}return Ck.call(this,b)&&!c} -function Aw(b,c,d){function e(b,c){return Ed(f,zd(f,b.fa))-Ed(f,zd(f,c.fa))}var f=d.Ga(c),g=d.Ga([c[0]-b.i,c[1]+b.i]),h=d.Ga([c[0]+b.i,c[1]-b.i]),g=Td([g,h]),g=Yv(b.d,g);if(0<g.length){g.sort(e);var h=g[0].fa,k=zd(f,h),n=d.f(k);if(Math.sqrt(Ed(c,n))<=b.i){c=d.f(h[0]);d=d.f(h[1]);c=Ed(n,c);d=Ed(n,d);b.J=Math.sqrt(Math.min(c,d))<=b.i;b.J&&(k=c>d?h[1]:h[0]);Bw(b,k);d={};d[ma(h)]=!0;c=1;for(n=g.length;c<n;++c)if(k=g[c].fa,Cd(h[0],k[0])&&Cd(h[1],k[1])||Cd(h[0],k[1])&&Cd(h[1],k[0]))d[ma(k)]=!0;else break; -b.V=d;return}}null!==b.f&&(b.j.wd(b.f),b.f=null)} -l.ui=function(b,c){for(var d=b.fa,e=b.feature,f=b.geometry,g=b.depth,h=b.index,k;c.length<f.s;)c.push(0);switch(f.H()){case "MultiLineString":k=f.K();k[g[0]].splice(h+1,0,c);break;case "Polygon":k=f.K();k[g[0]].splice(h+1,0,c);break;case "MultiPolygon":k=f.K();k[g[1]][g[0]].splice(h+1,0,c);break;case "LineString":k=f.K();k.splice(h+1,0,c);break;default:return}f.U(k);k=this.d;k.remove(b);Cw(this,f,h,g,1);var n={fa:[d[0],c],feature:e,geometry:f,depth:g,index:h};k.na(Td(n.fa),n);this.g.push([n,1]);d= -{fa:[c,d[1]],feature:e,geometry:f,depth:g,index:h+1};k.na(Td(d.fa),d);this.g.push([d,0])};function Cw(b,c,d,e,f){$v(b.d,c.D(),function(b){b.geometry===c&&(!m(e)||ib(b.depth,e))&&b.index>d&&(b.index+=f)})}function zw(){var b=Xl();return function(){return b.Point}};function Dw(b){ok.call(this,{handleEvent:Ew});b=m(b)?b:{};this.g=m(b.condition)?b.condition:wk;this.f=m(b.addCondition)?b.addCondition:cd;this.p=m(b.removeCondition)?b.removeCondition:cd;this.r=m(b.toggleCondition)?b.toggleCondition:yk;var c;if(m(b.layers))if(ka(b.layers))c=b.layers;else{var d=b.layers;c=function(b){return Za(d,b)}}else c=dd;this.e=c;this.d=new sp({style:m(b.style)?b.style:Fw()});b=this.d.a;z(b,"add",this.i,!1,this);z(b,"remove",this.q,!1,this)}u(Dw,ok);Dw.prototype.j=function(){return this.d.a}; -function Ew(b){if(!this.g(b))return!0;var c=this.f(b),d=this.p(b),e=this.r(b),f=b.map,g=this.d.a;if(c||d||e){var h=[],k=[];f.oe(b.pixel,function(b){-1==Sa.indexOf.call(g.a,b,void 0)?(c||e)&&k.push(b):(d||e)&&h.push(b)},void 0,this.e);for(f=h.length-1;0<=f;--f)g.remove(h[f]);g.qf(k)}else f=f.oe(b.pixel,function(b){return b},void 0,this.e),m(f)&&1==g.Ib()&&g.item(0)==f||(0!==g.Ib()&&g.clear(),m(f)&&g.push(f));return vk(b)} -Dw.prototype.setMap=function(b){var c=this.n,d=this.d.a;null===c||d.forEach(c.fc,c);Dw.S.setMap.call(this,b);this.d.setMap(b);null===b||d.forEach(b.eb,b)};function Fw(){var b=Xl();db(b.Polygon,b.LineString);db(b.GeometryCollection,b.LineString);return function(c){return b[c.N().H()]}}Dw.prototype.i=function(b){b=b.element;var c=this.n;null===c||c.eb(b)};Dw.prototype.q=function(b){b=b.element;var c=this.n;null===c||c.fc(b)};function $(b){b=m(b)?b:{};L.call(this,b);this.ea=null;z(this,xd("gradient"),this.Wd,!1,this);this.fc(m(b.gradient)?b.gradient:Gw);var c=Hw(m(b.radius)?b.radius:8,m(b.blur)?b.blur:15,m(b.shadow)?b.shadow:250),d=Array(256),e=m(b.weight)?b.weight:"weight",f;ia(e)?f=function(b){return b.get(e)}:f=e;this.oa(function(b){b=f(b);b=m(b)?Yb(b,0,1):1;var e=255*b|0,k=d[e];m(k)||(k=[new Tl({image:new Sj({opacity:b,src:c})})],d[e]=k);return k});this.set("renderOrder",null);z(this,"render",this.Xd,!1,this)} -u($,L);var Gw=["#00f","#0ff","#0f0","#ff0","#f00"];function Hw(b,c,d){var e=b+c+1,f=2*e,f=Tf(f,f);f.shadowOffsetX=f.shadowOffsetY=d;f.shadowBlur=c;f.shadowColor="#000";f.beginPath();c=e-d;f.arc(c,c,b,0,2*Math.PI,!0);f.fill();return f.canvas.toDataURL()}$.prototype.qa=function(){return this.get("gradient")};$.prototype.getGradient=$.prototype.qa; -$.prototype.Wd=function(){for(var b=this.qa(),c=Tf(1,256),d=c.createLinearGradient(0,0,1,256),e=1/(b.length-1),f=0,g=b.length;f<g;++f)d.addColorStop(f*e,b[f]);c.fillStyle=d;c.fillRect(0,0,1,256);this.ea=c.getImageData(0,0,1,256).data};$.prototype.Xd=function(b){b=b.context;var c=b.canvas,c=b.getImageData(0,0,c.width,c.height),d=c.data,e,f,g;e=0;for(f=d.length;e<f;e+=4)if(g=4*d[e+3])d[e]=this.ea[g],d[e+1]=this.ea[g+1],d[e+2]=this.ea[g+2];b.putImageData(c,0,0)}; -$.prototype.fc=function(b){this.set("gradient",b)};$.prototype.setGradient=$.prototype.fc;function Iw(b){return[b]};function Jw(b,c){var d=c||{},e=d.document||document,f=If("SCRIPT"),g={$f:f,bc:void 0},h=new yv(Kw,g),k=null,n=null!=d.timeout?d.timeout:5E3;0<n&&(k=window.setTimeout(function(){Lw(f,!0);var c=new Mw(Nw,"Timeout reached for loading script "+b);Av(h);Bv(h,!1,c)},n),g.bc=k);f.onload=f.onreadystatechange=function(){f.readyState&&"loaded"!=f.readyState&&"complete"!=f.readyState||(Lw(f,d.Zg||!1,k),Av(h),Bv(h,!0,null))};f.onerror=function(){Lw(f,!0,k);var c=new Mw(Ow,"Error while loading script "+b);Av(h); -Bv(h,!1,c)};Cf(f,{type:"text/javascript",charset:"UTF-8",src:b});Pw(e).appendChild(f);return h}function Pw(b){var c=b.getElementsByTagName("HEAD");return c&&0!=c.length?c[0]:b.documentElement}function Kw(){if(this&&this.$f){var b=this.$f;b&&"SCRIPT"==b.tagName&&Lw(b,!0,this.bc)}}function Lw(b,c,d){null!=d&&ba.clearTimeout(d);b.onload=ca;b.onerror=ca;b.onreadystatechange=ca;c&&window.setTimeout(function(){Nf(b)},0)}var Ow=0,Nw=1; -function Mw(b,c){var d="Jsloader error (code #"+b+")";c&&(d+=": "+c);xa.call(this,d);this.code=b}u(Mw,xa);function Qw(b,c){this.c=new Ah(b);this.a=c?c:"callback";this.bc=5E3}var Rw=0; -Qw.prototype.send=function(b,c,d,e){b=b||null;e=e||"_"+(Rw++).toString(36)+ua().toString(36);ba._callbacks_||(ba._callbacks_={});var f=this.c.clone();if(b)for(var g in b)if(!b.hasOwnProperty||b.hasOwnProperty(g)){var h=f,k=g,n=b[g];ga(n)||(n=[String(n)]);Uh(h.a,k,n)}c&&(ba._callbacks_[e]=Sw(e,c),c=this.a,g="_callbacks_."+e,ga(g)||(g=[String(g)]),Uh(f.a,c,g));c=Jw(f.toString(),{timeout:this.bc,Zg:!0});Ev(c,null,Tw(e,b,d),void 0);return{X:e,Xe:c}}; -Qw.prototype.cancel=function(b){b&&(b.Xe&&b.Xe.cancel(),b.X&&Uw(b.X,!1))};function Tw(b,c,d){return function(){Uw(b,!1);d&&d(c)}}function Sw(b,c){return function(d){Uw(b,!0);c.apply(void 0,arguments)}}function Uw(b,c){ba._callbacks_[b]&&(c?delete ba._callbacks_[b]:ba._callbacks_[b]=ca)};function Vw(b){var c=/\{z\}/g,d=/\{x\}/g,e=/\{y\}/g,f=/\{-y\}/g;return function(g){return null===g?void 0:b.replace(c,g[0].toString()).replace(d,g[1].toString()).replace(e,g[2].toString()).replace(f,function(){return((1<<g[0])-g[2]-1).toString()})}}function Ww(b){return Xw(Va(b,Vw))}function Xw(b){return 1===b.length?b[0]:function(c,d,e){return null===c?void 0:b[Zb((c[1]<<c[0])+c[2],b.length)](c,d,e)}}function Yw(){} -function Zw(b,c){var d=[0,0,0];return function(e,f,g){return null===e?void 0:c(b(e,g,d),f,g)}}function $w(b){var c=[],d=/\{(\d)-(\d)\}/.exec(b)||/\{([a-z])-([a-z])\}/.exec(b);if(d){var e=d[2].charCodeAt(0),f;for(f=d[1].charCodeAt(0);f<=e;++f)c.push(b.replace(d[0],String.fromCharCode(f)))}else c.push(b);return c};function ax(b){Io.call(this);this.e=m(b)?b:2048}u(ax,Io);function bx(b){return b.Ub()>b.e}function cx(b,c){for(var d,e;bx(b)&&!(d=b.a.dc,e=d.a[0].toString(),e in c&&c[e].contains(d.a));)b.pop().hc()};function dx(b){Fj.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,opaque:b.opaque,projection:b.projection,state:m(b.state)?b.state:void 0,tileGrid:b.tileGrid,tilePixelRatio:b.tilePixelRatio});this.tileUrlFunction=m(b.tileUrlFunction)?b.tileUrlFunction:Yw;this.crossOrigin=m(b.crossOrigin)?b.crossOrigin:null;this.b=new ax;this.tileLoadFunction=m(b.tileLoadFunction)?b.tileLoadFunction:ex;this.tileClass=m(b.tileClass)?b.tileClass:Yu}u(dx,Fj);function ex(b,c){b.Na().src=c}l=dx.prototype; -l.zd=function(){return bx(this.b)};l.se=function(b){cx(this.b,b)};l.Fb=function(b,c,d,e,f){var g=this.hb(b,c,d);if(Fo(this.b,g))return this.b.get(g);b=[b,c,d];e=this.tileUrlFunction(b,e,f);e=new this.tileClass(b,m(e)?0:4,m(e)?e:"",this.crossOrigin,this.tileLoadFunction);this.b.set(g,e);return e};l.ib=function(){return this.tileLoadFunction};l.jb=function(){return this.tileUrlFunction};l.ob=function(b){this.b.clear();this.tileLoadFunction=b;this.l()}; -l.pa=function(b){this.b.clear();this.tileUrlFunction=b;this.l()};l.He=function(b,c,d){b=this.hb(b,c,d);Fo(this.b,b)&&this.b.get(b)};function fx(b){var c=m(b.extent)?b.extent:om,d=Dj(c,b.maxZoom,b.tileSize);wj.call(this,{minZoom:b.minZoom,origin:le(c,"top-left"),resolutions:d,tileSize:b.tileSize})}u(fx,wj); -fx.prototype.Bb=function(b){b=m(b)?b:{};var c=this.minZoom,d=this.maxZoom,e=m(b.wrapX)?b.wrapX:!0,f=null;if(m(b.extent)){var f=Array(d+1),g;for(g=0;g<=d;++g)f[g]=g<c?null:zj(this,b.extent,g)}return function(b,g,n){g=b[0];if(g<c||d<g)return null;var p=Math.pow(2,g),q=b[1];if(e)q=Zb(q,p);else if(0>q||p<=q)return null;b=b[2];return b<-p||-1<b||null!==f&&!qf(f[g],q,b)?null:kf(g,q,-b-1,n)}};fx.prototype.kd=function(b,c){if(b[0]<this.maxZoom){var d=2*b[1],e=2*b[2];return pf(d,d+1,e,e+1,c)}return null}; -fx.prototype.$c=function(b,c,d,e){e=pf(0,b[1],0,b[2],e);for(b=b[0]-1;b>=this.minZoom;--b)if(e.a=e.d>>=1,e.b=e.c>>=1,c.call(d,b,e))return!0;return!1};function gx(b){dx.call(this,{crossOrigin:"anonymous",opaque:!0,projection:Ee("EPSG:3857"),state:"loading",tileLoadFunction:b.tileLoadFunction});this.d=m(b.culture)?b.culture:"en-us";this.a=m(b.maxZoom)?b.maxZoom:-1;var c=new Ah((Wb?"https:":"http:")+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+b.imagerySet);(new Qw(c,"jsonp")).send({include:"ImageryProviders",uriScheme:Wb?"https":"http",key:b.key},sa(this.f,this))}u(gx,dx);var hx=new tf({html:'<a class="ol-attribution-bing-tos" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'}); -gx.prototype.f=function(b){if(200!=b.statusCode||"OK"!=b.statusDescription||"ValidCredentials"!=b.authenticationResultCode||1!=b.resourceSets.length||1!=b.resourceSets[0].resources.length)rj(this,"error");else{var c=b.brandLogoUri,d=b.resourceSets[0].resources[0],e=-1==this.a?d.zoomMax:this.a,f=new fx({extent:Ej(this.g),minZoom:d.zoomMin,maxZoom:e,tileSize:d.imageWidth});this.tileGrid=f;var g=this.d;this.tileUrlFunction=Zw(f.Bb(),Xw(Va(d.imageUrlSubdomains,function(b){var c=d.imageUrl.replace("{subdomain}", -b).replace("{culture}",g);return function(b){return null===b?void 0:c.replace("{quadkey}",mf(b))}})));if(d.imageryProviders){var h=De(Ee("EPSG:4326"),this.g);b=Va(d.imageryProviders,function(b){var c=b.attribution,d={};Ta(b.coverageAreas,function(b){var c=b.zoomMin,g=Math.min(b.zoomMax,e);b=b.bbox;b=we([b[1],b[0],b[3],b[2]],h);var k,n;for(k=c;k<=g;++k)n=k.toString(),c=zj(f,b,k),n in d?d[n].push(c):d[n]=[c]});return new tf({html:c,tileRanges:d})});b.push(hx);this.e=b}this.r=c;rj(this,"ready")}};function ix(b){aw.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection});this.j=void 0;this.q=m(b.distance)?b.distance:20;this.a=[];this.p=b.source;this.p.u("change",ix.prototype.t,this)}u(ix,aw);ix.prototype.Hb=function(b,c,d){c!==this.j&&(this.clear(),this.j=c,this.p.Hb(b,c,d),jx(this),this.ya(this.a))};ix.prototype.t=function(){this.clear();jx(this);this.ya(this.a);this.l()}; -function jx(b){if(m(b.j)){$a(b.a);for(var c=Vd(),d=b.q*b.j,e=b.p.va(),f={},g=0,h=e.length;g<h;g++){var k=e[g];tb(f,ma(k).toString())||(k=k.N().K(),ce(k,c),Yd(c,d,c),k=Yv(b.p.b,c),k=Ua(k,function(b){b=ma(b).toString();return b in f?!1:f[b]=!0}),b.a.push(kx(k)))}}}function kx(b){for(var c=b.length,d=[0,0],e=0;e<c;e++){var f=b[e].N().K();yd(d,f)}c=1/c;d[0]*=c;d[1]*=c;d=new R(new jl(d));d.set("features",b);return d};function lx(b,c,d){if(ka(b))d&&(b=sa(b,d));else if(b&&"function"==typeof b.handleEvent)b=sa(b.handleEvent,b);else throw Error("Invalid listener argument");return 2147483647<c?-1:ba.setTimeout(b,c||0)};function mx(){}mx.prototype.a=null;function nx(b){var c;(c=b.a)||(c={},ox(b)&&(c[0]=!0,c[1]=!0),c=b.a=c);return c};var px;function qx(){}u(qx,mx);function rx(b){return(b=ox(b))?new ActiveXObject(b):new XMLHttpRequest}function ox(b){if(!b.c&&"undefined"==typeof XMLHttpRequest&&"undefined"!=typeof ActiveXObject){for(var c=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],d=0;d<c.length;d++){var e=c[d];try{return new ActiveXObject(e),b.c=e}catch(f){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return b.c}px=new qx;function sx(b){jd.call(this);this.r=new rh;this.i=b||null;this.a=!1;this.o=this.T=null;this.e=this.p="";this.c=this.j=this.d=this.n=!1;this.g=0;this.b=null;this.f=tx;this.q=this.t=!1}u(sx,jd);var tx="",ux=/^https?$/i,vx=["POST","PUT"];l=sx.prototype; -l.send=function(b,c,d,e){if(this.T)throw Error("[goog.net.XhrIo] Object is active with another request="+this.p+"; newUri="+b);c=c?c.toUpperCase():"GET";this.p=b;this.e="";this.n=!1;this.a=!0;this.T=this.i?rx(this.i):rx(px);this.o=this.i?nx(this.i):nx(px);this.T.onreadystatechange=sa(this.Hf,this);try{this.j=!0,this.T.open(c,String(b),!0),this.j=!1}catch(f){wx(this,f);return}b=d||"";var g=this.r.clone();e&&qh(e,function(b,c){g.set(c,b)});e=Xa(g.I());d=ba.FormData&&b instanceof ba.FormData;!Za(vx, -c)||e||d||g.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");g.forEach(function(b,c){this.T.setRequestHeader(c,b)},this);this.f&&(this.T.responseType=this.f);"withCredentials"in this.T&&(this.T.withCredentials=this.t);try{xx(this),0<this.g&&((this.q=yx(this.T))?(this.T.timeout=this.g,this.T.ontimeout=sa(this.bc,this)):this.b=lx(this.bc,this.g,this)),this.d=!0,this.T.send(b),this.d=!1}catch(h){wx(this,h)}};function yx(b){return Hb&&Tb(9)&&ja(b.timeout)&&m(b.ontimeout)} -function Ya(b){return"content-type"==b.toLowerCase()}l.bc=function(){"undefined"!=typeof aa&&this.T&&(this.e="Timed out after "+this.g+"ms, aborting",this.dispatchEvent("timeout"),this.T&&this.a&&(this.a=!1,this.c=!0,this.T.abort(),this.c=!1,this.dispatchEvent("complete"),this.dispatchEvent("abort"),zx(this)))};function wx(b,c){b.a=!1;b.T&&(b.c=!0,b.T.abort(),b.c=!1);b.e=c;Ax(b);zx(b)}function Ax(b){b.n||(b.n=!0,b.dispatchEvent("complete"),b.dispatchEvent("error"))} -l.M=function(){this.T&&(this.a&&(this.a=!1,this.c=!0,this.T.abort(),this.c=!1),zx(this,!0));sx.S.M.call(this)};l.Hf=function(){this.Sa||(this.j||this.d||this.c?Bx(this):this.rk())};l.rk=function(){Bx(this)}; -function Bx(b){if(b.a&&"undefined"!=typeof aa&&(!b.o[1]||4!=Cx(b)||2!=Dx(b)))if(b.d&&4==Cx(b))lx(b.Hf,0,b);else if(b.dispatchEvent("readystatechange"),4==Cx(b)){b.a=!1;try{if(Ex(b))b.dispatchEvent("complete"),b.dispatchEvent("success");else{var c;try{c=2<Cx(b)?b.T.statusText:""}catch(d){c=""}b.e=c+" ["+Dx(b)+"]";Ax(b)}}finally{zx(b)}}}function zx(b,c){if(b.T){xx(b);var d=b.T,e=b.o[0]?ca:null;b.T=null;b.o=null;c||b.dispatchEvent("ready");try{d.onreadystatechange=e}catch(f){}}} -function xx(b){b.T&&b.q&&(b.T.ontimeout=null);ja(b.b)&&(ba.clearTimeout(b.b),b.b=null)}function Ex(b){var c=Dx(b),d;a:switch(c){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:d=!0;break a;default:d=!1}if(!d){if(c=0===c)b=vh(String(b.p))[1]||null,!b&&self.location&&(b=self.location.protocol,b=b.substr(0,b.length-1)),c=!ux.test(b?b.toLowerCase():"");d=c}return d}function Cx(b){return b.T?b.T.readyState:0}function Dx(b){try{return 2<Cx(b)?b.T.status:-1}catch(c){return-1}} -function Fx(b){try{return b.T?b.T.responseText:""}catch(c){return""}}function Gx(b){try{if(!b.T)return null;if("response"in b.T)return b.T.response;switch(b.f){case tx:case "text":return b.T.responseText;case "arraybuffer":if("mozResponseArrayBuffer"in b.T)return b.T.mozResponseArrayBuffer}return null}catch(c){return null}};function Hx(b){aw.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection});this.format=b.format}u(Hx,aw); -function Ix(b,c,d,e,f){var g=new sx;g.f="binary"==b.format.H()&&bg?"arraybuffer":"text";z(g,"complete",function(b){b=b.target;if(Ex(b)){var c=this.format.H(),g;if("binary"==c&&bg)g=Gx(b);else if("json"==c)g=Fx(b);else if("text"==c)g=Fx(b);else if("xml"==c){if(!Hb)try{g=b.T?b.T.responseXML:null}catch(p){g=null}null!=g||(g=iq(Fx(b)))}null!=g?d.call(f,this.a(g)):rj(this,"error")}else e.call(f);tc(b)},!1,b);g.send(c)}Hx.prototype.a=function(b){return this.format.ja(b,{featureProjection:this.g})};function Jx(b){Hx.call(this,{attributions:b.attributions,format:b.format,logo:b.logo,projection:b.projection});m(b.arrayBuffer)&&this.fb(this.a(b.arrayBuffer));m(b.doc)&&this.fb(this.a(b.doc));m(b.node)&&this.fb(this.a(b.node));m(b.object)&&this.fb(this.a(b.object));m(b.text)&&this.fb(this.a(b.text));if(m(b.url)||m(b.urls))if(rj(this,"loading"),m(b.url)&&Ix(this,b.url,this.p,this.j,this),m(b.urls)){b=b.urls;var c,d;c=0;for(d=b.length;c<d;++c)Ix(this,b[c],this.p,this.j,this)}}u(Jx,Hx); -Jx.prototype.j=function(){rj(this,"error")};Jx.prototype.p=function(b){this.fb(b);rj(this,"ready")};function Kx(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,extent:b.extent,format:new Ep({defaultDataProjection:b.defaultProjection}),logo:b.logo,object:b.object,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Kx,Jx);function Lx(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,doc:b.doc,extent:b.extent,format:new Vq,logo:b.logo,node:b.node,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Lx,Jx);function Mx(b){b=m(b)?b:{};Jx.call(this,{format:new Fr({altitudeMode:b.altitudeMode}),projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Mx,Jx);function Nx(b){qj.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection,state:b.state});this.o=m(b.resolutions)?b.resolutions:null}u(Nx,qj);function Ox(b,c){if(null!==b.o){var d=ec(b.o,c,0);c=b.o[d]}return c}function Px(b,c){b.c().src=c};function Qx(b){Nx.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions,state:m(b.state)?b.state:void 0});this.t=b.canvasFunction;this.p=null;this.q=0;this.F=m(b.ratio)?b.ratio:1.5}u(Qx,Nx);Qx.prototype.rc=function(b,c,d,e){c=Ox(this,c);var f=this.p;if(null!==f&&this.q==this.c&&f.resolution==c&&f.e==d&&$d(f.D(),b))return f;b=b.slice();ue(b,this.F);e=this.t(b,c,d,[re(b)/c*d,oe(b)/c*d],e);null===e||(f=new Xu(b,c,d,this.e,e));this.p=f;this.q=this.c;return f};function Rx(b){Nx.call(this,{projection:b.projection,resolutions:b.resolutions});this.q=m(b.crossOrigin)?b.crossOrigin:null;this.b=m(b.displayDpi)?b.displayDpi:96;this.a=m(b.params)?b.params:{};var c;m(b.url)?c=Zu(b.url,this.a,sa(this.Q,this)):c=$u;this.j=c;this.F=m(b.imageLoadFunction)?b.imageLoadFunction:Px;this.t=m(b.hidpi)?b.hidpi:!0;this.p=m(b.metersPerUnit)?b.metersPerUnit:1;this.f=m(b.ratio)?b.ratio:1;this.ba=m(b.useOverlay)?b.useOverlay:!1;this.d=null;this.i=0}u(Rx,Nx);Rx.prototype.J=function(){return this.a}; -Rx.prototype.rc=function(b,c,d,e){c=Ox(this,c);d=this.t?d:1;var f=this.d;if(null!==f&&this.i==this.c&&f.resolution==c&&f.e==d&&$d(f.D(),b))return f;1!=this.f&&(b=b.slice(),ue(b,this.f));e=this.j(b,[re(b)/c*d,oe(b)/c*d],e);m(e)?f=new Wu(b,c,d,this.e,e,this.q,this.F):f=null;this.d=f;this.i=this.c;return f};Rx.prototype.V=function(b){Eb(this.a,b);this.l()}; -Rx.prototype.Q=function(b,c,d,e){var f;f=this.p;var g=re(d),h=oe(d),k=e[0],n=e[1],p=.0254/this.b;f=n*g>k*h?g*f/(k*p):h*f/(n*p);d=ke(d);e={OPERATION:this.ba?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.b,SETDISPLAYWIDTH:Math.round(e[0]),SETDISPLAYHEIGHT:Math.round(e[1]),SETVIEWSCALE:f,SETVIEWCENTERX:d[0],SETVIEWCENTERY:d[1]};Eb(e,c);return xh(zh([b],e))};function Sx(b){var c=m(b.attributions)?b.attributions:null,d=b.imageExtent,e,f;m(b.imageSize)&&(e=oe(d)/b.imageSize[1],f=[e]);var g=m(b.crossOrigin)?b.crossOrigin:null,h=m(b.imageLoadFunction)?b.imageLoadFunction:Px;Nx.call(this,{attributions:c,logo:b.logo,projection:Ee(b.projection),resolutions:f});this.a=new Wu(d,e,1,c,b.url,g,h)}u(Sx,Nx);Sx.prototype.rc=function(b){return qe(b,this.a.D())?this.a:null};function Tx(b){this.a=b.source;this.J=Kd();this.b=Tf();this.d=[0,0];this.i=null;Qx.call(this,{attributions:b.attributions,canvasFunction:sa(this.Xg,this),logo:b.logo,projection:b.projection,ratio:b.ratio,resolutions:b.resolutions,state:this.a.n});this.j=null;this.f=void 0;this.yf(b.style);z(this.a,"change",this.Aj,void 0,this)}u(Tx,Qx);l=Tx.prototype; -l.Xg=function(b,c,d,e,f){var g=new Wm(.5*c/d,b,c);this.a.Hb(b,c,f);var h=!1;this.a.Db(b,c,function(b){var e;if(!(e=h)){var f;m(b.a)?f=b.a.call(b,c):m(this.f)&&(f=this.f(b,c));if(null!=f){var q,r=!1;e=0;for(q=f.length;e<q;++e)r=An(g,b,f[e],zn(c,d),this.zj,this)||r;e=r}else e=!1}h=e},this);Xm(g);if(h)return null;this.d[0]!=e[0]||this.d[1]!=e[1]?(this.b.canvas.width=e[0],this.b.canvas.height=e[1],this.d[0]=e[0],this.d[1]=e[1]):this.b.clearRect(0,0,e[0],e[1]);b=Ux(this,ke(b),c,d,e);$m(g,this.b,d,b,0, -{});this.i=g;return this.b.canvas};l.yd=function(b,c,d,e,f){if(null!==this.i){var g={};return ck(this.i,b,0,d,e,function(b){var c=ma(b).toString();if(!(c in g))return g[c]=!0,f(b)})}};l.wj=function(){return this.a};l.xj=function(){return this.j};l.yj=function(){return this.f};function Ux(b,c,d,e,f){return Vj(b.J,f[0]/2,f[1]/2,e/d,-e/d,0,-c[0],-c[1])}l.zj=function(){this.l()};l.Aj=function(){rj(this,this.a.n)};l.yf=function(b){this.j=m(b)?b:Wl;this.f=null===b?void 0:Vl(this.j);this.l()};function Vx(b){b=m(b)?b:{};Nx.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions});this.t=m(b.crossOrigin)?b.crossOrigin:null;this.b=b.url;this.J=m(b.imageLoadFunction)?b.imageLoadFunction:Px;this.a=b.params;this.f=!0;Wx(this);this.q=b.serverType;this.F=m(b.hidpi)?b.hidpi:!0;this.d=null;this.i=[0,0];this.p=0;this.j=m(b.ratio)?b.ratio:1.5}u(Vx,Nx);var Xx=[101,101];l=Vx.prototype; -l.Bj=function(b,c,d,e){if(m(this.b)){var f=ne(b,c,0,Xx),g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:x(this.a,"LAYERS")};Eb(g,this.a,e);e=Math.floor((f[3]-b[1])/c);g[this.f?"I":"X"]=Math.floor((b[0]-f[0])/c);g[this.f?"J":"Y"]=e;return Yx(this,f,Xx,1,Ee(d),g)}};l.Cj=function(){return this.a}; -l.rc=function(b,c,d,e){if(!m(this.b))return null;c=Ox(this,c);1==d||this.F&&m(this.q)||(d=1);var f=this.d;if(null!==f&&this.p==this.c&&f.resolution==c&&f.e==d&&$d(f.D(),b))return f;f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Eb(f,this.a);b=b.slice();var g=(b[0]+b[2])/2,h=(b[1]+b[3])/2;if(1!=this.j){var k=this.j*re(b)/2,n=this.j*oe(b)/2;b[0]=g-k;b[1]=h-n;b[2]=g+k;b[3]=h+n}var k=c/d,n=Math.ceil(re(b)/k),p=Math.ceil(oe(b)/k);b[0]=g-k*n/2;b[2]=g+k*n/2;b[1]=h-k* -p/2;b[3]=h+k*p/2;this.i[0]=n;this.i[1]=p;e=Yx(this,b,this.i,d,e,f);this.d=new Wu(b,c,d,this.e,e,this.t,this.J);this.p=this.c;return this.d}; -function Yx(b,c,d,e,f,g){g[b.f?"CRS":"SRS"]=f.a;"STYLES"in b.a||(g.STYLES=new String(""));if(1!=e)switch(b.q){case "geoserver":g.FORMAT_OPTIONS="dpi:"+(90*e+.5|0);break;case "mapserver":g.MAP_RESOLUTION=90*e;break;case "carmentaserver":case "qgis":g.DPI=90*e}g.WIDTH=d[0];g.HEIGHT=d[1];d=f.b;var h;b.f&&"ne"==d.substr(0,2)?h=[c[1],c[0],c[3],c[2]]:h=c;g.BBOX=h.join(",");return xh(zh([b.b],g))}l.Dj=function(){return this.b};l.Ej=function(b){b!=this.b&&(this.b=b,this.d=null,this.l())}; -l.Fj=function(b){Eb(this.a,b);Wx(this);this.d=null;this.l()};function Wx(b){b.f=0<=Na(x(b.a,"VERSION","1.3.0"),"1.3")};function Zx(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,doc:b.doc,format:new Kr({extractStyles:b.extractStyles,defaultStyle:b.defaultStyle}),logo:b.logo,node:b.node,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(Zx,Jx);function $x(b){var c=m(b.projection)?b.projection:"EPSG:3857",d=new fx({extent:Ej(c),maxZoom:b.maxZoom,tileSize:b.tileSize});dx.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:c,tileGrid:d,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:Yw});this.d=d.Bb({wrapX:b.wrapX});m(b.tileUrlFunction)?this.pa(b.tileUrlFunction):m(b.urls)?this.pa(Ww(b.urls)):m(b.url)&&this.a(b.url)}u($x,dx); -$x.prototype.pa=function(b){$x.S.pa.call(this,Zw(this.d,b))};$x.prototype.a=function(b){this.pa(Ww($w(b)))};function ay(b){b=m(b)?b:{};var c;m(b.attributions)?c=b.attributions:c=[by];var d=Wb?"https:":"http:";$x.call(this,{attributions:c,crossOrigin:m(b.crossOrigin)?b.crossOrigin:"anonymous",opaque:!0,maxZoom:m(b.maxZoom)?b.maxZoom:19,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:d+"//{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png"})}u(ay,$x);var by=new tf({html:'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'});function cy(b){b=m(b)?b:{};var c=dy[b.layer];$x.call(this,{attributions:c.attributions,crossOrigin:"anonymous",logo:"//developer.mapquest.com/content/osm/mq_logo.png",maxZoom:c.maxZoom,opaque:!0,tileLoadFunction:b.tileLoadFunction,url:(Wb?"https:":"http:")+"//otile{1-4}-s.mqcdn.com/tiles/1.0.0/"+b.layer+"/{z}/{x}/{y}.jpg"})}u(cy,$x); -var ey=new tf({html:'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'}),dy={osm:{maxZoom:19,attributions:[ey,by]},sat:{maxZoom:18,attributions:[ey,new tf({html:"Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency"})]},hyb:{maxZoom:18,attributions:[ey,by]}};function fy(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,doc:b.doc,format:new ot,logo:b.logo,node:b.node,projection:b.projection,text:b.text,url:b.url,urls:b.urls})}u(fy,Jx);function gy(b){Hx.call(this,{attributions:b.attributions,format:b.format,logo:b.logo,projection:b.projection});this.p=new Wv;this.q=b.loader;this.t=m(b.strategy)?b.strategy:Iw;this.j={}}u(gy,Hx);gy.prototype.fb=function(b){var c=[],d,e;d=0;for(e=b.length;d<e;++d){var f=b[d],g=f.X;m(g)?g in this.j||(c.push(f),this.j[g]=!0):c.push(f)}gy.S.fb.call(this,c)};gy.prototype.clear=function(){yb(this.j);this.p.clear();gy.S.clear.call(this)}; -gy.prototype.Hb=function(b,c,d){var e=this.p;b=this.t(b,c);var f,g;f=0;for(g=b.length;f<g;++f){var h=b[f];$v(e,h,function(b){return $d(b.extent,h)})||(this.q.call(this,h,c,d),e.na(h,{extent:h.slice()}))}};var hy={terrain:{Wa:"jpg",opaque:!0},"terrain-background":{Wa:"jpg",opaque:!0},"terrain-labels":{Wa:"png",opaque:!1},"terrain-lines":{Wa:"png",opaque:!1},"toner-background":{Wa:"png",opaque:!0},toner:{Wa:"png",opaque:!0},"toner-hybrid":{Wa:"png",opaque:!1},"toner-labels":{Wa:"png",opaque:!1},"toner-lines":{Wa:"png",opaque:!1},"toner-lite":{Wa:"png",opaque:!0},watercolor:{Wa:"jpg",opaque:!0}},iy={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:3,maxZoom:16}}; -function jy(b){var c=b.layer.indexOf("-"),d=hy[b.layer],e=Wb?"https://stamen-tiles-{a-d}.a.ssl.fastly.net/":"http://{a-d}.tile.stamen.com/";$x.call(this,{attributions:ky,crossOrigin:"anonymous",maxZoom:iy[-1==c?b.layer:b.layer.slice(0,c)].maxZoom,opaque:d.opaque,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:e+b.layer+"/{z}/{x}/{y}."+d.Wa})}u(jy,$x); -var ky=[new tf({html:'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.'}),by];function ly(b,c){uj.call(this,b,2);this.b=c.sa(b[0]);this.c={}}u(ly,uj);ly.prototype.Na=function(b){b=m(b)?ma(b):-1;if(b in this.c)return this.c[b];var c=this.b,d=Tf(c,c);d.strokeStyle="black";d.strokeRect(.5,.5,c+.5,c+.5);d.fillStyle="black";d.textAlign="center";d.textBaseline="middle";d.font="24px sans-serif";d.fillText(nf(this.a),c/2,c/2);return this.c[b]=d.canvas};function my(b){Fj.call(this,{opaque:!1,projection:b.projection,tileGrid:b.tileGrid});this.a=new ax}u(my,Fj);my.prototype.zd=function(){return bx(this.a)}; -my.prototype.se=function(b){cx(this.a,b)};my.prototype.Fb=function(b,c,d){var e=this.hb(b,c,d);if(Fo(this.a,e))return this.a.get(e);b=new ly([b,c,d],this.tileGrid);this.a.set(e,b);return b};function ny(b){dx.call(this,{crossOrigin:b.crossOrigin,projection:Ee("EPSG:3857"),state:"loading",tileLoadFunction:b.tileLoadFunction});this.d=b.wrapX;(new Qw(b.url)).send(void 0,sa(this.a,this))}u(ny,dx); -ny.prototype.a=function(b){var c=Ee("EPSG:4326"),d=this.g,e;m(b.bounds)&&(e=we(b.bounds,De(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=new fx({extent:Ej(d),maxZoom:g,minZoom:f});this.tileUrlFunction=Zw(d.Bb({extent:e,wrapX:this.d}),Ww(b.tiles));if(m(b.attribution)){c=m(e)?e:c.D();e={};for(var h;f<=g;++f)h=f.toString(),e[h]=[zj(d,c,f)];this.e=[new tf({html:b.attribution,tileRanges:e})]}rj(this,"ready")};function oy(b){Fj.call(this,{projection:Ee("EPSG:3857"),state:"loading"});this.f=m(b.preemptive)?b.preemptive:!0;this.b=Yw;this.a=new ax;this.d=void 0;(new Qw(b.url)).send(void 0,sa(this.Gj,this))}u(oy,Fj);l=oy.prototype;l.zd=function(){return bx(this.a)};l.se=function(b){cx(this.a,b)};l.Gh=function(){return this.d};l.eh=function(b,c,d,e,f){null===this.tileGrid?!0===f?$h(function(){d.call(e,null)}):d.call(e,null):(c=this.tileGrid.Vb(b,c),py(this.Fb(c[0],c[1],c[2],1,this.g),b,d,e,f))}; -l.Gj=function(b){var c=Ee("EPSG:4326"),d=this.g,e;m(b.bounds)&&(e=we(b.bounds,De(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=new fx({extent:Ej(d),maxZoom:g,minZoom:f});this.d=b.template;var h=b.grids;if(null!=h){this.b=Zw(d.Bb({extent:e}),Ww(h));if(m(b.attribution)){c=m(e)?e:c.D();for(e={};f<=g;++f)h=f.toString(),e[h]=[zj(d,c,f)];this.e=[new tf({html:b.attribution,tileRanges:e})]}rj(this,"ready")}else rj(this,"error")}; -l.Fb=function(b,c,d,e,f){var g=this.hb(b,c,d);if(Fo(this.a,g))return this.a.get(g);b=[b,c,d];e=this.b(b,e,f);e=new qy(b,m(e)?0:4,m(e)?e:"",yj(this.tileGrid,b),this.f);this.a.set(g,e);return e};l.He=function(b,c,d){b=this.hb(b,c,d);Fo(this.a,b)&&this.a.get(b)};function qy(b,c,d,e,f){uj.call(this,b,c);this.g=d;this.c=e;this.o=f;this.d=this.e=this.b=null}u(qy,uj);l=qy.prototype;l.Na=function(){return null}; -function ry(b,c){if(null===b.b||null===b.e||null===b.d)return null;var d=b.b[Math.floor((1-(c[1]-b.c[1])/(b.c[3]-b.c[1]))*b.b.length)];if(!ia(d))return null;d=d.charCodeAt(Math.floor((c[0]-b.c[0])/(b.c[2]-b.c[0])*d.length));93<=d&&d--;35<=d&&d--;d=b.e[d-32];return null!=d?b.d[d]:null}function py(b,c,d,e,f){0==b.state&&!0===f?(Wc(b,"change",function(){d.call(e,ry(this,c))},!1,b),sy(b)):!0===f?$h(function(){d.call(e,ry(this,c))},b):d.call(e,ry(b,c))}l.mb=function(){return this.g}; -l.Vh=function(){this.state=3;vj(this)};l.gi=function(b){this.b=b.grid;this.e=b.keys;this.d=b.data;this.state=4;vj(this)};function sy(b){0==b.state&&(b.state=1,(new Qw(b.g)).send(void 0,sa(b.gi,b),sa(b.Vh,b)))}l.load=function(){this.o&&sy(this)};function ty(b){Hx.call(this,{attributions:b.attributions,format:b.format,logo:b.logo,projection:b.projection});this.p=b.tileGrid;this.q=Yw;this.t=this.p.Bb();this.j={};m(b.tileUrlFunction)?(this.q=b.tileUrlFunction,this.l()):m(b.urls)?(this.q=Ww(b.urls),this.l()):m(b.url)&&(this.q=Ww($w(b.url)),this.l())}u(ty,Hx);l=ty.prototype;l.clear=function(){yb(this.j)}; -function uy(b,c,d,e){var f=b.j;b=b.p.Vb(c,d);f=f[b[0]+"/"+b[1]+"/"+b[2]];if(m(f))for(b=0,d=f.length;b<d;++b){var g=f[b];if(g.N().Jb(c[0],c[1])&&e.call(void 0,g))break}}l.Db=function(b,c,d,e){var f=this.p,g=this.j;c=ec(f.a,c,0);b=zj(f,b,c);for(var h,f=b.a;f<=b.d;++f)for(h=b.b;h<=b.c;++h){var k=g[c+"/"+f+"/"+h];if(m(k)){var n,p;n=0;for(p=k.length;n<p;++n){var q=d.call(e,k[n]);if(q)return q}}}};l.va=function(){var b=this.j,c=[],d;for(d in b)db(c,b[d]);return c}; -l.jh=function(b,c){var d=[];uy(this,b,c,function(b){d.push(b)});return d};l.Hb=function(b,c,d){function e(b,c){k[b]=c;rj(this,"ready")}var f=this.t,g=this.p,h=this.q,k=this.j;c=ec(g.a,c,0);b=zj(g,b,c);var g=[c,0,0],n,p;for(n=b.a;n<=b.d;++n)for(p=b.b;p<=b.c;++p){var q=c+"/"+n+"/"+p;if(!(q in k)){g[0]=c;g[1]=n;g[2]=p;f(g,d,g);var r=h(g,1,d);m(r)&&(k[q]=[],Ix(this,r,ta(e,q),ca,this))}}};function vy(b){b=m(b)?b:{};var c=m(b.params)?b.params:{};dx.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,opaque:!x(c,"TRANSPARENT",!0),projection:b.projection,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction,tileUrlFunction:sa(this.Kj,this)});var d=b.urls;!m(d)&&m(b.url)&&(d=$w(b.url));this.f=null!=d?d:[];this.o=m(b.gutter)?b.gutter:0;this.a=c;this.d=!0;this.i=b.serverType;this.p=m(b.hidpi)?b.hidpi:!0;this.j="";wy(this);this.q=Vd();xy(this)}u(vy,dx);l=vy.prototype; -l.Hj=function(b,c,d,e){d=Ee(d);var f=this.tileGrid;null===f&&(f=Gj(this,d));c=f.Vb(b,c);if(!(f.a.length<=c[0])){var g=f.ka(c[0]),h=yj(f,c,this.q),f=f.sa(c[0]),k=this.o;0!==k&&(f+=2*k,h=Yd(h,g*k,h));k={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:x(this.a,"LAYERS")};Eb(k,this.a,e);e=Math.floor((h[3]-b[1])/g);k[this.d?"I":"X"]=Math.floor((b[0]-h[0])/g);k[this.d?"J":"Y"]=e;return yy(this,c,f,h,1,d,k)}};l.bd=function(){return this.o}; -l.hb=function(b,c,d){return this.j+vy.S.hb.call(this,b,c,d)};l.Ij=function(){return this.a}; -function yy(b,c,d,e,f,g,h){var k=b.f;if(0!=k.length){h.WIDTH=d;h.HEIGHT=d;h[b.d?"CRS":"SRS"]=g.a;"STYLES"in b.a||(h.STYLES=new String(""));if(1!=f)switch(b.i){case "geoserver":h.FORMAT_OPTIONS="dpi:"+(90*f+.5|0);break;case "mapserver":h.MAP_RESOLUTION=90*f;break;case "carmentaserver":case "qgis":h.DPI=90*f}d=g.b;b.d&&"ne"==d.substr(0,2)&&(b=e[0],e[0]=e[1],e[1]=b,b=e[2],e[2]=e[3],e[3]=b);h.BBOX=e.join(",");return xh(zh([1==k.length?k[0]:k[Zb((c[1]<<c[0])+c[2],k.length)]],h))}} -l.Gc=function(b,c,d){b=vy.S.Gc.call(this,b,c,d);return 1!=c&&this.p&&m(this.i)?b*c+.5|0:b};l.Kh=function(){return this.f};function wy(b){var c=0,d=[],e,f;e=0;for(f=b.f.length;e<f;++e)d[c++]=b.f[e];for(var g in b.a)d[c++]=g+"-"+b.a[g];b.j=d.join("#")}l.Jj=function(b){b=m(b)?$w(b):null;this.zf(b)};l.zf=function(b){this.f=null!=b?b:[];wy(this);this.l()}; -l.Kj=function(b,c,d){var e=this.tileGrid;null===e&&(e=Gj(this,d));if(!(e.a.length<=b[0])){1==c||this.p&&m(this.i)||(c=1);var f=e.ka(b[0]),g=yj(e,b,this.q),e=e.sa(b[0]),h=this.o;0!==h&&(e+=2*h,g=Yd(g,f*h,g));1!=c&&(e=e*c+.5|0);f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Eb(f,this.a);return yy(this,b,e,g,c,d,f)}};l.Lj=function(b){Eb(this.a,b);wy(this);xy(this);this.l()};function xy(b){b.d=0<=Na(x(b.a,"VERSION","1.3.0"),"1.3")};function zy(b){b=m(b)?b:{};Jx.call(this,{attributions:b.attributions,extent:b.extent,format:new Et({defaultDataProjection:b.defaultProjection}),logo:b.logo,object:b.object,projection:b.projection,text:b.text,url:b.url})}u(zy,Jx);function Ay(b){this.b=b.matrixIds;wj.call(this,{origin:b.origin,origins:b.origins,resolutions:b.resolutions,tileSize:b.tileSize,tileSizes:b.tileSizes})}u(Ay,wj);Ay.prototype.g=function(){return this.b};function By(b){function c(b){b="KVP"==f?xh(zh([b],h)):b.replace(/\{(\w+?)\}/g,function(b,c){return c in h?h[c]:b});return function(c){if(null!==c){var d={TileMatrix:g.b[c[0]],TileCol:c[1],TileRow:c[2]};Eb(d,k);c=b;return c="KVP"==f?xh(zh([c],d)):c.replace(/\{(\w+?)\}/g,function(b,c){return d[c]})}}}var d=m(b.version)?b.version:"1.0.0",e=m(b.format)?b.format:"image/jpeg";this.a=m(b.dimensions)?b.dimensions:{};this.d="";Cy(this);var f=m(b.requestEncoding)?b.requestEncoding:"KVP",g=b.tileGrid,h={Layer:b.layer, -Style:b.style,TileMatrixSet:b.matrixSet};"KVP"==f&&Eb(h,{Service:"WMTS",Request:"GetTile",Version:d,Format:e});var k=this.a,d=Yw,e=b.urls;!m(e)&&m(b.url)&&(e=$w(b.url));m(e)&&(d=Xw(Va(e,c)));var n=Vd(),p=[0,0,0],d=Zw(function(b,c,d){if(g.a.length<=b[0])return null;var e=b[1],f=-b[2]-1,h=yj(g,b,n),k=c.D();null!==k&&c.f&&(c=Math.ceil(re(k)/re(h)),e=Zb(e,c),p[0]=b[0],p[1]=e,p[2]=b[2],h=yj(g,p,n));return!qe(h,k)||qe(h,k)&&(h[0]==k[2]||h[2]==k[0]||h[1]==k[3]||h[3]==k[1])?null:kf(b[0],e,f,d)},d);dx.call(this, -{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:b.projection,tileClass:b.tileClass,tileGrid:g,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:d})}u(By,dx);By.prototype.f=function(){return this.a};By.prototype.hb=function(b,c,d){return this.d+By.S.hb.call(this,b,c,d)};function Cy(b){var c=0,d=[],e;for(e in b.a)d[c++]=e+"-"+b.a[e];b.d=d.join("/")}By.prototype.o=function(b){Eb(this.a,b);Cy(this);this.l()};function Dy(b){var c=m(b)?b:c;wj.call(this,{origin:[0,0],resolutions:c.resolutions})}u(Dy,wj);Dy.prototype.Bb=function(b){b=m(b)?b:{};var c=this.minZoom,d=this.maxZoom,e=null;if(m(b.extent)){var e=Array(d+1),f;for(f=0;f<=d;++f)e[f]=f<c?null:zj(this,b.extent,f)}return function(b,f,k){f=b[0];if(f<c||d<f)return null;var n=Math.pow(2,f),p=b[1];if(0>p||n<=p)return null;b=b[2];return b<-n||-1<b||null!==e&&!qf(e[f],p,-b-1)?null:kf(f,p,-b-1,k)}};function Ey(b){b=m(b)?b:{};var c=b.size,d=c[0],e=c[1],f=[],g=256;switch(m(b.tierSizeCalculation)?b.tierSizeCalculation:"default"){case "default":for(;d>g||e>g;)f.push([Math.ceil(d/g),Math.ceil(e/g)]),g+=g;break;case "truncated":for(;d>g||e>g;)f.push([Math.ceil(d/g),Math.ceil(e/g)]),d>>=1,e>>=1}f.push([1,1]);f.reverse();for(var g=[1],h=[0],e=1,d=f.length;e<d;e++)g.push(1<<e),h.push(f[e-1][0]*f[e-1][1]+h[e-1]);g.reverse();var g=new Dy({resolutions:g}),k=b.url,c=Zw(g.Bb({extent:[0,0,c[0],c[1]]}),function(b){if(null!== -b){var c=b[0],d=b[1];b=b[2];return k+"TileGroup"+((d+b*f[c][0]+h[c])/256|0)+"/"+c+"-"+d+"-"+b+".jpg"}});dx.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,tileClass:Fy,tileGrid:g,tileUrlFunction:c})}u(Ey,dx);function Fy(b,c,d,e,f){Yu.call(this,b,c,d,e,f);this.e={}}u(Fy,Yu); -Fy.prototype.Na=function(b){var c=m(b)?ma(b).toString():"";if(c in this.e)return this.e[c];b=Fy.S.Na.call(this,b);if(2==this.state){if(256==b.width&&256==b.height)return this.e[c]=b;var d=Tf(256,256);d.drawImage(b,0,0);return this.e[c]=d.canvas}return b};function Gy(b){b=m(b)?b:{};this.c=m(b.initialSize)?b.initialSize:256;this.b=m(b.maxSize)?b.maxSize:m(va)?va:2048;this.a=m(b.space)?b.space:1;this.e=[new Hy(this.c,this.a)];this.d=this.c;this.f=[new Hy(this.d,this.a)]} -Gy.prototype.add=function(b,c,d,e,f,g){if(c+this.a>this.b||d+this.a>this.b)return null;e=Iy(this,!1,b,c,d,e,g);if(null===e)return null;var h=null;m(f)&&(h=Iy(this,!0,b,c,d,f,g));return{offsetX:e.offsetX,offsetY:e.offsetY,image:e.image,Wl:null===h?void 0:h.offsetX,Xl:null===h?void 0:h.offsetY,kf:null===h?void 0:h.image}}; -function Iy(b,c,d,e,f,g,h){var k=c?b.f:b.e,n,p,q;p=0;for(q=k.length;p<q;++p){n=k[p];n=n.add(d,e,f,g,h);if(null!==n)return n;null===n&&p===q-1&&(c?(n=Math.min(2*b.d,b.b),b.d=n):(n=Math.min(2*b.c,b.b),b.c=n),n=new Hy(n,b.a),k.push(n),++q)}}function Hy(b,c){this.a=c;this.c=[{x:0,y:0,width:b,height:b}];this.d={};this.b=If("CANVAS");this.b.width=b;this.b.height=b;this.e=this.b.getContext("2d")}Hy.prototype.get=function(b){return x(this.d,b,null)}; -Hy.prototype.add=function(b,c,d,e,f){var g,h,k;h=0;for(k=this.c.length;h<k;++h)if(g=this.c[h],g.width>=c+this.a&&g.height>=d+this.a)return k={offsetX:g.x+this.a,offsetY:g.y+this.a,image:this.b},this.d[b]=k,e.call(f,this.e,g.x+this.a,g.y+this.a),b=h,c=c+this.a,d=d+this.a,f=e=void 0,g.width-c>g.height-d?(e={x:g.x+c,y:g.y,width:g.width-c,height:g.height},f={x:g.x,y:g.y+d,width:c,height:g.height-d},Jy(this,b,e,f)):(e={x:g.x+c,y:g.y,width:g.width-c,height:d},f={x:g.x,y:g.y+d,width:g.width,height:g.height- -d},Jy(this,b,e,f)),k;return null};function Jy(b,c,d,e){c=[c,1];0<d.width&&0<d.height&&c.push(d);0<e.width&&0<e.height&&c.push(e);b.c.splice.apply(b.c,c)};function Ky(b){this.j=this.b=this.d=null;this.o=m(b.fill)?b.fill:null;this.J=[0,0];this.a=b.points;this.c=m(b.radius)?b.radius:b.radius1;this.f=m(b.radius2)?b.radius2:this.c;this.g=m(b.angle)?b.angle:0;this.e=m(b.stroke)?b.stroke:null;this.F=this.Q=this.t=null;var c=b.atlasManager,d=null,e,f=0;null!==this.e&&(e=wg(this.e.a),f=this.e.c,m(f)||(f=1),d=this.e.b,cg||(d=null));var g=2*(this.c+f)+1,d={strokeStyle:e,Nc:f,size:g,lineDash:d};if(m(c)){g=Math.round(g);e=null===this.o;var h;e&&(h=sa(this.Df,this, -d));f=this.ub();d=c.add(f,g,g,sa(this.Ef,this,d),h);this.b=d.image;this.J=[d.offsetX,d.offsetY];c=d.image.width;this.j=e?d.kf:this.b}else this.b=If("CANVAS"),this.b.height=g,this.b.width=g,c=g=this.b.width,h=this.b.getContext("2d"),this.Ef(d,h,0,0),null===this.o?(h=this.j=If("CANVAS"),h.height=d.size,h.width=d.size,h=h.getContext("2d"),this.Df(d,h,0,0)):this.j=this.b;this.t=[g/2,g/2];this.Q=[g,g];this.F=[c,c];Rj.call(this,{opacity:1,rotateWithView:!1,rotation:m(b.rotation)?b.rotation:0,scale:1,snapToPixel:m(b.snapToPixel)? -b.snapToPixel:!0})}u(Ky,Rj);l=Ky.prototype;l.tb=function(){return this.t};l.Qj=function(){return this.g};l.Rj=function(){return this.o};l.te=function(){return this.j};l.yb=function(){return this.b};l.cd=function(){return this.F};l.ue=function(){return 2};l.zb=function(){return this.J};l.Sj=function(){return this.a};l.Tj=function(){return this.c};l.Eh=function(){return this.f};l.ab=function(){return this.Q};l.Uj=function(){return this.e};l.ne=ca;l.load=ca;l.Ge=ca; -l.Ef=function(b,c,d,e){var f;c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.f!==this.c&&(this.a*=2);for(d=0;d<=this.a;d++)e=2*d*Math.PI/this.a-Math.PI/2+this.g,f=0===d%2?this.c:this.f,c.lineTo(b.size/2+f*Math.cos(e),b.size/2+f*Math.sin(e));null!==this.o&&(c.fillStyle=wg(this.o.a),c.fill());null!==this.e&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()}; -l.Df=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.f!==this.c&&(this.a*=2);var f;for(d=0;d<=this.a;d++)f=2*d*Math.PI/this.a-Math.PI/2+this.g,e=0===d%2?this.c:this.f,c.lineTo(b.size/2+e*Math.cos(f),b.size/2+e*Math.sin(f));c.fillStyle=Ol;c.fill();null!==this.e&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Nc,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()}; -l.ub=function(){var b=null===this.e?"-":this.e.ub(),c=null===this.o?"-":this.o.ub();if(null===this.d||b!=this.d[1]||c!=this.d[2]||this.c!=this.d[3]||this.f!=this.d[4]||this.g!=this.d[5]||this.a!=this.d[6])this.d=["r"+b+c+(m(this.c)?this.c.toString():"-")+(m(this.f)?this.f.toString():"-")+(m(this.g)?this.g.toString():"-")+(m(this.a)?this.a.toString():"-"),b,c,this.c,this.f,this.g,this.a];return this.d[0]};t("ol.animation.bounce",function(b){var c=b.resolution,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:ff;return function(b,h){if(h.time<d)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<d+e){var k=f((h.time-d)/e),n=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*n;h.viewHints[0]+=1;return!0}return!1}},OPENLAYERS);t("ol.animation.pan",gf,OPENLAYERS);t("ol.animation.rotate",hf,OPENLAYERS);t("ol.animation.zoom",jf,OPENLAYERS); -t("ol.Attribution",tf,OPENLAYERS);tf.prototype.getHTML=tf.prototype.b;pg.prototype.element=pg.prototype.element;t("ol.Collection",B,OPENLAYERS);B.prototype.clear=B.prototype.clear;B.prototype.extend=B.prototype.qf;B.prototype.forEach=B.prototype.forEach;B.prototype.getArray=B.prototype.Mi;B.prototype.item=B.prototype.item;B.prototype.getLength=B.prototype.Ib;B.prototype.insertAt=B.prototype.rd;B.prototype.pop=B.prototype.pop;B.prototype.push=B.prototype.push;B.prototype.remove=B.prototype.remove; -B.prototype.removeAt=B.prototype.De;B.prototype.setAt=B.prototype.bl;t("ol.coordinate.add",yd,OPENLAYERS);t("ol.coordinate.createStringXY",function(b){return function(c){return Fd(c,b)}},OPENLAYERS);t("ol.coordinate.format",Bd,OPENLAYERS);t("ol.coordinate.rotate",Dd,OPENLAYERS);t("ol.coordinate.toStringHDMS",function(b){return m(b)?Ad(b[1],"NS")+" "+Ad(b[0],"EW"):""},OPENLAYERS);t("ol.coordinate.toStringXY",Fd,OPENLAYERS);t("ol.DeviceOrientation",Q,OPENLAYERS);Q.prototype.getAlpha=Q.prototype.e; -Q.prototype.getBeta=Q.prototype.f;Q.prototype.getGamma=Q.prototype.g;Q.prototype.getHeading=Q.prototype.i;Q.prototype.getTracking=Q.prototype.d;Q.prototype.setTracking=Q.prototype.b;t("ol.easing.easeIn",function(b){return Math.pow(b,3)},OPENLAYERS);t("ol.easing.easeOut",cf,OPENLAYERS);t("ol.easing.inAndOut",df,OPENLAYERS);t("ol.easing.linear",ef,OPENLAYERS);t("ol.easing.upAndDown",ff,OPENLAYERS);t("ol.extent.boundingExtent",Td,OPENLAYERS);t("ol.extent.buffer",Yd,OPENLAYERS); -t("ol.extent.containsCoordinate",function(b,c){return ae(b,c[0],c[1])},OPENLAYERS);t("ol.extent.containsExtent",$d,OPENLAYERS);t("ol.extent.containsXY",ae,OPENLAYERS);t("ol.extent.createEmpty",Vd,OPENLAYERS);t("ol.extent.equals",de,OPENLAYERS);t("ol.extent.extend",ee,OPENLAYERS);t("ol.extent.getBottomLeft",he,OPENLAYERS);t("ol.extent.getBottomRight",ie,OPENLAYERS);t("ol.extent.getCenter",ke,OPENLAYERS);t("ol.extent.getHeight",oe,OPENLAYERS);t("ol.extent.getIntersection",pe,OPENLAYERS); -t("ol.extent.getSize",function(b){return[b[2]-b[0],b[3]-b[1]]},OPENLAYERS);t("ol.extent.getTopLeft",me,OPENLAYERS);t("ol.extent.getTopRight",je,OPENLAYERS);t("ol.extent.getWidth",re,OPENLAYERS);t("ol.extent.intersects",qe,OPENLAYERS);t("ol.extent.isEmpty",se,OPENLAYERS);t("ol.extent.applyTransform",we,OPENLAYERS);t("ol.Feature",R,OPENLAYERS);R.prototype.clone=R.prototype.clone;R.prototype.getGeometry=R.prototype.N;R.prototype.getId=R.prototype.nh;R.prototype.getGeometryName=R.prototype.mh; -R.prototype.getStyle=R.prototype.Si;R.prototype.getStyleFunction=R.prototype.Ti;R.prototype.setGeometry=R.prototype.Ma;R.prototype.setStyle=R.prototype.i;R.prototype.setId=R.prototype.d;R.prototype.setGeometryName=R.prototype.e;t("ol.FeatureOverlay",sp,OPENLAYERS);sp.prototype.addFeature=sp.prototype.rf;sp.prototype.getFeatures=sp.prototype.Ni;sp.prototype.removeFeature=sp.prototype.wd;sp.prototype.setFeatures=sp.prototype.Mc;sp.prototype.setMap=sp.prototype.setMap;sp.prototype.setStyle=sp.prototype.tf; -sp.prototype.getStyle=sp.prototype.Oi;sp.prototype.getStyleFunction=sp.prototype.Pi;t("ol.Geolocation",Z,OPENLAYERS);Z.prototype.getAccuracy=Z.prototype.$e;Z.prototype.getAccuracyGeometry=Z.prototype.p;Z.prototype.getAltitude=Z.prototype.q;Z.prototype.getAltitudeAccuracy=Z.prototype.r;Z.prototype.getHeading=Z.prototype.F;Z.prototype.getPosition=Z.prototype.J;Z.prototype.getProjection=Z.prototype.g;Z.prototype.getSpeed=Z.prototype.t;Z.prototype.getTracking=Z.prototype.i; -Z.prototype.getTrackingOptions=Z.prototype.f;Z.prototype.setProjection=Z.prototype.n;Z.prototype.setTracking=Z.prototype.b;Z.prototype.setTrackingOptions=Z.prototype.j;t("ol.Graticule",Ru,OPENLAYERS);Ru.prototype.getMap=Ru.prototype.Wi;Ru.prototype.getMeridians=Ru.prototype.wh;Ru.prototype.getParallels=Ru.prototype.Bh;Ru.prototype.setMap=Ru.prototype.setMap;t("ol.has.DEVICE_PIXEL_RATIO",ag,OPENLAYERS);t("ol.has.CANVAS",dg,OPENLAYERS);t("ol.has.DEVICE_ORIENTATION",eg,OPENLAYERS); -t("ol.has.GEOLOCATION",fg,OPENLAYERS);t("ol.has.TOUCH",gg,OPENLAYERS);t("ol.has.WEBGL",$f,OPENLAYERS);Wu.prototype.getImage=Wu.prototype.c;Yu.prototype.getImage=Yu.prototype.Na;t("ol.Kinetic",lk,OPENLAYERS);t("ol.loadingstrategy.all",function(){return[[-Infinity,-Infinity,Infinity,Infinity]]},OPENLAYERS);t("ol.loadingstrategy.bbox",Iw,OPENLAYERS); -t("ol.loadingstrategy.createTile",function(b){return function(c,d){var e=ec(b.a,d,0),f=zj(b,c,e),g=[],e=[e,0,0];for(e[1]=f.a;e[1]<=f.d;++e[1])for(e[2]=f.b;e[2]<=f.c;++e[2])g.push(yj(b,e));return g}},OPENLAYERS);t("ol.Map",O,OPENLAYERS);O.prototype.addControl=O.prototype.Qg;O.prototype.addInteraction=O.prototype.Rg;O.prototype.addLayer=O.prototype.Se;O.prototype.addOverlay=O.prototype.Te;O.prototype.beforeRender=O.prototype.Ua;O.prototype.forEachFeatureAtPixel=O.prototype.oe; -O.prototype.getEventCoordinate=O.prototype.ih;O.prototype.getEventPixel=O.prototype.ad;O.prototype.getTarget=O.prototype.qc;O.prototype.getTargetElement=O.prototype.Fh;O.prototype.getCoordinateFromPixel=O.prototype.Ga;O.prototype.getControls=O.prototype.hh;O.prototype.getOverlays=O.prototype.Ah;O.prototype.getInteractions=O.prototype.oh;O.prototype.getLayerGroup=O.prototype.Eb;O.prototype.getLayers=O.prototype.ea;O.prototype.getPixelFromCoordinate=O.prototype.f;O.prototype.getSize=O.prototype.e; -O.prototype.getView=O.prototype.a;O.prototype.getViewport=O.prototype.Lh;O.prototype.renderSync=O.prototype.Zk;O.prototype.render=O.prototype.render;O.prototype.removeControl=O.prototype.Uk;O.prototype.removeInteraction=O.prototype.Vk;O.prototype.removeLayer=O.prototype.Wk;O.prototype.removeOverlay=O.prototype.Xk;O.prototype.setLayerGroup=O.prototype.cg;O.prototype.setSize=O.prototype.J;O.prototype.setTarget=O.prototype.qa;O.prototype.setView=O.prototype.Ta;O.prototype.updateSize=O.prototype.j; -cj.prototype.originalEvent=cj.prototype.originalEvent;cj.prototype.pixel=cj.prototype.pixel;cj.prototype.coordinate=cj.prototype.coordinate;cj.prototype.preventDefault=cj.prototype.preventDefault;cj.prototype.stopPropagation=cj.prototype.lb;Zg.prototype.map=Zg.prototype.map;Zg.prototype.frameState=Zg.prototype.frameState;od.prototype.key=od.prototype.key;od.prototype.oldValue=od.prototype.oldValue;pd.prototype.transform=pd.prototype.e;t("ol.Object",td,OPENLAYERS);td.prototype.bindTo=td.prototype.O; -td.prototype.get=td.prototype.get;td.prototype.getKeys=td.prototype.I;td.prototype.getProperties=td.prototype.L;td.prototype.set=td.prototype.set;td.prototype.setProperties=td.prototype.G;td.prototype.unbind=td.prototype.P;td.prototype.unbindAll=td.prototype.R;t("ol.Observable",md,OPENLAYERS);t("ol.Observable.unByKey",nd,OPENLAYERS);md.prototype.changed=md.prototype.l;md.prototype.getRevision=md.prototype.A;md.prototype.on=md.prototype.u;md.prototype.once=md.prototype.B;md.prototype.un=md.prototype.v; -md.prototype.unByKey=md.prototype.C;t("ol.WEBGL_MAX_TEXTURE_SIZE",va,OPENLAYERS);t("ol.inherits",u,OPENLAYERS);t("ol.Overlay",P,OPENLAYERS);P.prototype.getElement=P.prototype.d;P.prototype.getMap=P.prototype.e;P.prototype.getOffset=P.prototype.g;P.prototype.getPosition=P.prototype.n;P.prototype.getPositioning=P.prototype.i;P.prototype.setElement=P.prototype.Ee;P.prototype.setMap=P.prototype.setMap;P.prototype.setOffset=P.prototype.j;P.prototype.setPosition=P.prototype.f; -P.prototype.setPositioning=P.prototype.p;uj.prototype.getTileCoord=uj.prototype.f;t("ol.View",A,OPENLAYERS);A.prototype.constrainCenter=A.prototype.i;A.prototype.constrainResolution=A.prototype.constrainResolution;A.prototype.constrainRotation=A.prototype.constrainRotation;A.prototype.getCenter=A.prototype.a;A.prototype.calculateExtent=A.prototype.g;A.prototype.getProjection=A.prototype.J;A.prototype.getResolution=A.prototype.b;A.prototype.getResolutionForExtent=A.prototype.n; -A.prototype.getRotation=A.prototype.e;A.prototype.getZoom=A.prototype.Oh;A.prototype.fitExtent=A.prototype.ge;A.prototype.fitGeometry=A.prototype.dh;A.prototype.centerOn=A.prototype.Yg;A.prototype.rotate=A.prototype.rotate;A.prototype.setCenter=A.prototype.Oa;A.prototype.setResolution=A.prototype.d;A.prototype.setRotation=A.prototype.r;A.prototype.setZoom=A.prototype.Q;t("ol.xml.getAllTextContent",Pp,OPENLAYERS);t("ol.xml.parse",iq,OPENLAYERS);t("ol.webgl.Context",Jo,OPENLAYERS); -Jo.prototype.getGL=Jo.prototype.kk;Jo.prototype.useProgram=Jo.prototype.Gd;t("ol.tilegrid.TileGrid",wj,OPENLAYERS);wj.prototype.getMaxZoom=wj.prototype.ed;wj.prototype.getMinZoom=wj.prototype.fd;wj.prototype.getOrigin=wj.prototype.Lb;wj.prototype.getResolution=wj.prototype.ka;wj.prototype.getResolutions=wj.prototype.Fd;wj.prototype.getTileCoordForCoordAndResolution=wj.prototype.Vb;wj.prototype.getTileCoordForCoordAndZ=wj.prototype.Fc;wj.prototype.getTileSize=wj.prototype.sa; -t("ol.tilegrid.WMTS",Ay,OPENLAYERS);Ay.prototype.getMatrixIds=Ay.prototype.g;t("ol.tilegrid.XYZ",fx,OPENLAYERS);t("ol.tilegrid.Zoomify",Dy,OPENLAYERS);t("ol.style.AtlasManager",Gy,OPENLAYERS);t("ol.style.Circle",Sl,OPENLAYERS);Sl.prototype.getAnchor=Sl.prototype.tb;Sl.prototype.getFill=Sl.prototype.Mj;Sl.prototype.getImage=Sl.prototype.yb;Sl.prototype.getOrigin=Sl.prototype.zb;Sl.prototype.getRadius=Sl.prototype.Nj;Sl.prototype.getSize=Sl.prototype.ab;Sl.prototype.getStroke=Sl.prototype.Oj; -t("ol.style.Fill",Rl,OPENLAYERS);Rl.prototype.getColor=Rl.prototype.b;Rl.prototype.setColor=Rl.prototype.d;t("ol.style.Icon",Sj,OPENLAYERS);Sj.prototype.getAnchor=Sj.prototype.tb;Sj.prototype.getImage=Sj.prototype.yb;Sj.prototype.getOrigin=Sj.prototype.zb;Sj.prototype.getSrc=Sj.prototype.Pj;Sj.prototype.getSize=Sj.prototype.ab;t("ol.style.Image",Rj,OPENLAYERS);Rj.prototype.getOpacity=Rj.prototype.Ad;Rj.prototype.getRotateWithView=Rj.prototype.hd;Rj.prototype.getRotation=Rj.prototype.Bd; -Rj.prototype.getScale=Rj.prototype.Cd;Rj.prototype.getSnapToPixel=Rj.prototype.jd;Rj.prototype.getImage=Rj.prototype.yb;Rj.prototype.setRotation=Rj.prototype.Dd;Rj.prototype.setScale=Rj.prototype.Ed;t("ol.style.RegularShape",Ky,OPENLAYERS);Ky.prototype.getAnchor=Ky.prototype.tb;Ky.prototype.getAngle=Ky.prototype.Qj;Ky.prototype.getFill=Ky.prototype.Rj;Ky.prototype.getImage=Ky.prototype.yb;Ky.prototype.getOrigin=Ky.prototype.zb;Ky.prototype.getPoints=Ky.prototype.Sj;Ky.prototype.getRadius=Ky.prototype.Tj; -Ky.prototype.getRadius2=Ky.prototype.Eh;Ky.prototype.getSize=Ky.prototype.ab;Ky.prototype.getStroke=Ky.prototype.Uj;t("ol.style.Stroke",Nl,OPENLAYERS);Nl.prototype.getColor=Nl.prototype.Vj;Nl.prototype.getLineCap=Nl.prototype.rh;Nl.prototype.getLineDash=Nl.prototype.Wj;Nl.prototype.getLineJoin=Nl.prototype.sh;Nl.prototype.getMiterLimit=Nl.prototype.xh;Nl.prototype.getWidth=Nl.prototype.Xj;Nl.prototype.setColor=Nl.prototype.Yj;Nl.prototype.setLineCap=Nl.prototype.el;Nl.prototype.setLineDash=Nl.prototype.Zj; -Nl.prototype.setLineJoin=Nl.prototype.fl;Nl.prototype.setMiterLimit=Nl.prototype.gl;Nl.prototype.setWidth=Nl.prototype.ol;t("ol.style.Style",Tl,OPENLAYERS);Tl.prototype.getGeometry=Tl.prototype.N;Tl.prototype.getGeometryFunction=Tl.prototype.lh;Tl.prototype.getFill=Tl.prototype.$j;Tl.prototype.getImage=Tl.prototype.ak;Tl.prototype.getStroke=Tl.prototype.bk;Tl.prototype.getText=Tl.prototype.ck;Tl.prototype.getZIndex=Tl.prototype.Nh;Tl.prototype.setGeometry=Tl.prototype.Ff;Tl.prototype.setZIndex=Tl.prototype.ql; -t("ol.style.Text",Jr,OPENLAYERS);Jr.prototype.getFont=Jr.prototype.kh;Jr.prototype.getOffsetX=Jr.prototype.yh;Jr.prototype.getOffsetY=Jr.prototype.zh;Jr.prototype.getFill=Jr.prototype.dk;Jr.prototype.getRotation=Jr.prototype.ek;Jr.prototype.getScale=Jr.prototype.fk;Jr.prototype.getStroke=Jr.prototype.gk;Jr.prototype.getText=Jr.prototype.hk;Jr.prototype.getTextAlign=Jr.prototype.Hh;Jr.prototype.getTextBaseline=Jr.prototype.Ih;Jr.prototype.setFont=Jr.prototype.dl;Jr.prototype.setFill=Jr.prototype.cl; -Jr.prototype.setRotation=Jr.prototype.ik;Jr.prototype.setScale=Jr.prototype.jk;Jr.prototype.setStroke=Jr.prototype.kl;Jr.prototype.setText=Jr.prototype.ll;Jr.prototype.setTextAlign=Jr.prototype.ml;Jr.prototype.setTextBaseline=Jr.prototype.nl;t("ol.Sphere",xe,OPENLAYERS);t("ol.source.BingMaps",gx,OPENLAYERS);t("ol.source.BingMaps.TOS_ATTRIBUTION",hx,OPENLAYERS);t("ol.source.Cluster",ix,OPENLAYERS);Hx.prototype.readFeatures=Hx.prototype.a;t("ol.source.GeoJSON",Kx,OPENLAYERS);t("ol.source.GPX",Lx,OPENLAYERS); -t("ol.source.IGC",Mx,OPENLAYERS);t("ol.source.ImageCanvas",Qx,OPENLAYERS);t("ol.source.ImageMapGuide",Rx,OPENLAYERS);Rx.prototype.getParams=Rx.prototype.J;Rx.prototype.updateParams=Rx.prototype.V;t("ol.source.Image",Nx,OPENLAYERS);t("ol.source.ImageStatic",Sx,OPENLAYERS);t("ol.source.ImageVector",Tx,OPENLAYERS);Tx.prototype.getSource=Tx.prototype.wj;Tx.prototype.getStyle=Tx.prototype.xj;Tx.prototype.getStyleFunction=Tx.prototype.yj;Tx.prototype.setStyle=Tx.prototype.yf;t("ol.source.ImageWMS",Vx,OPENLAYERS); -Vx.prototype.getGetFeatureInfoUrl=Vx.prototype.Bj;Vx.prototype.getParams=Vx.prototype.Cj;Vx.prototype.getUrl=Vx.prototype.Dj;Vx.prototype.setUrl=Vx.prototype.Ej;Vx.prototype.updateParams=Vx.prototype.Fj;t("ol.source.KML",Zx,OPENLAYERS);t("ol.source.MapQuest",cy,OPENLAYERS);t("ol.source.OSM",ay,OPENLAYERS);t("ol.source.OSM.ATTRIBUTION",by,OPENLAYERS);t("ol.source.OSMXML",fy,OPENLAYERS);t("ol.source.ServerVector",gy,OPENLAYERS);gy.prototype.readFeatures=gy.prototype.a;t("ol.source.Source",qj,OPENLAYERS); -qj.prototype.getAttributions=qj.prototype.Y;qj.prototype.getLogo=qj.prototype.W;qj.prototype.getProjection=qj.prototype.Z;qj.prototype.getState=qj.prototype.$;t("ol.source.Stamen",jy,OPENLAYERS);t("ol.source.StaticVector",Jx,OPENLAYERS);t("ol.source.TileDebug",my,OPENLAYERS);t("ol.source.TileImage",dx,OPENLAYERS);dx.prototype.getTileLoadFunction=dx.prototype.ib;dx.prototype.getTileUrlFunction=dx.prototype.jb;dx.prototype.setTileLoadFunction=dx.prototype.ob;dx.prototype.setTileUrlFunction=dx.prototype.pa; -t("ol.source.TileJSON",ny,OPENLAYERS);t("ol.source.Tile",Fj,OPENLAYERS);Fj.prototype.getTileGrid=Fj.prototype.za;t("ol.source.TileUTFGrid",oy,OPENLAYERS);oy.prototype.getTemplate=oy.prototype.Gh;oy.prototype.forDataAtCoordinateAndResolution=oy.prototype.eh;t("ol.source.TileVector",ty,OPENLAYERS);ty.prototype.getFeatures=ty.prototype.va;ty.prototype.getFeaturesAtCoordinateAndResolution=ty.prototype.jh;t("ol.source.TileWMS",vy,OPENLAYERS);vy.prototype.getGetFeatureInfoUrl=vy.prototype.Hj; -vy.prototype.getParams=vy.prototype.Ij;vy.prototype.getUrls=vy.prototype.Kh;vy.prototype.setUrl=vy.prototype.Jj;vy.prototype.setUrls=vy.prototype.zf;vy.prototype.updateParams=vy.prototype.Lj;t("ol.source.TopoJSON",zy,OPENLAYERS);t("ol.source.Vector",aw,OPENLAYERS);aw.prototype.addFeature=aw.prototype.Pa;aw.prototype.addFeatures=aw.prototype.ya;aw.prototype.clear=aw.prototype.clear;aw.prototype.forEachFeature=aw.prototype.Xa;aw.prototype.forEachFeatureInExtent=aw.prototype.ra; -aw.prototype.forEachFeatureIntersectingExtent=aw.prototype.Fa;aw.prototype.getFeatures=aw.prototype.va;aw.prototype.getFeaturesAtCoordinate=aw.prototype.Ia;aw.prototype.getClosestFeatureToCoordinate=aw.prototype.Ya;aw.prototype.getExtent=aw.prototype.D;aw.prototype.getFeatureById=aw.prototype.Ha;aw.prototype.removeFeature=aw.prototype.$a;dw.prototype.feature=dw.prototype.feature;t("ol.source.WMTS",By,OPENLAYERS);By.prototype.getDimensions=By.prototype.f;By.prototype.updateDimensions=By.prototype.o; -t("ol.source.XYZ",$x,OPENLAYERS);$x.prototype.setTileUrlFunction=$x.prototype.pa;$x.prototype.setUrl=$x.prototype.a;t("ol.source.Zoomify",Ey,OPENLAYERS);Al.prototype.vectorContext=Al.prototype.vectorContext;Al.prototype.frameState=Al.prototype.frameState;Al.prototype.context=Al.prototype.context;Al.prototype.glContext=Al.prototype.glContext;no.prototype.drawAsync=no.prototype.ic;no.prototype.drawCircleGeometry=no.prototype.jc;no.prototype.drawFeature=no.prototype.ee; -no.prototype.drawGeometryCollectionGeometry=no.prototype.Zc;no.prototype.drawPointGeometry=no.prototype.rb;no.prototype.drawLineStringGeometry=no.prototype.Cb;no.prototype.drawMultiLineStringGeometry=no.prototype.kc;no.prototype.drawMultiPointGeometry=no.prototype.qb;no.prototype.drawMultiPolygonGeometry=no.prototype.lc;no.prototype.drawPolygonGeometry=no.prototype.Sb;no.prototype.drawText=no.prototype.sb;no.prototype.setFillStrokeStyle=no.prototype.wa;no.prototype.setImageStyle=no.prototype.cb; -no.prototype.setTextStyle=no.prototype.xa;um.prototype.drawAsync=um.prototype.ic;um.prototype.drawCircleGeometry=um.prototype.jc;um.prototype.drawFeature=um.prototype.ee;um.prototype.drawPointGeometry=um.prototype.rb;um.prototype.drawMultiPointGeometry=um.prototype.qb;um.prototype.drawLineStringGeometry=um.prototype.Cb;um.prototype.drawMultiLineStringGeometry=um.prototype.kc;um.prototype.drawPolygonGeometry=um.prototype.Sb;um.prototype.drawMultiPolygonGeometry=um.prototype.lc; -um.prototype.setFillStrokeStyle=um.prototype.wa;um.prototype.setImageStyle=um.prototype.cb;um.prototype.setTextStyle=um.prototype.xa;t("ol.proj.common.add",tm,OPENLAYERS);t("ol.proj.METERS_PER_UNIT",Ae,OPENLAYERS);t("ol.proj.Projection",Be,OPENLAYERS);Be.prototype.getCode=Be.prototype.gh;Be.prototype.getExtent=Be.prototype.D;Be.prototype.getUnits=Be.prototype.rj;Be.prototype.getMetersPerUnit=Be.prototype.ie;Be.prototype.getWorldExtent=Be.prototype.Mh;Be.prototype.isGlobal=Be.prototype.wi; -Be.prototype.setExtent=Be.prototype.sj;Be.prototype.setWorldExtent=Be.prototype.pl;t("ol.proj.addEquivalentProjections",He,OPENLAYERS);t("ol.proj.addProjection",Qe,OPENLAYERS);t("ol.proj.addCoordinateTransforms",Se,OPENLAYERS);t("ol.proj.get",Ee,OPENLAYERS);t("ol.proj.getTransform",Ve,OPENLAYERS);t("ol.proj.transform",function(b,c,d){return Ve(c,d)(b,void 0,b.length)},OPENLAYERS);t("ol.proj.transformExtent",Xe,OPENLAYERS);t("ol.layer.Heatmap",$,OPENLAYERS);$.prototype.getGradient=$.prototype.qa; -$.prototype.setGradient=$.prototype.fc;t("ol.layer.Image",J,OPENLAYERS);J.prototype.getSource=J.prototype.a;t("ol.layer.Layer",E,OPENLAYERS);E.prototype.getSource=E.prototype.a;E.prototype.setSource=E.prototype.ga;t("ol.layer.Base",D,OPENLAYERS);D.prototype.getBrightness=D.prototype.d;D.prototype.getContrast=D.prototype.e;D.prototype.getHue=D.prototype.f;D.prototype.getExtent=D.prototype.D;D.prototype.getMaxResolution=D.prototype.g;D.prototype.getMinResolution=D.prototype.i; -D.prototype.getOpacity=D.prototype.j;D.prototype.getSaturation=D.prototype.n;D.prototype.getVisible=D.prototype.b;D.prototype.setBrightness=D.prototype.t;D.prototype.setContrast=D.prototype.F;D.prototype.setHue=D.prototype.J;D.prototype.setExtent=D.prototype.p;D.prototype.setMaxResolution=D.prototype.Q;D.prototype.setMinResolution=D.prototype.V;D.prototype.setOpacity=D.prototype.q;D.prototype.setSaturation=D.prototype.ba;D.prototype.setVisible=D.prototype.ca;t("ol.layer.Group",I,OPENLAYERS); -I.prototype.getLayers=I.prototype.Yb;I.prototype.setLayers=I.prototype.r;t("ol.layer.Tile",K,OPENLAYERS);K.prototype.getPreload=K.prototype.r;K.prototype.getSource=K.prototype.a;K.prototype.setPreload=K.prototype.oa;K.prototype.getUseInterimTilesOnError=K.prototype.ea;K.prototype.setUseInterimTilesOnError=K.prototype.qa;t("ol.layer.Vector",L,OPENLAYERS);L.prototype.getSource=L.prototype.a;L.prototype.getStyle=L.prototype.Uc;L.prototype.getStyleFunction=L.prototype.Vc;L.prototype.setStyle=L.prototype.oa; -t("ol.interaction.DoubleClickZoom",sk,OPENLAYERS);t("ol.interaction.DoubleClickZoom.handleEvent",tk,OPENLAYERS);t("ol.interaction.DragAndDrop",Nv,OPENLAYERS);t("ol.interaction.DragAndDrop.handleEvent",dd,OPENLAYERS);Ov.prototype.features=Ov.prototype.features;Ov.prototype.file=Ov.prototype.file;Ov.prototype.projection=Ov.prototype.projection;El.prototype.coordinate=El.prototype.coordinate;t("ol.interaction.DragBox",Fl,OPENLAYERS);Fl.prototype.getGeometry=Fl.prototype.N; -t("ol.interaction.DragPan",Ek,OPENLAYERS);t("ol.interaction.DragRotateAndZoom",Rv,OPENLAYERS);t("ol.interaction.DragRotate",Ik,OPENLAYERS);t("ol.interaction.DragZoom",Yl,OPENLAYERS);gw.prototype.feature=gw.prototype.feature;t("ol.interaction.Draw",hw,OPENLAYERS);t("ol.interaction.Draw.handleEvent",jw,OPENLAYERS);hw.prototype.finishDrawing=hw.prototype.V;t("ol.interaction.Interaction",ok,OPENLAYERS);ok.prototype.getActive=ok.prototype.a;ok.prototype.setActive=ok.prototype.b; -t("ol.interaction.defaults",mm,OPENLAYERS);t("ol.interaction.KeyboardPan",Zl,OPENLAYERS);t("ol.interaction.KeyboardPan.handleEvent",$l,OPENLAYERS);t("ol.interaction.KeyboardZoom",am,OPENLAYERS);t("ol.interaction.KeyboardZoom.handleEvent",bm,OPENLAYERS);t("ol.interaction.Modify",uw,OPENLAYERS);t("ol.interaction.Modify.handleEvent",xw,OPENLAYERS);t("ol.interaction.MouseWheelZoom",cm,OPENLAYERS);t("ol.interaction.MouseWheelZoom.handleEvent",dm,OPENLAYERS);t("ol.interaction.PinchRotate",em,OPENLAYERS); -t("ol.interaction.PinchZoom",im,OPENLAYERS);t("ol.interaction.Pointer",Bk,OPENLAYERS);t("ol.interaction.Pointer.handleEvent",Ck,OPENLAYERS);t("ol.interaction.Select",Dw,OPENLAYERS);Dw.prototype.getFeatures=Dw.prototype.j;t("ol.interaction.Select.handleEvent",Ew,OPENLAYERS);Dw.prototype.setMap=Dw.prototype.setMap;t("ol.geom.Circle",gn,OPENLAYERS);gn.prototype.clone=gn.prototype.clone;gn.prototype.getCenter=gn.prototype.qe;gn.prototype.getExtent=gn.prototype.D;gn.prototype.getRadius=gn.prototype.vf; -gn.prototype.getType=gn.prototype.H;gn.prototype.setCenter=gn.prototype.kj;gn.prototype.setCenterAndRadius=gn.prototype.ag;gn.prototype.setRadius=gn.prototype.jl;gn.prototype.transform=gn.prototype.e;t("ol.geom.Geometry",Mk,OPENLAYERS);Mk.prototype.clone=Mk.prototype.clone;Mk.prototype.getClosestPoint=Mk.prototype.f;Mk.prototype.getExtent=Mk.prototype.D;Mk.prototype.getType=Mk.prototype.H;Mk.prototype.applyTransform=Mk.prototype.ma;Mk.prototype.intersectsExtent=Mk.prototype.ha; -Mk.prototype.translate=Mk.prototype.Aa;Mk.prototype.transform=Mk.prototype.e;t("ol.geom.GeometryCollection",jn,OPENLAYERS);jn.prototype.clone=jn.prototype.clone;jn.prototype.getExtent=jn.prototype.D;jn.prototype.getGeometries=jn.prototype.af;jn.prototype.getType=jn.prototype.H;jn.prototype.intersectsExtent=jn.prototype.ha;jn.prototype.setGeometries=jn.prototype.bg;jn.prototype.applyTransform=jn.prototype.ma;jn.prototype.translate=jn.prototype.Aa;t("ol.geom.LinearRing",hl,OPENLAYERS); -hl.prototype.clone=hl.prototype.clone;hl.prototype.getArea=hl.prototype.nj;hl.prototype.getCoordinates=hl.prototype.K;hl.prototype.getType=hl.prototype.H;hl.prototype.setCoordinates=hl.prototype.U;t("ol.geom.LineString",M,OPENLAYERS);M.prototype.appendCoordinate=M.prototype.Sg;M.prototype.clone=M.prototype.clone;M.prototype.getCoordinateAtM=M.prototype.lj;M.prototype.getCoordinates=M.prototype.K;M.prototype.getLength=M.prototype.mj;M.prototype.getType=M.prototype.H;M.prototype.intersectsExtent=M.prototype.ha; -M.prototype.setCoordinates=M.prototype.U;t("ol.geom.MultiLineString",rn,OPENLAYERS);rn.prototype.appendLineString=rn.prototype.Tg;rn.prototype.clone=rn.prototype.clone;rn.prototype.getCoordinateAtM=rn.prototype.oj;rn.prototype.getCoordinates=rn.prototype.K;rn.prototype.getLineString=rn.prototype.th;rn.prototype.getLineStrings=rn.prototype.Ec;rn.prototype.getType=rn.prototype.H;rn.prototype.intersectsExtent=rn.prototype.ha;rn.prototype.setCoordinates=rn.prototype.U;t("ol.geom.MultiPoint",un,OPENLAYERS); -un.prototype.appendPoint=un.prototype.Vg;un.prototype.clone=un.prototype.clone;un.prototype.getCoordinates=un.prototype.K;un.prototype.getPoint=un.prototype.Ch;un.prototype.getPoints=un.prototype.xd;un.prototype.getType=un.prototype.H;un.prototype.intersectsExtent=un.prototype.ha;un.prototype.setCoordinates=un.prototype.U;t("ol.geom.MultiPolygon",vn,OPENLAYERS);vn.prototype.appendPolygon=vn.prototype.Wg;vn.prototype.clone=vn.prototype.clone;vn.prototype.getArea=vn.prototype.pj; -vn.prototype.getCoordinates=vn.prototype.K;vn.prototype.getInteriorPoints=vn.prototype.qh;vn.prototype.getPolygon=vn.prototype.Dh;vn.prototype.getPolygons=vn.prototype.gd;vn.prototype.getType=vn.prototype.H;vn.prototype.intersectsExtent=vn.prototype.ha;vn.prototype.setCoordinates=vn.prototype.U;t("ol.geom.Point",jl,OPENLAYERS);jl.prototype.clone=jl.prototype.clone;jl.prototype.getCoordinates=jl.prototype.K;jl.prototype.getType=jl.prototype.H;jl.prototype.intersectsExtent=jl.prototype.ha; -jl.prototype.setCoordinates=jl.prototype.U;t("ol.geom.Polygon",H,OPENLAYERS);H.prototype.appendLinearRing=H.prototype.Ug;H.prototype.clone=H.prototype.clone;H.prototype.getArea=H.prototype.qj;H.prototype.getCoordinates=H.prototype.K;H.prototype.getInteriorPoint=H.prototype.ph;H.prototype.getLinearRingCount=H.prototype.vh;H.prototype.getLinearRing=H.prototype.uh;H.prototype.getLinearRings=H.prototype.dd;H.prototype.getType=H.prototype.H;H.prototype.intersectsExtent=H.prototype.ha; -H.prototype.setCoordinates=H.prototype.U;t("ol.geom.Polygon.circular",zl,OPENLAYERS);t("ol.geom.Polygon.fromExtent",function(b){var c=b[0],d=b[1],e=b[2];b=b[3];c=[c,d,c,b,e,b,e,d,c,d];d=new H(null);wl(d,"XY",c,[c.length]);return d},OPENLAYERS);t("ol.geom.SimpleGeometry",Ok,OPENLAYERS);Ok.prototype.getExtent=Ok.prototype.D;Ok.prototype.getFirstCoordinate=Ok.prototype.vb;Ok.prototype.getLastCoordinate=Ok.prototype.wb;Ok.prototype.getLayout=Ok.prototype.xb;Ok.prototype.applyTransform=Ok.prototype.ma; -Ok.prototype.translate=Ok.prototype.Aa;t("ol.format.Feature",up,OPENLAYERS);t("ol.format.GeoJSON",Ep,OPENLAYERS);Ep.prototype.readFeature=Ep.prototype.Nb;Ep.prototype.readFeatures=Ep.prototype.ja;Ep.prototype.readGeometry=Ep.prototype.Jc;Ep.prototype.readProjection=Ep.prototype.Ba;Ep.prototype.writeFeature=Ep.prototype.Pd;Ep.prototype.writeFeatureObject=Ep.prototype.a;Ep.prototype.writeFeatures=Ep.prototype.Qb;Ep.prototype.writeFeaturesObject=Ep.prototype.d;Ep.prototype.writeGeometry=Ep.prototype.Qc; -Ep.prototype.writeGeometryObject=Ep.prototype.e;t("ol.format.GPX",Vq,OPENLAYERS);Vq.prototype.readFeature=Vq.prototype.Nb;Vq.prototype.readFeatures=Vq.prototype.ja;Vq.prototype.readProjection=Vq.prototype.Ba;Vq.prototype.writeFeatures=Vq.prototype.Qb;Vq.prototype.writeFeaturesNode=Vq.prototype.a;t("ol.format.IGC",Fr,OPENLAYERS);Fr.prototype.readFeature=Fr.prototype.Nb;Fr.prototype.readFeatures=Fr.prototype.ja;Fr.prototype.readProjection=Fr.prototype.Ba;t("ol.format.KML",Kr,OPENLAYERS); -Kr.prototype.readFeature=Kr.prototype.Nb;Kr.prototype.readFeatures=Kr.prototype.ja;Kr.prototype.readName=Kr.prototype.Nk;Kr.prototype.readProjection=Kr.prototype.Ba;Kr.prototype.writeFeatures=Kr.prototype.Qb;Kr.prototype.writeFeaturesNode=Kr.prototype.a;t("ol.format.OSMXML",ot,OPENLAYERS);ot.prototype.readFeatures=ot.prototype.ja;ot.prototype.readProjection=ot.prototype.Ba;t("ol.format.Polyline",zt,OPENLAYERS);t("ol.format.Polyline.encodeDeltas",At,OPENLAYERS); -t("ol.format.Polyline.decodeDeltas",Ct,OPENLAYERS);t("ol.format.Polyline.encodeFloats",Bt,OPENLAYERS);t("ol.format.Polyline.decodeFloats",Dt,OPENLAYERS);zt.prototype.readFeature=zt.prototype.Nb;zt.prototype.readFeatures=zt.prototype.ja;zt.prototype.readGeometry=zt.prototype.Jc;zt.prototype.readProjection=zt.prototype.Ba;zt.prototype.writeGeometry=zt.prototype.Qc;t("ol.format.TopoJSON",Et,OPENLAYERS);Et.prototype.readFeatures=Et.prototype.ja;Et.prototype.readProjection=Et.prototype.Ba; -t("ol.format.WFS",Kt,OPENLAYERS);Kt.prototype.readFeatures=Kt.prototype.ja;Kt.prototype.readTransactionResponse=Kt.prototype.g;Kt.prototype.readFeatureCollectionMetadata=Kt.prototype.f;Kt.prototype.writeGetFeature=Kt.prototype.n;Kt.prototype.writeTransaction=Kt.prototype.j;Kt.prototype.readProjection=Kt.prototype.Ba;t("ol.format.WKT",Xt,OPENLAYERS);Xt.prototype.readFeature=Xt.prototype.Nb;Xt.prototype.readFeatures=Xt.prototype.ja;Xt.prototype.readGeometry=Xt.prototype.Jc; -Xt.prototype.writeFeature=Xt.prototype.Pd;Xt.prototype.writeFeatures=Xt.prototype.Qb;Xt.prototype.writeGeometry=Xt.prototype.Qc;t("ol.format.WMSCapabilities",ou,OPENLAYERS);ou.prototype.read=ou.prototype.a;t("ol.format.WMSGetFeatureInfo",Lu,OPENLAYERS);Lu.prototype.readFeatures=Lu.prototype.ja;t("ol.format.GML2",Uq,OPENLAYERS);t("ol.format.GML3",X,OPENLAYERS);X.prototype.writeGeometryNode=X.prototype.o;X.prototype.writeFeatures=X.prototype.Qb;X.prototype.writeFeaturesNode=X.prototype.a; -t("ol.format.GML",X,OPENLAYERS);X.prototype.writeFeatures=X.prototype.Qb;X.prototype.writeFeaturesNode=X.prototype.a;t("ol.format.GMLBase",zq,OPENLAYERS);zq.prototype.readFeatures=zq.prototype.ja;t("ol.events.condition.altKeyOnly",function(b){b=b.a;return b.c&&!b.g&&!b.d},OPENLAYERS);t("ol.events.condition.altShiftKeysOnly",uk,OPENLAYERS);t("ol.events.condition.always",dd,OPENLAYERS);t("ol.events.condition.click",function(b){return b.type==gj},OPENLAYERS);t("ol.events.condition.mouseMove",vk,OPENLAYERS); -t("ol.events.condition.never",cd,OPENLAYERS);t("ol.events.condition.singleClick",wk,OPENLAYERS);t("ol.events.condition.noModifierKeys",xk,OPENLAYERS);t("ol.events.condition.platformModifierKeyOnly",function(b){b=b.a;return!b.c&&b.g&&!b.d},OPENLAYERS);t("ol.events.condition.shiftKeyOnly",yk,OPENLAYERS);t("ol.events.condition.targetNotEditable",zk,OPENLAYERS);t("ol.events.condition.mouseOnly",Ak,OPENLAYERS);t("ol.dom.Input",rp,OPENLAYERS);rp.prototype.getChecked=rp.prototype.a; -rp.prototype.getValue=rp.prototype.b;rp.prototype.setValue=rp.prototype.e;rp.prototype.setChecked=rp.prototype.d;t("ol.control.Attribution",ah,OPENLAYERS);t("ol.control.Attribution.render",bh,OPENLAYERS);ah.prototype.getCollapsible=ah.prototype.bj;ah.prototype.setCollapsible=ah.prototype.ej;ah.prototype.setCollapsed=ah.prototype.dj;ah.prototype.getCollapsed=ah.prototype.aj;t("ol.control.Control",$g,OPENLAYERS);$g.prototype.getMap=$g.prototype.d;$g.prototype.setMap=$g.prototype.setMap; -t("ol.control.defaults",gh,OPENLAYERS);t("ol.control.FullScreen",lh,OPENLAYERS);t("ol.control.MousePosition",mh,OPENLAYERS);t("ol.control.MousePosition.render",nh,OPENLAYERS);mh.prototype.getCoordinateFormat=mh.prototype.g;mh.prototype.getProjection=mh.prototype.p;mh.prototype.setMap=mh.prototype.setMap;mh.prototype.setCoordinateFormat=mh.prototype.r;mh.prototype.setProjection=mh.prototype.q;t("ol.control.OverviewMap",Ro,OPENLAYERS);Ro.prototype.setMap=Ro.prototype.setMap; -t("ol.control.OverviewMap.render",So,OPENLAYERS);Ro.prototype.getCollapsible=Ro.prototype.gj;Ro.prototype.setCollapsible=Ro.prototype.jj;Ro.prototype.setCollapsed=Ro.prototype.ij;Ro.prototype.getCollapsed=Ro.prototype.fj;t("ol.control.Rotate",dh,OPENLAYERS);t("ol.control.Rotate.render",eh,OPENLAYERS);t("ol.control.ScaleLine",Xo,OPENLAYERS);Xo.prototype.getUnits=Xo.prototype.j;t("ol.control.ScaleLine.render",Yo,OPENLAYERS);Xo.prototype.setUnits=Xo.prototype.p;t("ol.control.Zoom",fh,OPENLAYERS); -t("ol.control.ZoomSlider",lp,OPENLAYERS);t("ol.control.ZoomSlider.render",np,OPENLAYERS);t("ol.control.ZoomToExtent",qp,OPENLAYERS);t("ol.color.asArray",ug,OPENLAYERS);t("ol.color.asString",wg,OPENLAYERS);td.prototype.changed=td.prototype.l;td.prototype.getRevision=td.prototype.A;td.prototype.on=td.prototype.u;td.prototype.once=td.prototype.B;td.prototype.un=td.prototype.v;td.prototype.unByKey=td.prototype.C;B.prototype.bindTo=B.prototype.O;B.prototype.get=B.prototype.get;B.prototype.getKeys=B.prototype.I; -B.prototype.getProperties=B.prototype.L;B.prototype.set=B.prototype.set;B.prototype.setProperties=B.prototype.G;B.prototype.unbind=B.prototype.P;B.prototype.unbindAll=B.prototype.R;B.prototype.changed=B.prototype.l;B.prototype.getRevision=B.prototype.A;B.prototype.on=B.prototype.u;B.prototype.once=B.prototype.B;B.prototype.un=B.prototype.v;B.prototype.unByKey=B.prototype.C;Q.prototype.bindTo=Q.prototype.O;Q.prototype.get=Q.prototype.get;Q.prototype.getKeys=Q.prototype.I; -Q.prototype.getProperties=Q.prototype.L;Q.prototype.set=Q.prototype.set;Q.prototype.setProperties=Q.prototype.G;Q.prototype.unbind=Q.prototype.P;Q.prototype.unbindAll=Q.prototype.R;Q.prototype.changed=Q.prototype.l;Q.prototype.getRevision=Q.prototype.A;Q.prototype.on=Q.prototype.u;Q.prototype.once=Q.prototype.B;Q.prototype.un=Q.prototype.v;Q.prototype.unByKey=Q.prototype.C;R.prototype.bindTo=R.prototype.O;R.prototype.get=R.prototype.get;R.prototype.getKeys=R.prototype.I; -R.prototype.getProperties=R.prototype.L;R.prototype.set=R.prototype.set;R.prototype.setProperties=R.prototype.G;R.prototype.unbind=R.prototype.P;R.prototype.unbindAll=R.prototype.R;R.prototype.changed=R.prototype.l;R.prototype.getRevision=R.prototype.A;R.prototype.on=R.prototype.u;R.prototype.once=R.prototype.B;R.prototype.un=R.prototype.v;R.prototype.unByKey=R.prototype.C;Z.prototype.bindTo=Z.prototype.O;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.I; -Z.prototype.getProperties=Z.prototype.L;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.G;Z.prototype.unbind=Z.prototype.P;Z.prototype.unbindAll=Z.prototype.R;Z.prototype.changed=Z.prototype.l;Z.prototype.getRevision=Z.prototype.A;Z.prototype.on=Z.prototype.u;Z.prototype.once=Z.prototype.B;Z.prototype.un=Z.prototype.v;Z.prototype.unByKey=Z.prototype.C;Yu.prototype.getTileCoord=Yu.prototype.f;O.prototype.bindTo=O.prototype.O;O.prototype.get=O.prototype.get; -O.prototype.getKeys=O.prototype.I;O.prototype.getProperties=O.prototype.L;O.prototype.set=O.prototype.set;O.prototype.setProperties=O.prototype.G;O.prototype.unbind=O.prototype.P;O.prototype.unbindAll=O.prototype.R;O.prototype.changed=O.prototype.l;O.prototype.getRevision=O.prototype.A;O.prototype.on=O.prototype.u;O.prototype.once=O.prototype.B;O.prototype.un=O.prototype.v;O.prototype.unByKey=O.prototype.C;cj.prototype.map=cj.prototype.map;cj.prototype.frameState=cj.prototype.frameState; -dj.prototype.originalEvent=dj.prototype.originalEvent;dj.prototype.pixel=dj.prototype.pixel;dj.prototype.coordinate=dj.prototype.coordinate;dj.prototype.preventDefault=dj.prototype.preventDefault;dj.prototype.stopPropagation=dj.prototype.lb;dj.prototype.map=dj.prototype.map;dj.prototype.frameState=dj.prototype.frameState;P.prototype.bindTo=P.prototype.O;P.prototype.get=P.prototype.get;P.prototype.getKeys=P.prototype.I;P.prototype.getProperties=P.prototype.L;P.prototype.set=P.prototype.set; -P.prototype.setProperties=P.prototype.G;P.prototype.unbind=P.prototype.P;P.prototype.unbindAll=P.prototype.R;P.prototype.changed=P.prototype.l;P.prototype.getRevision=P.prototype.A;P.prototype.on=P.prototype.u;P.prototype.once=P.prototype.B;P.prototype.un=P.prototype.v;P.prototype.unByKey=P.prototype.C;A.prototype.bindTo=A.prototype.O;A.prototype.get=A.prototype.get;A.prototype.getKeys=A.prototype.I;A.prototype.getProperties=A.prototype.L;A.prototype.set=A.prototype.set; -A.prototype.setProperties=A.prototype.G;A.prototype.unbind=A.prototype.P;A.prototype.unbindAll=A.prototype.R;A.prototype.changed=A.prototype.l;A.prototype.getRevision=A.prototype.A;A.prototype.on=A.prototype.u;A.prototype.once=A.prototype.B;A.prototype.un=A.prototype.v;A.prototype.unByKey=A.prototype.C;Ay.prototype.getMaxZoom=Ay.prototype.ed;Ay.prototype.getMinZoom=Ay.prototype.fd;Ay.prototype.getOrigin=Ay.prototype.Lb;Ay.prototype.getResolution=Ay.prototype.ka;Ay.prototype.getResolutions=Ay.prototype.Fd; -Ay.prototype.getTileCoordForCoordAndResolution=Ay.prototype.Vb;Ay.prototype.getTileCoordForCoordAndZ=Ay.prototype.Fc;Ay.prototype.getTileSize=Ay.prototype.sa;fx.prototype.getMaxZoom=fx.prototype.ed;fx.prototype.getMinZoom=fx.prototype.fd;fx.prototype.getOrigin=fx.prototype.Lb;fx.prototype.getResolution=fx.prototype.ka;fx.prototype.getResolutions=fx.prototype.Fd;fx.prototype.getTileCoordForCoordAndResolution=fx.prototype.Vb;fx.prototype.getTileCoordForCoordAndZ=fx.prototype.Fc; -fx.prototype.getTileSize=fx.prototype.sa;Dy.prototype.getMaxZoom=Dy.prototype.ed;Dy.prototype.getMinZoom=Dy.prototype.fd;Dy.prototype.getOrigin=Dy.prototype.Lb;Dy.prototype.getResolution=Dy.prototype.ka;Dy.prototype.getResolutions=Dy.prototype.Fd;Dy.prototype.getTileCoordForCoordAndResolution=Dy.prototype.Vb;Dy.prototype.getTileCoordForCoordAndZ=Dy.prototype.Fc;Dy.prototype.getTileSize=Dy.prototype.sa;Sl.prototype.getOpacity=Sl.prototype.Ad;Sl.prototype.getRotateWithView=Sl.prototype.hd; -Sl.prototype.getRotation=Sl.prototype.Bd;Sl.prototype.getScale=Sl.prototype.Cd;Sl.prototype.getSnapToPixel=Sl.prototype.jd;Sl.prototype.setRotation=Sl.prototype.Dd;Sl.prototype.setScale=Sl.prototype.Ed;Sj.prototype.getOpacity=Sj.prototype.Ad;Sj.prototype.getRotateWithView=Sj.prototype.hd;Sj.prototype.getRotation=Sj.prototype.Bd;Sj.prototype.getScale=Sj.prototype.Cd;Sj.prototype.getSnapToPixel=Sj.prototype.jd;Sj.prototype.setRotation=Sj.prototype.Dd;Sj.prototype.setScale=Sj.prototype.Ed; -Ky.prototype.getOpacity=Ky.prototype.Ad;Ky.prototype.getRotateWithView=Ky.prototype.hd;Ky.prototype.getRotation=Ky.prototype.Bd;Ky.prototype.getScale=Ky.prototype.Cd;Ky.prototype.getSnapToPixel=Ky.prototype.jd;Ky.prototype.setRotation=Ky.prototype.Dd;Ky.prototype.setScale=Ky.prototype.Ed;qj.prototype.changed=qj.prototype.l;qj.prototype.getRevision=qj.prototype.A;qj.prototype.on=qj.prototype.u;qj.prototype.once=qj.prototype.B;qj.prototype.un=qj.prototype.v;qj.prototype.unByKey=qj.prototype.C; -Fj.prototype.getAttributions=Fj.prototype.Y;Fj.prototype.getLogo=Fj.prototype.W;Fj.prototype.getProjection=Fj.prototype.Z;Fj.prototype.getState=Fj.prototype.$;Fj.prototype.changed=Fj.prototype.l;Fj.prototype.getRevision=Fj.prototype.A;Fj.prototype.on=Fj.prototype.u;Fj.prototype.once=Fj.prototype.B;Fj.prototype.un=Fj.prototype.v;Fj.prototype.unByKey=Fj.prototype.C;dx.prototype.getTileGrid=dx.prototype.za;dx.prototype.getAttributions=dx.prototype.Y;dx.prototype.getLogo=dx.prototype.W; -dx.prototype.getProjection=dx.prototype.Z;dx.prototype.getState=dx.prototype.$;dx.prototype.changed=dx.prototype.l;dx.prototype.getRevision=dx.prototype.A;dx.prototype.on=dx.prototype.u;dx.prototype.once=dx.prototype.B;dx.prototype.un=dx.prototype.v;dx.prototype.unByKey=dx.prototype.C;gx.prototype.getTileLoadFunction=gx.prototype.ib;gx.prototype.getTileUrlFunction=gx.prototype.jb;gx.prototype.setTileLoadFunction=gx.prototype.ob;gx.prototype.setTileUrlFunction=gx.prototype.pa; -gx.prototype.getTileGrid=gx.prototype.za;gx.prototype.getAttributions=gx.prototype.Y;gx.prototype.getLogo=gx.prototype.W;gx.prototype.getProjection=gx.prototype.Z;gx.prototype.getState=gx.prototype.$;gx.prototype.changed=gx.prototype.l;gx.prototype.getRevision=gx.prototype.A;gx.prototype.on=gx.prototype.u;gx.prototype.once=gx.prototype.B;gx.prototype.un=gx.prototype.v;gx.prototype.unByKey=gx.prototype.C;aw.prototype.getAttributions=aw.prototype.Y;aw.prototype.getLogo=aw.prototype.W; -aw.prototype.getProjection=aw.prototype.Z;aw.prototype.getState=aw.prototype.$;aw.prototype.changed=aw.prototype.l;aw.prototype.getRevision=aw.prototype.A;aw.prototype.on=aw.prototype.u;aw.prototype.once=aw.prototype.B;aw.prototype.un=aw.prototype.v;aw.prototype.unByKey=aw.prototype.C;ix.prototype.addFeature=ix.prototype.Pa;ix.prototype.addFeatures=ix.prototype.ya;ix.prototype.clear=ix.prototype.clear;ix.prototype.forEachFeature=ix.prototype.Xa;ix.prototype.forEachFeatureInExtent=ix.prototype.ra; -ix.prototype.forEachFeatureIntersectingExtent=ix.prototype.Fa;ix.prototype.getFeatures=ix.prototype.va;ix.prototype.getFeaturesAtCoordinate=ix.prototype.Ia;ix.prototype.getClosestFeatureToCoordinate=ix.prototype.Ya;ix.prototype.getExtent=ix.prototype.D;ix.prototype.getFeatureById=ix.prototype.Ha;ix.prototype.removeFeature=ix.prototype.$a;ix.prototype.getAttributions=ix.prototype.Y;ix.prototype.getLogo=ix.prototype.W;ix.prototype.getProjection=ix.prototype.Z;ix.prototype.getState=ix.prototype.$; -ix.prototype.changed=ix.prototype.l;ix.prototype.getRevision=ix.prototype.A;ix.prototype.on=ix.prototype.u;ix.prototype.once=ix.prototype.B;ix.prototype.un=ix.prototype.v;ix.prototype.unByKey=ix.prototype.C;Hx.prototype.addFeature=Hx.prototype.Pa;Hx.prototype.addFeatures=Hx.prototype.ya;Hx.prototype.clear=Hx.prototype.clear;Hx.prototype.forEachFeature=Hx.prototype.Xa;Hx.prototype.forEachFeatureInExtent=Hx.prototype.ra;Hx.prototype.forEachFeatureIntersectingExtent=Hx.prototype.Fa; -Hx.prototype.getFeatures=Hx.prototype.va;Hx.prototype.getFeaturesAtCoordinate=Hx.prototype.Ia;Hx.prototype.getClosestFeatureToCoordinate=Hx.prototype.Ya;Hx.prototype.getExtent=Hx.prototype.D;Hx.prototype.getFeatureById=Hx.prototype.Ha;Hx.prototype.removeFeature=Hx.prototype.$a;Hx.prototype.getAttributions=Hx.prototype.Y;Hx.prototype.getLogo=Hx.prototype.W;Hx.prototype.getProjection=Hx.prototype.Z;Hx.prototype.getState=Hx.prototype.$;Hx.prototype.changed=Hx.prototype.l;Hx.prototype.getRevision=Hx.prototype.A; -Hx.prototype.on=Hx.prototype.u;Hx.prototype.once=Hx.prototype.B;Hx.prototype.un=Hx.prototype.v;Hx.prototype.unByKey=Hx.prototype.C;Jx.prototype.readFeatures=Jx.prototype.a;Jx.prototype.addFeature=Jx.prototype.Pa;Jx.prototype.addFeatures=Jx.prototype.ya;Jx.prototype.clear=Jx.prototype.clear;Jx.prototype.forEachFeature=Jx.prototype.Xa;Jx.prototype.forEachFeatureInExtent=Jx.prototype.ra;Jx.prototype.forEachFeatureIntersectingExtent=Jx.prototype.Fa;Jx.prototype.getFeatures=Jx.prototype.va; -Jx.prototype.getFeaturesAtCoordinate=Jx.prototype.Ia;Jx.prototype.getClosestFeatureToCoordinate=Jx.prototype.Ya;Jx.prototype.getExtent=Jx.prototype.D;Jx.prototype.getFeatureById=Jx.prototype.Ha;Jx.prototype.removeFeature=Jx.prototype.$a;Jx.prototype.getAttributions=Jx.prototype.Y;Jx.prototype.getLogo=Jx.prototype.W;Jx.prototype.getProjection=Jx.prototype.Z;Jx.prototype.getState=Jx.prototype.$;Jx.prototype.changed=Jx.prototype.l;Jx.prototype.getRevision=Jx.prototype.A;Jx.prototype.on=Jx.prototype.u; -Jx.prototype.once=Jx.prototype.B;Jx.prototype.un=Jx.prototype.v;Jx.prototype.unByKey=Jx.prototype.C;Kx.prototype.readFeatures=Kx.prototype.a;Kx.prototype.addFeature=Kx.prototype.Pa;Kx.prototype.addFeatures=Kx.prototype.ya;Kx.prototype.clear=Kx.prototype.clear;Kx.prototype.forEachFeature=Kx.prototype.Xa;Kx.prototype.forEachFeatureInExtent=Kx.prototype.ra;Kx.prototype.forEachFeatureIntersectingExtent=Kx.prototype.Fa;Kx.prototype.getFeatures=Kx.prototype.va;Kx.prototype.getFeaturesAtCoordinate=Kx.prototype.Ia; -Kx.prototype.getClosestFeatureToCoordinate=Kx.prototype.Ya;Kx.prototype.getExtent=Kx.prototype.D;Kx.prototype.getFeatureById=Kx.prototype.Ha;Kx.prototype.removeFeature=Kx.prototype.$a;Kx.prototype.getAttributions=Kx.prototype.Y;Kx.prototype.getLogo=Kx.prototype.W;Kx.prototype.getProjection=Kx.prototype.Z;Kx.prototype.getState=Kx.prototype.$;Kx.prototype.changed=Kx.prototype.l;Kx.prototype.getRevision=Kx.prototype.A;Kx.prototype.on=Kx.prototype.u;Kx.prototype.once=Kx.prototype.B;Kx.prototype.un=Kx.prototype.v; -Kx.prototype.unByKey=Kx.prototype.C;Lx.prototype.readFeatures=Lx.prototype.a;Lx.prototype.addFeature=Lx.prototype.Pa;Lx.prototype.addFeatures=Lx.prototype.ya;Lx.prototype.clear=Lx.prototype.clear;Lx.prototype.forEachFeature=Lx.prototype.Xa;Lx.prototype.forEachFeatureInExtent=Lx.prototype.ra;Lx.prototype.forEachFeatureIntersectingExtent=Lx.prototype.Fa;Lx.prototype.getFeatures=Lx.prototype.va;Lx.prototype.getFeaturesAtCoordinate=Lx.prototype.Ia;Lx.prototype.getClosestFeatureToCoordinate=Lx.prototype.Ya; -Lx.prototype.getExtent=Lx.prototype.D;Lx.prototype.getFeatureById=Lx.prototype.Ha;Lx.prototype.removeFeature=Lx.prototype.$a;Lx.prototype.getAttributions=Lx.prototype.Y;Lx.prototype.getLogo=Lx.prototype.W;Lx.prototype.getProjection=Lx.prototype.Z;Lx.prototype.getState=Lx.prototype.$;Lx.prototype.changed=Lx.prototype.l;Lx.prototype.getRevision=Lx.prototype.A;Lx.prototype.on=Lx.prototype.u;Lx.prototype.once=Lx.prototype.B;Lx.prototype.un=Lx.prototype.v;Lx.prototype.unByKey=Lx.prototype.C; -Mx.prototype.readFeatures=Mx.prototype.a;Mx.prototype.addFeature=Mx.prototype.Pa;Mx.prototype.addFeatures=Mx.prototype.ya;Mx.prototype.clear=Mx.prototype.clear;Mx.prototype.forEachFeature=Mx.prototype.Xa;Mx.prototype.forEachFeatureInExtent=Mx.prototype.ra;Mx.prototype.forEachFeatureIntersectingExtent=Mx.prototype.Fa;Mx.prototype.getFeatures=Mx.prototype.va;Mx.prototype.getFeaturesAtCoordinate=Mx.prototype.Ia;Mx.prototype.getClosestFeatureToCoordinate=Mx.prototype.Ya;Mx.prototype.getExtent=Mx.prototype.D; -Mx.prototype.getFeatureById=Mx.prototype.Ha;Mx.prototype.removeFeature=Mx.prototype.$a;Mx.prototype.getAttributions=Mx.prototype.Y;Mx.prototype.getLogo=Mx.prototype.W;Mx.prototype.getProjection=Mx.prototype.Z;Mx.prototype.getState=Mx.prototype.$;Mx.prototype.changed=Mx.prototype.l;Mx.prototype.getRevision=Mx.prototype.A;Mx.prototype.on=Mx.prototype.u;Mx.prototype.once=Mx.prototype.B;Mx.prototype.un=Mx.prototype.v;Mx.prototype.unByKey=Mx.prototype.C;Nx.prototype.getAttributions=Nx.prototype.Y; -Nx.prototype.getLogo=Nx.prototype.W;Nx.prototype.getProjection=Nx.prototype.Z;Nx.prototype.getState=Nx.prototype.$;Nx.prototype.changed=Nx.prototype.l;Nx.prototype.getRevision=Nx.prototype.A;Nx.prototype.on=Nx.prototype.u;Nx.prototype.once=Nx.prototype.B;Nx.prototype.un=Nx.prototype.v;Nx.prototype.unByKey=Nx.prototype.C;Qx.prototype.getAttributions=Qx.prototype.Y;Qx.prototype.getLogo=Qx.prototype.W;Qx.prototype.getProjection=Qx.prototype.Z;Qx.prototype.getState=Qx.prototype.$; -Qx.prototype.changed=Qx.prototype.l;Qx.prototype.getRevision=Qx.prototype.A;Qx.prototype.on=Qx.prototype.u;Qx.prototype.once=Qx.prototype.B;Qx.prototype.un=Qx.prototype.v;Qx.prototype.unByKey=Qx.prototype.C;Rx.prototype.getAttributions=Rx.prototype.Y;Rx.prototype.getLogo=Rx.prototype.W;Rx.prototype.getProjection=Rx.prototype.Z;Rx.prototype.getState=Rx.prototype.$;Rx.prototype.changed=Rx.prototype.l;Rx.prototype.getRevision=Rx.prototype.A;Rx.prototype.on=Rx.prototype.u;Rx.prototype.once=Rx.prototype.B; -Rx.prototype.un=Rx.prototype.v;Rx.prototype.unByKey=Rx.prototype.C;Sx.prototype.getAttributions=Sx.prototype.Y;Sx.prototype.getLogo=Sx.prototype.W;Sx.prototype.getProjection=Sx.prototype.Z;Sx.prototype.getState=Sx.prototype.$;Sx.prototype.changed=Sx.prototype.l;Sx.prototype.getRevision=Sx.prototype.A;Sx.prototype.on=Sx.prototype.u;Sx.prototype.once=Sx.prototype.B;Sx.prototype.un=Sx.prototype.v;Sx.prototype.unByKey=Sx.prototype.C;Tx.prototype.getAttributions=Tx.prototype.Y;Tx.prototype.getLogo=Tx.prototype.W; -Tx.prototype.getProjection=Tx.prototype.Z;Tx.prototype.getState=Tx.prototype.$;Tx.prototype.changed=Tx.prototype.l;Tx.prototype.getRevision=Tx.prototype.A;Tx.prototype.on=Tx.prototype.u;Tx.prototype.once=Tx.prototype.B;Tx.prototype.un=Tx.prototype.v;Tx.prototype.unByKey=Tx.prototype.C;Vx.prototype.getAttributions=Vx.prototype.Y;Vx.prototype.getLogo=Vx.prototype.W;Vx.prototype.getProjection=Vx.prototype.Z;Vx.prototype.getState=Vx.prototype.$;Vx.prototype.changed=Vx.prototype.l; -Vx.prototype.getRevision=Vx.prototype.A;Vx.prototype.on=Vx.prototype.u;Vx.prototype.once=Vx.prototype.B;Vx.prototype.un=Vx.prototype.v;Vx.prototype.unByKey=Vx.prototype.C;Zx.prototype.readFeatures=Zx.prototype.a;Zx.prototype.addFeature=Zx.prototype.Pa;Zx.prototype.addFeatures=Zx.prototype.ya;Zx.prototype.clear=Zx.prototype.clear;Zx.prototype.forEachFeature=Zx.prototype.Xa;Zx.prototype.forEachFeatureInExtent=Zx.prototype.ra;Zx.prototype.forEachFeatureIntersectingExtent=Zx.prototype.Fa; -Zx.prototype.getFeatures=Zx.prototype.va;Zx.prototype.getFeaturesAtCoordinate=Zx.prototype.Ia;Zx.prototype.getClosestFeatureToCoordinate=Zx.prototype.Ya;Zx.prototype.getExtent=Zx.prototype.D;Zx.prototype.getFeatureById=Zx.prototype.Ha;Zx.prototype.removeFeature=Zx.prototype.$a;Zx.prototype.getAttributions=Zx.prototype.Y;Zx.prototype.getLogo=Zx.prototype.W;Zx.prototype.getProjection=Zx.prototype.Z;Zx.prototype.getState=Zx.prototype.$;Zx.prototype.changed=Zx.prototype.l;Zx.prototype.getRevision=Zx.prototype.A; -Zx.prototype.on=Zx.prototype.u;Zx.prototype.once=Zx.prototype.B;Zx.prototype.un=Zx.prototype.v;Zx.prototype.unByKey=Zx.prototype.C;$x.prototype.getTileLoadFunction=$x.prototype.ib;$x.prototype.getTileUrlFunction=$x.prototype.jb;$x.prototype.setTileLoadFunction=$x.prototype.ob;$x.prototype.getTileGrid=$x.prototype.za;$x.prototype.getAttributions=$x.prototype.Y;$x.prototype.getLogo=$x.prototype.W;$x.prototype.getProjection=$x.prototype.Z;$x.prototype.getState=$x.prototype.$;$x.prototype.changed=$x.prototype.l; -$x.prototype.getRevision=$x.prototype.A;$x.prototype.on=$x.prototype.u;$x.prototype.once=$x.prototype.B;$x.prototype.un=$x.prototype.v;$x.prototype.unByKey=$x.prototype.C;cy.prototype.setTileUrlFunction=cy.prototype.pa;cy.prototype.setUrl=cy.prototype.a;cy.prototype.getTileLoadFunction=cy.prototype.ib;cy.prototype.getTileUrlFunction=cy.prototype.jb;cy.prototype.setTileLoadFunction=cy.prototype.ob;cy.prototype.getTileGrid=cy.prototype.za;cy.prototype.getAttributions=cy.prototype.Y; -cy.prototype.getLogo=cy.prototype.W;cy.prototype.getProjection=cy.prototype.Z;cy.prototype.getState=cy.prototype.$;cy.prototype.changed=cy.prototype.l;cy.prototype.getRevision=cy.prototype.A;cy.prototype.on=cy.prototype.u;cy.prototype.once=cy.prototype.B;cy.prototype.un=cy.prototype.v;cy.prototype.unByKey=cy.prototype.C;ay.prototype.setTileUrlFunction=ay.prototype.pa;ay.prototype.setUrl=ay.prototype.a;ay.prototype.getTileLoadFunction=ay.prototype.ib;ay.prototype.getTileUrlFunction=ay.prototype.jb; -ay.prototype.setTileLoadFunction=ay.prototype.ob;ay.prototype.getTileGrid=ay.prototype.za;ay.prototype.getAttributions=ay.prototype.Y;ay.prototype.getLogo=ay.prototype.W;ay.prototype.getProjection=ay.prototype.Z;ay.prototype.getState=ay.prototype.$;ay.prototype.changed=ay.prototype.l;ay.prototype.getRevision=ay.prototype.A;ay.prototype.on=ay.prototype.u;ay.prototype.once=ay.prototype.B;ay.prototype.un=ay.prototype.v;ay.prototype.unByKey=ay.prototype.C;fy.prototype.readFeatures=fy.prototype.a; -fy.prototype.addFeature=fy.prototype.Pa;fy.prototype.addFeatures=fy.prototype.ya;fy.prototype.clear=fy.prototype.clear;fy.prototype.forEachFeature=fy.prototype.Xa;fy.prototype.forEachFeatureInExtent=fy.prototype.ra;fy.prototype.forEachFeatureIntersectingExtent=fy.prototype.Fa;fy.prototype.getFeatures=fy.prototype.va;fy.prototype.getFeaturesAtCoordinate=fy.prototype.Ia;fy.prototype.getClosestFeatureToCoordinate=fy.prototype.Ya;fy.prototype.getExtent=fy.prototype.D;fy.prototype.getFeatureById=fy.prototype.Ha; -fy.prototype.removeFeature=fy.prototype.$a;fy.prototype.getAttributions=fy.prototype.Y;fy.prototype.getLogo=fy.prototype.W;fy.prototype.getProjection=fy.prototype.Z;fy.prototype.getState=fy.prototype.$;fy.prototype.changed=fy.prototype.l;fy.prototype.getRevision=fy.prototype.A;fy.prototype.on=fy.prototype.u;fy.prototype.once=fy.prototype.B;fy.prototype.un=fy.prototype.v;fy.prototype.unByKey=fy.prototype.C;gy.prototype.addFeature=gy.prototype.Pa;gy.prototype.addFeatures=gy.prototype.ya; -gy.prototype.forEachFeature=gy.prototype.Xa;gy.prototype.forEachFeatureInExtent=gy.prototype.ra;gy.prototype.forEachFeatureIntersectingExtent=gy.prototype.Fa;gy.prototype.getFeatures=gy.prototype.va;gy.prototype.getFeaturesAtCoordinate=gy.prototype.Ia;gy.prototype.getClosestFeatureToCoordinate=gy.prototype.Ya;gy.prototype.getExtent=gy.prototype.D;gy.prototype.getFeatureById=gy.prototype.Ha;gy.prototype.removeFeature=gy.prototype.$a;gy.prototype.getAttributions=gy.prototype.Y; -gy.prototype.getLogo=gy.prototype.W;gy.prototype.getProjection=gy.prototype.Z;gy.prototype.getState=gy.prototype.$;gy.prototype.changed=gy.prototype.l;gy.prototype.getRevision=gy.prototype.A;gy.prototype.on=gy.prototype.u;gy.prototype.once=gy.prototype.B;gy.prototype.un=gy.prototype.v;gy.prototype.unByKey=gy.prototype.C;jy.prototype.setTileUrlFunction=jy.prototype.pa;jy.prototype.setUrl=jy.prototype.a;jy.prototype.getTileLoadFunction=jy.prototype.ib;jy.prototype.getTileUrlFunction=jy.prototype.jb; -jy.prototype.setTileLoadFunction=jy.prototype.ob;jy.prototype.getTileGrid=jy.prototype.za;jy.prototype.getAttributions=jy.prototype.Y;jy.prototype.getLogo=jy.prototype.W;jy.prototype.getProjection=jy.prototype.Z;jy.prototype.getState=jy.prototype.$;jy.prototype.changed=jy.prototype.l;jy.prototype.getRevision=jy.prototype.A;jy.prototype.on=jy.prototype.u;jy.prototype.once=jy.prototype.B;jy.prototype.un=jy.prototype.v;jy.prototype.unByKey=jy.prototype.C;my.prototype.getTileGrid=my.prototype.za; -my.prototype.getAttributions=my.prototype.Y;my.prototype.getLogo=my.prototype.W;my.prototype.getProjection=my.prototype.Z;my.prototype.getState=my.prototype.$;my.prototype.changed=my.prototype.l;my.prototype.getRevision=my.prototype.A;my.prototype.on=my.prototype.u;my.prototype.once=my.prototype.B;my.prototype.un=my.prototype.v;my.prototype.unByKey=my.prototype.C;ny.prototype.getTileLoadFunction=ny.prototype.ib;ny.prototype.getTileUrlFunction=ny.prototype.jb;ny.prototype.setTileLoadFunction=ny.prototype.ob; -ny.prototype.setTileUrlFunction=ny.prototype.pa;ny.prototype.getTileGrid=ny.prototype.za;ny.prototype.getAttributions=ny.prototype.Y;ny.prototype.getLogo=ny.prototype.W;ny.prototype.getProjection=ny.prototype.Z;ny.prototype.getState=ny.prototype.$;ny.prototype.changed=ny.prototype.l;ny.prototype.getRevision=ny.prototype.A;ny.prototype.on=ny.prototype.u;ny.prototype.once=ny.prototype.B;ny.prototype.un=ny.prototype.v;ny.prototype.unByKey=ny.prototype.C;oy.prototype.getTileGrid=oy.prototype.za; -oy.prototype.getAttributions=oy.prototype.Y;oy.prototype.getLogo=oy.prototype.W;oy.prototype.getProjection=oy.prototype.Z;oy.prototype.getState=oy.prototype.$;oy.prototype.changed=oy.prototype.l;oy.prototype.getRevision=oy.prototype.A;oy.prototype.on=oy.prototype.u;oy.prototype.once=oy.prototype.B;oy.prototype.un=oy.prototype.v;oy.prototype.unByKey=oy.prototype.C;ty.prototype.readFeatures=ty.prototype.a;ty.prototype.forEachFeatureIntersectingExtent=ty.prototype.Fa; -ty.prototype.getFeaturesAtCoordinate=ty.prototype.Ia;ty.prototype.getFeatureById=ty.prototype.Ha;ty.prototype.getAttributions=ty.prototype.Y;ty.prototype.getLogo=ty.prototype.W;ty.prototype.getProjection=ty.prototype.Z;ty.prototype.getState=ty.prototype.$;ty.prototype.changed=ty.prototype.l;ty.prototype.getRevision=ty.prototype.A;ty.prototype.on=ty.prototype.u;ty.prototype.once=ty.prototype.B;ty.prototype.un=ty.prototype.v;ty.prototype.unByKey=ty.prototype.C;vy.prototype.getTileLoadFunction=vy.prototype.ib; -vy.prototype.getTileUrlFunction=vy.prototype.jb;vy.prototype.setTileLoadFunction=vy.prototype.ob;vy.prototype.setTileUrlFunction=vy.prototype.pa;vy.prototype.getTileGrid=vy.prototype.za;vy.prototype.getAttributions=vy.prototype.Y;vy.prototype.getLogo=vy.prototype.W;vy.prototype.getProjection=vy.prototype.Z;vy.prototype.getState=vy.prototype.$;vy.prototype.changed=vy.prototype.l;vy.prototype.getRevision=vy.prototype.A;vy.prototype.on=vy.prototype.u;vy.prototype.once=vy.prototype.B; -vy.prototype.un=vy.prototype.v;vy.prototype.unByKey=vy.prototype.C;zy.prototype.readFeatures=zy.prototype.a;zy.prototype.addFeature=zy.prototype.Pa;zy.prototype.addFeatures=zy.prototype.ya;zy.prototype.clear=zy.prototype.clear;zy.prototype.forEachFeature=zy.prototype.Xa;zy.prototype.forEachFeatureInExtent=zy.prototype.ra;zy.prototype.forEachFeatureIntersectingExtent=zy.prototype.Fa;zy.prototype.getFeatures=zy.prototype.va;zy.prototype.getFeaturesAtCoordinate=zy.prototype.Ia; -zy.prototype.getClosestFeatureToCoordinate=zy.prototype.Ya;zy.prototype.getExtent=zy.prototype.D;zy.prototype.getFeatureById=zy.prototype.Ha;zy.prototype.removeFeature=zy.prototype.$a;zy.prototype.getAttributions=zy.prototype.Y;zy.prototype.getLogo=zy.prototype.W;zy.prototype.getProjection=zy.prototype.Z;zy.prototype.getState=zy.prototype.$;zy.prototype.changed=zy.prototype.l;zy.prototype.getRevision=zy.prototype.A;zy.prototype.on=zy.prototype.u;zy.prototype.once=zy.prototype.B;zy.prototype.un=zy.prototype.v; -zy.prototype.unByKey=zy.prototype.C;By.prototype.getTileLoadFunction=By.prototype.ib;By.prototype.getTileUrlFunction=By.prototype.jb;By.prototype.setTileLoadFunction=By.prototype.ob;By.prototype.setTileUrlFunction=By.prototype.pa;By.prototype.getTileGrid=By.prototype.za;By.prototype.getAttributions=By.prototype.Y;By.prototype.getLogo=By.prototype.W;By.prototype.getProjection=By.prototype.Z;By.prototype.getState=By.prototype.$;By.prototype.changed=By.prototype.l;By.prototype.getRevision=By.prototype.A; -By.prototype.on=By.prototype.u;By.prototype.once=By.prototype.B;By.prototype.un=By.prototype.v;By.prototype.unByKey=By.prototype.C;Ey.prototype.getTileLoadFunction=Ey.prototype.ib;Ey.prototype.getTileUrlFunction=Ey.prototype.jb;Ey.prototype.setTileLoadFunction=Ey.prototype.ob;Ey.prototype.setTileUrlFunction=Ey.prototype.pa;Ey.prototype.getTileGrid=Ey.prototype.za;Ey.prototype.getAttributions=Ey.prototype.Y;Ey.prototype.getLogo=Ey.prototype.W;Ey.prototype.getProjection=Ey.prototype.Z; -Ey.prototype.getState=Ey.prototype.$;Ey.prototype.changed=Ey.prototype.l;Ey.prototype.getRevision=Ey.prototype.A;Ey.prototype.on=Ey.prototype.u;Ey.prototype.once=Ey.prototype.B;Ey.prototype.un=Ey.prototype.v;Ey.prototype.unByKey=Ey.prototype.C;D.prototype.bindTo=D.prototype.O;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.I;D.prototype.getProperties=D.prototype.L;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.G;D.prototype.unbind=D.prototype.P; -D.prototype.unbindAll=D.prototype.R;D.prototype.changed=D.prototype.l;D.prototype.getRevision=D.prototype.A;D.prototype.on=D.prototype.u;D.prototype.once=D.prototype.B;D.prototype.un=D.prototype.v;D.prototype.unByKey=D.prototype.C;E.prototype.getBrightness=E.prototype.d;E.prototype.getContrast=E.prototype.e;E.prototype.getHue=E.prototype.f;E.prototype.getExtent=E.prototype.D;E.prototype.getMaxResolution=E.prototype.g;E.prototype.getMinResolution=E.prototype.i;E.prototype.getOpacity=E.prototype.j; -E.prototype.getSaturation=E.prototype.n;E.prototype.getVisible=E.prototype.b;E.prototype.setBrightness=E.prototype.t;E.prototype.setContrast=E.prototype.F;E.prototype.setHue=E.prototype.J;E.prototype.setExtent=E.prototype.p;E.prototype.setMaxResolution=E.prototype.Q;E.prototype.setMinResolution=E.prototype.V;E.prototype.setOpacity=E.prototype.q;E.prototype.setSaturation=E.prototype.ba;E.prototype.setVisible=E.prototype.ca;E.prototype.bindTo=E.prototype.O;E.prototype.get=E.prototype.get; -E.prototype.getKeys=E.prototype.I;E.prototype.getProperties=E.prototype.L;E.prototype.set=E.prototype.set;E.prototype.setProperties=E.prototype.G;E.prototype.unbind=E.prototype.P;E.prototype.unbindAll=E.prototype.R;E.prototype.changed=E.prototype.l;E.prototype.getRevision=E.prototype.A;E.prototype.on=E.prototype.u;E.prototype.once=E.prototype.B;E.prototype.un=E.prototype.v;E.prototype.unByKey=E.prototype.C;L.prototype.setSource=L.prototype.ga;L.prototype.getBrightness=L.prototype.d; -L.prototype.getContrast=L.prototype.e;L.prototype.getHue=L.prototype.f;L.prototype.getExtent=L.prototype.D;L.prototype.getMaxResolution=L.prototype.g;L.prototype.getMinResolution=L.prototype.i;L.prototype.getOpacity=L.prototype.j;L.prototype.getSaturation=L.prototype.n;L.prototype.getVisible=L.prototype.b;L.prototype.setBrightness=L.prototype.t;L.prototype.setContrast=L.prototype.F;L.prototype.setHue=L.prototype.J;L.prototype.setExtent=L.prototype.p;L.prototype.setMaxResolution=L.prototype.Q; -L.prototype.setMinResolution=L.prototype.V;L.prototype.setOpacity=L.prototype.q;L.prototype.setSaturation=L.prototype.ba;L.prototype.setVisible=L.prototype.ca;L.prototype.bindTo=L.prototype.O;L.prototype.get=L.prototype.get;L.prototype.getKeys=L.prototype.I;L.prototype.getProperties=L.prototype.L;L.prototype.set=L.prototype.set;L.prototype.setProperties=L.prototype.G;L.prototype.unbind=L.prototype.P;L.prototype.unbindAll=L.prototype.R;L.prototype.changed=L.prototype.l;L.prototype.getRevision=L.prototype.A; -L.prototype.on=L.prototype.u;L.prototype.once=L.prototype.B;L.prototype.un=L.prototype.v;L.prototype.unByKey=L.prototype.C;$.prototype.getSource=$.prototype.a;$.prototype.getStyle=$.prototype.Uc;$.prototype.getStyleFunction=$.prototype.Vc;$.prototype.setStyle=$.prototype.oa;$.prototype.setSource=$.prototype.ga;$.prototype.getBrightness=$.prototype.d;$.prototype.getContrast=$.prototype.e;$.prototype.getHue=$.prototype.f;$.prototype.getExtent=$.prototype.D;$.prototype.getMaxResolution=$.prototype.g; -$.prototype.getMinResolution=$.prototype.i;$.prototype.getOpacity=$.prototype.j;$.prototype.getSaturation=$.prototype.n;$.prototype.getVisible=$.prototype.b;$.prototype.setBrightness=$.prototype.t;$.prototype.setContrast=$.prototype.F;$.prototype.setHue=$.prototype.J;$.prototype.setExtent=$.prototype.p;$.prototype.setMaxResolution=$.prototype.Q;$.prototype.setMinResolution=$.prototype.V;$.prototype.setOpacity=$.prototype.q;$.prototype.setSaturation=$.prototype.ba;$.prototype.setVisible=$.prototype.ca; -$.prototype.bindTo=$.prototype.O;$.prototype.get=$.prototype.get;$.prototype.getKeys=$.prototype.I;$.prototype.getProperties=$.prototype.L;$.prototype.set=$.prototype.set;$.prototype.setProperties=$.prototype.G;$.prototype.unbind=$.prototype.P;$.prototype.unbindAll=$.prototype.R;$.prototype.changed=$.prototype.l;$.prototype.getRevision=$.prototype.A;$.prototype.on=$.prototype.u;$.prototype.once=$.prototype.B;$.prototype.un=$.prototype.v;$.prototype.unByKey=$.prototype.C;J.prototype.setSource=J.prototype.ga; -J.prototype.getBrightness=J.prototype.d;J.prototype.getContrast=J.prototype.e;J.prototype.getHue=J.prototype.f;J.prototype.getExtent=J.prototype.D;J.prototype.getMaxResolution=J.prototype.g;J.prototype.getMinResolution=J.prototype.i;J.prototype.getOpacity=J.prototype.j;J.prototype.getSaturation=J.prototype.n;J.prototype.getVisible=J.prototype.b;J.prototype.setBrightness=J.prototype.t;J.prototype.setContrast=J.prototype.F;J.prototype.setHue=J.prototype.J;J.prototype.setExtent=J.prototype.p; -J.prototype.setMaxResolution=J.prototype.Q;J.prototype.setMinResolution=J.prototype.V;J.prototype.setOpacity=J.prototype.q;J.prototype.setSaturation=J.prototype.ba;J.prototype.setVisible=J.prototype.ca;J.prototype.bindTo=J.prototype.O;J.prototype.get=J.prototype.get;J.prototype.getKeys=J.prototype.I;J.prototype.getProperties=J.prototype.L;J.prototype.set=J.prototype.set;J.prototype.setProperties=J.prototype.G;J.prototype.unbind=J.prototype.P;J.prototype.unbindAll=J.prototype.R; -J.prototype.changed=J.prototype.l;J.prototype.getRevision=J.prototype.A;J.prototype.on=J.prototype.u;J.prototype.once=J.prototype.B;J.prototype.un=J.prototype.v;J.prototype.unByKey=J.prototype.C;I.prototype.getBrightness=I.prototype.d;I.prototype.getContrast=I.prototype.e;I.prototype.getHue=I.prototype.f;I.prototype.getExtent=I.prototype.D;I.prototype.getMaxResolution=I.prototype.g;I.prototype.getMinResolution=I.prototype.i;I.prototype.getOpacity=I.prototype.j;I.prototype.getSaturation=I.prototype.n; -I.prototype.getVisible=I.prototype.b;I.prototype.setBrightness=I.prototype.t;I.prototype.setContrast=I.prototype.F;I.prototype.setHue=I.prototype.J;I.prototype.setExtent=I.prototype.p;I.prototype.setMaxResolution=I.prototype.Q;I.prototype.setMinResolution=I.prototype.V;I.prototype.setOpacity=I.prototype.q;I.prototype.setSaturation=I.prototype.ba;I.prototype.setVisible=I.prototype.ca;I.prototype.bindTo=I.prototype.O;I.prototype.get=I.prototype.get;I.prototype.getKeys=I.prototype.I; -I.prototype.getProperties=I.prototype.L;I.prototype.set=I.prototype.set;I.prototype.setProperties=I.prototype.G;I.prototype.unbind=I.prototype.P;I.prototype.unbindAll=I.prototype.R;I.prototype.changed=I.prototype.l;I.prototype.getRevision=I.prototype.A;I.prototype.on=I.prototype.u;I.prototype.once=I.prototype.B;I.prototype.un=I.prototype.v;I.prototype.unByKey=I.prototype.C;K.prototype.setSource=K.prototype.ga;K.prototype.getBrightness=K.prototype.d;K.prototype.getContrast=K.prototype.e; -K.prototype.getHue=K.prototype.f;K.prototype.getExtent=K.prototype.D;K.prototype.getMaxResolution=K.prototype.g;K.prototype.getMinResolution=K.prototype.i;K.prototype.getOpacity=K.prototype.j;K.prototype.getSaturation=K.prototype.n;K.prototype.getVisible=K.prototype.b;K.prototype.setBrightness=K.prototype.t;K.prototype.setContrast=K.prototype.F;K.prototype.setHue=K.prototype.J;K.prototype.setExtent=K.prototype.p;K.prototype.setMaxResolution=K.prototype.Q;K.prototype.setMinResolution=K.prototype.V; -K.prototype.setOpacity=K.prototype.q;K.prototype.setSaturation=K.prototype.ba;K.prototype.setVisible=K.prototype.ca;K.prototype.bindTo=K.prototype.O;K.prototype.get=K.prototype.get;K.prototype.getKeys=K.prototype.I;K.prototype.getProperties=K.prototype.L;K.prototype.set=K.prototype.set;K.prototype.setProperties=K.prototype.G;K.prototype.unbind=K.prototype.P;K.prototype.unbindAll=K.prototype.R;K.prototype.changed=K.prototype.l;K.prototype.getRevision=K.prototype.A;K.prototype.on=K.prototype.u; -K.prototype.once=K.prototype.B;K.prototype.un=K.prototype.v;K.prototype.unByKey=K.prototype.C;ok.prototype.bindTo=ok.prototype.O;ok.prototype.get=ok.prototype.get;ok.prototype.getKeys=ok.prototype.I;ok.prototype.getProperties=ok.prototype.L;ok.prototype.set=ok.prototype.set;ok.prototype.setProperties=ok.prototype.G;ok.prototype.unbind=ok.prototype.P;ok.prototype.unbindAll=ok.prototype.R;ok.prototype.changed=ok.prototype.l;ok.prototype.getRevision=ok.prototype.A;ok.prototype.on=ok.prototype.u; -ok.prototype.once=ok.prototype.B;ok.prototype.un=ok.prototype.v;ok.prototype.unByKey=ok.prototype.C;sk.prototype.getActive=sk.prototype.a;sk.prototype.setActive=sk.prototype.b;sk.prototype.bindTo=sk.prototype.O;sk.prototype.get=sk.prototype.get;sk.prototype.getKeys=sk.prototype.I;sk.prototype.getProperties=sk.prototype.L;sk.prototype.set=sk.prototype.set;sk.prototype.setProperties=sk.prototype.G;sk.prototype.unbind=sk.prototype.P;sk.prototype.unbindAll=sk.prototype.R;sk.prototype.changed=sk.prototype.l; -sk.prototype.getRevision=sk.prototype.A;sk.prototype.on=sk.prototype.u;sk.prototype.once=sk.prototype.B;sk.prototype.un=sk.prototype.v;sk.prototype.unByKey=sk.prototype.C;Nv.prototype.getActive=Nv.prototype.a;Nv.prototype.setActive=Nv.prototype.b;Nv.prototype.bindTo=Nv.prototype.O;Nv.prototype.get=Nv.prototype.get;Nv.prototype.getKeys=Nv.prototype.I;Nv.prototype.getProperties=Nv.prototype.L;Nv.prototype.set=Nv.prototype.set;Nv.prototype.setProperties=Nv.prototype.G;Nv.prototype.unbind=Nv.prototype.P; -Nv.prototype.unbindAll=Nv.prototype.R;Nv.prototype.changed=Nv.prototype.l;Nv.prototype.getRevision=Nv.prototype.A;Nv.prototype.on=Nv.prototype.u;Nv.prototype.once=Nv.prototype.B;Nv.prototype.un=Nv.prototype.v;Nv.prototype.unByKey=Nv.prototype.C;Bk.prototype.getActive=Bk.prototype.a;Bk.prototype.setActive=Bk.prototype.b;Bk.prototype.bindTo=Bk.prototype.O;Bk.prototype.get=Bk.prototype.get;Bk.prototype.getKeys=Bk.prototype.I;Bk.prototype.getProperties=Bk.prototype.L;Bk.prototype.set=Bk.prototype.set; -Bk.prototype.setProperties=Bk.prototype.G;Bk.prototype.unbind=Bk.prototype.P;Bk.prototype.unbindAll=Bk.prototype.R;Bk.prototype.changed=Bk.prototype.l;Bk.prototype.getRevision=Bk.prototype.A;Bk.prototype.on=Bk.prototype.u;Bk.prototype.once=Bk.prototype.B;Bk.prototype.un=Bk.prototype.v;Bk.prototype.unByKey=Bk.prototype.C;Fl.prototype.getActive=Fl.prototype.a;Fl.prototype.setActive=Fl.prototype.b;Fl.prototype.bindTo=Fl.prototype.O;Fl.prototype.get=Fl.prototype.get;Fl.prototype.getKeys=Fl.prototype.I; -Fl.prototype.getProperties=Fl.prototype.L;Fl.prototype.set=Fl.prototype.set;Fl.prototype.setProperties=Fl.prototype.G;Fl.prototype.unbind=Fl.prototype.P;Fl.prototype.unbindAll=Fl.prototype.R;Fl.prototype.changed=Fl.prototype.l;Fl.prototype.getRevision=Fl.prototype.A;Fl.prototype.on=Fl.prototype.u;Fl.prototype.once=Fl.prototype.B;Fl.prototype.un=Fl.prototype.v;Fl.prototype.unByKey=Fl.prototype.C;Ek.prototype.getActive=Ek.prototype.a;Ek.prototype.setActive=Ek.prototype.b;Ek.prototype.bindTo=Ek.prototype.O; -Ek.prototype.get=Ek.prototype.get;Ek.prototype.getKeys=Ek.prototype.I;Ek.prototype.getProperties=Ek.prototype.L;Ek.prototype.set=Ek.prototype.set;Ek.prototype.setProperties=Ek.prototype.G;Ek.prototype.unbind=Ek.prototype.P;Ek.prototype.unbindAll=Ek.prototype.R;Ek.prototype.changed=Ek.prototype.l;Ek.prototype.getRevision=Ek.prototype.A;Ek.prototype.on=Ek.prototype.u;Ek.prototype.once=Ek.prototype.B;Ek.prototype.un=Ek.prototype.v;Ek.prototype.unByKey=Ek.prototype.C;Rv.prototype.getActive=Rv.prototype.a; -Rv.prototype.setActive=Rv.prototype.b;Rv.prototype.bindTo=Rv.prototype.O;Rv.prototype.get=Rv.prototype.get;Rv.prototype.getKeys=Rv.prototype.I;Rv.prototype.getProperties=Rv.prototype.L;Rv.prototype.set=Rv.prototype.set;Rv.prototype.setProperties=Rv.prototype.G;Rv.prototype.unbind=Rv.prototype.P;Rv.prototype.unbindAll=Rv.prototype.R;Rv.prototype.changed=Rv.prototype.l;Rv.prototype.getRevision=Rv.prototype.A;Rv.prototype.on=Rv.prototype.u;Rv.prototype.once=Rv.prototype.B;Rv.prototype.un=Rv.prototype.v; -Rv.prototype.unByKey=Rv.prototype.C;Ik.prototype.getActive=Ik.prototype.a;Ik.prototype.setActive=Ik.prototype.b;Ik.prototype.bindTo=Ik.prototype.O;Ik.prototype.get=Ik.prototype.get;Ik.prototype.getKeys=Ik.prototype.I;Ik.prototype.getProperties=Ik.prototype.L;Ik.prototype.set=Ik.prototype.set;Ik.prototype.setProperties=Ik.prototype.G;Ik.prototype.unbind=Ik.prototype.P;Ik.prototype.unbindAll=Ik.prototype.R;Ik.prototype.changed=Ik.prototype.l;Ik.prototype.getRevision=Ik.prototype.A;Ik.prototype.on=Ik.prototype.u; -Ik.prototype.once=Ik.prototype.B;Ik.prototype.un=Ik.prototype.v;Ik.prototype.unByKey=Ik.prototype.C;Yl.prototype.getGeometry=Yl.prototype.N;Yl.prototype.getActive=Yl.prototype.a;Yl.prototype.setActive=Yl.prototype.b;Yl.prototype.bindTo=Yl.prototype.O;Yl.prototype.get=Yl.prototype.get;Yl.prototype.getKeys=Yl.prototype.I;Yl.prototype.getProperties=Yl.prototype.L;Yl.prototype.set=Yl.prototype.set;Yl.prototype.setProperties=Yl.prototype.G;Yl.prototype.unbind=Yl.prototype.P;Yl.prototype.unbindAll=Yl.prototype.R; -Yl.prototype.changed=Yl.prototype.l;Yl.prototype.getRevision=Yl.prototype.A;Yl.prototype.on=Yl.prototype.u;Yl.prototype.once=Yl.prototype.B;Yl.prototype.un=Yl.prototype.v;Yl.prototype.unByKey=Yl.prototype.C;hw.prototype.getActive=hw.prototype.a;hw.prototype.setActive=hw.prototype.b;hw.prototype.bindTo=hw.prototype.O;hw.prototype.get=hw.prototype.get;hw.prototype.getKeys=hw.prototype.I;hw.prototype.getProperties=hw.prototype.L;hw.prototype.set=hw.prototype.set;hw.prototype.setProperties=hw.prototype.G; -hw.prototype.unbind=hw.prototype.P;hw.prototype.unbindAll=hw.prototype.R;hw.prototype.changed=hw.prototype.l;hw.prototype.getRevision=hw.prototype.A;hw.prototype.on=hw.prototype.u;hw.prototype.once=hw.prototype.B;hw.prototype.un=hw.prototype.v;hw.prototype.unByKey=hw.prototype.C;Zl.prototype.getActive=Zl.prototype.a;Zl.prototype.setActive=Zl.prototype.b;Zl.prototype.bindTo=Zl.prototype.O;Zl.prototype.get=Zl.prototype.get;Zl.prototype.getKeys=Zl.prototype.I;Zl.prototype.getProperties=Zl.prototype.L; -Zl.prototype.set=Zl.prototype.set;Zl.prototype.setProperties=Zl.prototype.G;Zl.prototype.unbind=Zl.prototype.P;Zl.prototype.unbindAll=Zl.prototype.R;Zl.prototype.changed=Zl.prototype.l;Zl.prototype.getRevision=Zl.prototype.A;Zl.prototype.on=Zl.prototype.u;Zl.prototype.once=Zl.prototype.B;Zl.prototype.un=Zl.prototype.v;Zl.prototype.unByKey=Zl.prototype.C;am.prototype.getActive=am.prototype.a;am.prototype.setActive=am.prototype.b;am.prototype.bindTo=am.prototype.O;am.prototype.get=am.prototype.get; -am.prototype.getKeys=am.prototype.I;am.prototype.getProperties=am.prototype.L;am.prototype.set=am.prototype.set;am.prototype.setProperties=am.prototype.G;am.prototype.unbind=am.prototype.P;am.prototype.unbindAll=am.prototype.R;am.prototype.changed=am.prototype.l;am.prototype.getRevision=am.prototype.A;am.prototype.on=am.prototype.u;am.prototype.once=am.prototype.B;am.prototype.un=am.prototype.v;am.prototype.unByKey=am.prototype.C;uw.prototype.getActive=uw.prototype.a;uw.prototype.setActive=uw.prototype.b; -uw.prototype.bindTo=uw.prototype.O;uw.prototype.get=uw.prototype.get;uw.prototype.getKeys=uw.prototype.I;uw.prototype.getProperties=uw.prototype.L;uw.prototype.set=uw.prototype.set;uw.prototype.setProperties=uw.prototype.G;uw.prototype.unbind=uw.prototype.P;uw.prototype.unbindAll=uw.prototype.R;uw.prototype.changed=uw.prototype.l;uw.prototype.getRevision=uw.prototype.A;uw.prototype.on=uw.prototype.u;uw.prototype.once=uw.prototype.B;uw.prototype.un=uw.prototype.v;uw.prototype.unByKey=uw.prototype.C; -cm.prototype.getActive=cm.prototype.a;cm.prototype.setActive=cm.prototype.b;cm.prototype.bindTo=cm.prototype.O;cm.prototype.get=cm.prototype.get;cm.prototype.getKeys=cm.prototype.I;cm.prototype.getProperties=cm.prototype.L;cm.prototype.set=cm.prototype.set;cm.prototype.setProperties=cm.prototype.G;cm.prototype.unbind=cm.prototype.P;cm.prototype.unbindAll=cm.prototype.R;cm.prototype.changed=cm.prototype.l;cm.prototype.getRevision=cm.prototype.A;cm.prototype.on=cm.prototype.u;cm.prototype.once=cm.prototype.B; -cm.prototype.un=cm.prototype.v;cm.prototype.unByKey=cm.prototype.C;em.prototype.getActive=em.prototype.a;em.prototype.setActive=em.prototype.b;em.prototype.bindTo=em.prototype.O;em.prototype.get=em.prototype.get;em.prototype.getKeys=em.prototype.I;em.prototype.getProperties=em.prototype.L;em.prototype.set=em.prototype.set;em.prototype.setProperties=em.prototype.G;em.prototype.unbind=em.prototype.P;em.prototype.unbindAll=em.prototype.R;em.prototype.changed=em.prototype.l;em.prototype.getRevision=em.prototype.A; -em.prototype.on=em.prototype.u;em.prototype.once=em.prototype.B;em.prototype.un=em.prototype.v;em.prototype.unByKey=em.prototype.C;im.prototype.getActive=im.prototype.a;im.prototype.setActive=im.prototype.b;im.prototype.bindTo=im.prototype.O;im.prototype.get=im.prototype.get;im.prototype.getKeys=im.prototype.I;im.prototype.getProperties=im.prototype.L;im.prototype.set=im.prototype.set;im.prototype.setProperties=im.prototype.G;im.prototype.unbind=im.prototype.P;im.prototype.unbindAll=im.prototype.R; -im.prototype.changed=im.prototype.l;im.prototype.getRevision=im.prototype.A;im.prototype.on=im.prototype.u;im.prototype.once=im.prototype.B;im.prototype.un=im.prototype.v;im.prototype.unByKey=im.prototype.C;Dw.prototype.getActive=Dw.prototype.a;Dw.prototype.setActive=Dw.prototype.b;Dw.prototype.bindTo=Dw.prototype.O;Dw.prototype.get=Dw.prototype.get;Dw.prototype.getKeys=Dw.prototype.I;Dw.prototype.getProperties=Dw.prototype.L;Dw.prototype.set=Dw.prototype.set;Dw.prototype.setProperties=Dw.prototype.G; -Dw.prototype.unbind=Dw.prototype.P;Dw.prototype.unbindAll=Dw.prototype.R;Dw.prototype.changed=Dw.prototype.l;Dw.prototype.getRevision=Dw.prototype.A;Dw.prototype.on=Dw.prototype.u;Dw.prototype.once=Dw.prototype.B;Dw.prototype.un=Dw.prototype.v;Dw.prototype.unByKey=Dw.prototype.C;Mk.prototype.changed=Mk.prototype.l;Mk.prototype.getRevision=Mk.prototype.A;Mk.prototype.on=Mk.prototype.u;Mk.prototype.once=Mk.prototype.B;Mk.prototype.un=Mk.prototype.v;Mk.prototype.unByKey=Mk.prototype.C; -Ok.prototype.clone=Ok.prototype.clone;Ok.prototype.getClosestPoint=Ok.prototype.f;Ok.prototype.getType=Ok.prototype.H;Ok.prototype.intersectsExtent=Ok.prototype.ha;Ok.prototype.transform=Ok.prototype.e;Ok.prototype.changed=Ok.prototype.l;Ok.prototype.getRevision=Ok.prototype.A;Ok.prototype.on=Ok.prototype.u;Ok.prototype.once=Ok.prototype.B;Ok.prototype.un=Ok.prototype.v;Ok.prototype.unByKey=Ok.prototype.C;gn.prototype.getFirstCoordinate=gn.prototype.vb;gn.prototype.getLastCoordinate=gn.prototype.wb; -gn.prototype.getLayout=gn.prototype.xb;gn.prototype.applyTransform=gn.prototype.ma;gn.prototype.translate=gn.prototype.Aa;gn.prototype.getClosestPoint=gn.prototype.f;gn.prototype.intersectsExtent=gn.prototype.ha;gn.prototype.changed=gn.prototype.l;gn.prototype.getRevision=gn.prototype.A;gn.prototype.on=gn.prototype.u;gn.prototype.once=gn.prototype.B;gn.prototype.un=gn.prototype.v;gn.prototype.unByKey=gn.prototype.C;jn.prototype.getClosestPoint=jn.prototype.f;jn.prototype.transform=jn.prototype.e; -jn.prototype.changed=jn.prototype.l;jn.prototype.getRevision=jn.prototype.A;jn.prototype.on=jn.prototype.u;jn.prototype.once=jn.prototype.B;jn.prototype.un=jn.prototype.v;jn.prototype.unByKey=jn.prototype.C;hl.prototype.getExtent=hl.prototype.D;hl.prototype.getFirstCoordinate=hl.prototype.vb;hl.prototype.getLastCoordinate=hl.prototype.wb;hl.prototype.getLayout=hl.prototype.xb;hl.prototype.applyTransform=hl.prototype.ma;hl.prototype.translate=hl.prototype.Aa;hl.prototype.getClosestPoint=hl.prototype.f; -hl.prototype.intersectsExtent=hl.prototype.ha;hl.prototype.transform=hl.prototype.e;hl.prototype.changed=hl.prototype.l;hl.prototype.getRevision=hl.prototype.A;hl.prototype.on=hl.prototype.u;hl.prototype.once=hl.prototype.B;hl.prototype.un=hl.prototype.v;hl.prototype.unByKey=hl.prototype.C;M.prototype.getExtent=M.prototype.D;M.prototype.getFirstCoordinate=M.prototype.vb;M.prototype.getLastCoordinate=M.prototype.wb;M.prototype.getLayout=M.prototype.xb;M.prototype.applyTransform=M.prototype.ma; -M.prototype.translate=M.prototype.Aa;M.prototype.getClosestPoint=M.prototype.f;M.prototype.transform=M.prototype.e;M.prototype.changed=M.prototype.l;M.prototype.getRevision=M.prototype.A;M.prototype.on=M.prototype.u;M.prototype.once=M.prototype.B;M.prototype.un=M.prototype.v;M.prototype.unByKey=M.prototype.C;rn.prototype.getExtent=rn.prototype.D;rn.prototype.getFirstCoordinate=rn.prototype.vb;rn.prototype.getLastCoordinate=rn.prototype.wb;rn.prototype.getLayout=rn.prototype.xb; -rn.prototype.applyTransform=rn.prototype.ma;rn.prototype.translate=rn.prototype.Aa;rn.prototype.getClosestPoint=rn.prototype.f;rn.prototype.transform=rn.prototype.e;rn.prototype.changed=rn.prototype.l;rn.prototype.getRevision=rn.prototype.A;rn.prototype.on=rn.prototype.u;rn.prototype.once=rn.prototype.B;rn.prototype.un=rn.prototype.v;rn.prototype.unByKey=rn.prototype.C;un.prototype.getExtent=un.prototype.D;un.prototype.getFirstCoordinate=un.prototype.vb;un.prototype.getLastCoordinate=un.prototype.wb; -un.prototype.getLayout=un.prototype.xb;un.prototype.applyTransform=un.prototype.ma;un.prototype.translate=un.prototype.Aa;un.prototype.getClosestPoint=un.prototype.f;un.prototype.transform=un.prototype.e;un.prototype.changed=un.prototype.l;un.prototype.getRevision=un.prototype.A;un.prototype.on=un.prototype.u;un.prototype.once=un.prototype.B;un.prototype.un=un.prototype.v;un.prototype.unByKey=un.prototype.C;vn.prototype.getExtent=vn.prototype.D;vn.prototype.getFirstCoordinate=vn.prototype.vb; -vn.prototype.getLastCoordinate=vn.prototype.wb;vn.prototype.getLayout=vn.prototype.xb;vn.prototype.applyTransform=vn.prototype.ma;vn.prototype.translate=vn.prototype.Aa;vn.prototype.getClosestPoint=vn.prototype.f;vn.prototype.transform=vn.prototype.e;vn.prototype.changed=vn.prototype.l;vn.prototype.getRevision=vn.prototype.A;vn.prototype.on=vn.prototype.u;vn.prototype.once=vn.prototype.B;vn.prototype.un=vn.prototype.v;vn.prototype.unByKey=vn.prototype.C;jl.prototype.getFirstCoordinate=jl.prototype.vb; -jl.prototype.getLastCoordinate=jl.prototype.wb;jl.prototype.getLayout=jl.prototype.xb;jl.prototype.applyTransform=jl.prototype.ma;jl.prototype.translate=jl.prototype.Aa;jl.prototype.getClosestPoint=jl.prototype.f;jl.prototype.transform=jl.prototype.e;jl.prototype.changed=jl.prototype.l;jl.prototype.getRevision=jl.prototype.A;jl.prototype.on=jl.prototype.u;jl.prototype.once=jl.prototype.B;jl.prototype.un=jl.prototype.v;jl.prototype.unByKey=jl.prototype.C;H.prototype.getExtent=H.prototype.D; -H.prototype.getFirstCoordinate=H.prototype.vb;H.prototype.getLastCoordinate=H.prototype.wb;H.prototype.getLayout=H.prototype.xb;H.prototype.applyTransform=H.prototype.ma;H.prototype.translate=H.prototype.Aa;H.prototype.getClosestPoint=H.prototype.f;H.prototype.transform=H.prototype.e;H.prototype.changed=H.prototype.l;H.prototype.getRevision=H.prototype.A;H.prototype.on=H.prototype.u;H.prototype.once=H.prototype.B;H.prototype.un=H.prototype.v;H.prototype.unByKey=H.prototype.C; -Uq.prototype.readFeatures=Uq.prototype.ja;X.prototype.readFeatures=X.prototype.ja;X.prototype.readFeatures=X.prototype.ja;rp.prototype.bindTo=rp.prototype.O;rp.prototype.get=rp.prototype.get;rp.prototype.getKeys=rp.prototype.I;rp.prototype.getProperties=rp.prototype.L;rp.prototype.set=rp.prototype.set;rp.prototype.setProperties=rp.prototype.G;rp.prototype.unbind=rp.prototype.P;rp.prototype.unbindAll=rp.prototype.R;rp.prototype.changed=rp.prototype.l;rp.prototype.getRevision=rp.prototype.A; -rp.prototype.on=rp.prototype.u;rp.prototype.once=rp.prototype.B;rp.prototype.un=rp.prototype.v;rp.prototype.unByKey=rp.prototype.C;$g.prototype.bindTo=$g.prototype.O;$g.prototype.get=$g.prototype.get;$g.prototype.getKeys=$g.prototype.I;$g.prototype.getProperties=$g.prototype.L;$g.prototype.set=$g.prototype.set;$g.prototype.setProperties=$g.prototype.G;$g.prototype.unbind=$g.prototype.P;$g.prototype.unbindAll=$g.prototype.R;$g.prototype.changed=$g.prototype.l;$g.prototype.getRevision=$g.prototype.A; -$g.prototype.on=$g.prototype.u;$g.prototype.once=$g.prototype.B;$g.prototype.un=$g.prototype.v;$g.prototype.unByKey=$g.prototype.C;ah.prototype.getMap=ah.prototype.d;ah.prototype.setMap=ah.prototype.setMap;ah.prototype.bindTo=ah.prototype.O;ah.prototype.get=ah.prototype.get;ah.prototype.getKeys=ah.prototype.I;ah.prototype.getProperties=ah.prototype.L;ah.prototype.set=ah.prototype.set;ah.prototype.setProperties=ah.prototype.G;ah.prototype.unbind=ah.prototype.P;ah.prototype.unbindAll=ah.prototype.R; -ah.prototype.changed=ah.prototype.l;ah.prototype.getRevision=ah.prototype.A;ah.prototype.on=ah.prototype.u;ah.prototype.once=ah.prototype.B;ah.prototype.un=ah.prototype.v;ah.prototype.unByKey=ah.prototype.C;lh.prototype.getMap=lh.prototype.d;lh.prototype.setMap=lh.prototype.setMap;lh.prototype.bindTo=lh.prototype.O;lh.prototype.get=lh.prototype.get;lh.prototype.getKeys=lh.prototype.I;lh.prototype.getProperties=lh.prototype.L;lh.prototype.set=lh.prototype.set;lh.prototype.setProperties=lh.prototype.G; -lh.prototype.unbind=lh.prototype.P;lh.prototype.unbindAll=lh.prototype.R;lh.prototype.changed=lh.prototype.l;lh.prototype.getRevision=lh.prototype.A;lh.prototype.on=lh.prototype.u;lh.prototype.once=lh.prototype.B;lh.prototype.un=lh.prototype.v;lh.prototype.unByKey=lh.prototype.C;mh.prototype.getMap=mh.prototype.d;mh.prototype.bindTo=mh.prototype.O;mh.prototype.get=mh.prototype.get;mh.prototype.getKeys=mh.prototype.I;mh.prototype.getProperties=mh.prototype.L;mh.prototype.set=mh.prototype.set; -mh.prototype.setProperties=mh.prototype.G;mh.prototype.unbind=mh.prototype.P;mh.prototype.unbindAll=mh.prototype.R;mh.prototype.changed=mh.prototype.l;mh.prototype.getRevision=mh.prototype.A;mh.prototype.on=mh.prototype.u;mh.prototype.once=mh.prototype.B;mh.prototype.un=mh.prototype.v;mh.prototype.unByKey=mh.prototype.C;Ro.prototype.getMap=Ro.prototype.d;Ro.prototype.bindTo=Ro.prototype.O;Ro.prototype.get=Ro.prototype.get;Ro.prototype.getKeys=Ro.prototype.I;Ro.prototype.getProperties=Ro.prototype.L; -Ro.prototype.set=Ro.prototype.set;Ro.prototype.setProperties=Ro.prototype.G;Ro.prototype.unbind=Ro.prototype.P;Ro.prototype.unbindAll=Ro.prototype.R;Ro.prototype.changed=Ro.prototype.l;Ro.prototype.getRevision=Ro.prototype.A;Ro.prototype.on=Ro.prototype.u;Ro.prototype.once=Ro.prototype.B;Ro.prototype.un=Ro.prototype.v;Ro.prototype.unByKey=Ro.prototype.C;dh.prototype.getMap=dh.prototype.d;dh.prototype.setMap=dh.prototype.setMap;dh.prototype.bindTo=dh.prototype.O;dh.prototype.get=dh.prototype.get; -dh.prototype.getKeys=dh.prototype.I;dh.prototype.getProperties=dh.prototype.L;dh.prototype.set=dh.prototype.set;dh.prototype.setProperties=dh.prototype.G;dh.prototype.unbind=dh.prototype.P;dh.prototype.unbindAll=dh.prototype.R;dh.prototype.changed=dh.prototype.l;dh.prototype.getRevision=dh.prototype.A;dh.prototype.on=dh.prototype.u;dh.prototype.once=dh.prototype.B;dh.prototype.un=dh.prototype.v;dh.prototype.unByKey=dh.prototype.C;Xo.prototype.getMap=Xo.prototype.d;Xo.prototype.setMap=Xo.prototype.setMap; -Xo.prototype.bindTo=Xo.prototype.O;Xo.prototype.get=Xo.prototype.get;Xo.prototype.getKeys=Xo.prototype.I;Xo.prototype.getProperties=Xo.prototype.L;Xo.prototype.set=Xo.prototype.set;Xo.prototype.setProperties=Xo.prototype.G;Xo.prototype.unbind=Xo.prototype.P;Xo.prototype.unbindAll=Xo.prototype.R;Xo.prototype.changed=Xo.prototype.l;Xo.prototype.getRevision=Xo.prototype.A;Xo.prototype.on=Xo.prototype.u;Xo.prototype.once=Xo.prototype.B;Xo.prototype.un=Xo.prototype.v;Xo.prototype.unByKey=Xo.prototype.C; -fh.prototype.getMap=fh.prototype.d;fh.prototype.setMap=fh.prototype.setMap;fh.prototype.bindTo=fh.prototype.O;fh.prototype.get=fh.prototype.get;fh.prototype.getKeys=fh.prototype.I;fh.prototype.getProperties=fh.prototype.L;fh.prototype.set=fh.prototype.set;fh.prototype.setProperties=fh.prototype.G;fh.prototype.unbind=fh.prototype.P;fh.prototype.unbindAll=fh.prototype.R;fh.prototype.changed=fh.prototype.l;fh.prototype.getRevision=fh.prototype.A;fh.prototype.on=fh.prototype.u;fh.prototype.once=fh.prototype.B; -fh.prototype.un=fh.prototype.v;fh.prototype.unByKey=fh.prototype.C;lp.prototype.getMap=lp.prototype.d;lp.prototype.bindTo=lp.prototype.O;lp.prototype.get=lp.prototype.get;lp.prototype.getKeys=lp.prototype.I;lp.prototype.getProperties=lp.prototype.L;lp.prototype.set=lp.prototype.set;lp.prototype.setProperties=lp.prototype.G;lp.prototype.unbind=lp.prototype.P;lp.prototype.unbindAll=lp.prototype.R;lp.prototype.changed=lp.prototype.l;lp.prototype.getRevision=lp.prototype.A;lp.prototype.on=lp.prototype.u; -lp.prototype.once=lp.prototype.B;lp.prototype.un=lp.prototype.v;lp.prototype.unByKey=lp.prototype.C;qp.prototype.getMap=qp.prototype.d;qp.prototype.setMap=qp.prototype.setMap;qp.prototype.bindTo=qp.prototype.O;qp.prototype.get=qp.prototype.get;qp.prototype.getKeys=qp.prototype.I;qp.prototype.getProperties=qp.prototype.L;qp.prototype.set=qp.prototype.set;qp.prototype.setProperties=qp.prototype.G;qp.prototype.unbind=qp.prototype.P;qp.prototype.unbindAll=qp.prototype.R;qp.prototype.changed=qp.prototype.l; -qp.prototype.getRevision=qp.prototype.A;qp.prototype.on=qp.prototype.u;qp.prototype.once=qp.prototype.B;qp.prototype.un=qp.prototype.v;qp.prototype.unByKey=qp.prototype.C; +function Ex(b,c){this.g=[];this.u=b;this.A=c||null;this.f=this.a=!1;this.c=void 0;this.B=this.G=this.j=!1;this.i=0;this.b=null;this.l=0}Ex.prototype.cancel=function(b){if(this.a)this.c instanceof Ex&&this.c.cancel();else{if(this.b){var c=this.b;delete this.b;b?c.cancel(b):(c.l--,0>=c.l&&c.cancel())}this.u?this.u.call(this.A,this):this.B=!0;this.a||(b=new Fx,Gx(this),Hx(this,!1,b))}};Ex.prototype.v=function(b,c){this.j=!1;Hx(this,b,c)};function Hx(b,c,d){b.a=!0;b.c=d;b.f=!c;Ix(b)} +function Gx(b){if(b.a){if(!b.B)throw new Jx;b.B=!1}}Ex.prototype.$c=function(b){Gx(this);Hx(this,!0,b)};function Kx(b,c,d,e){b.g.push([c,d,e]);b.a&&Ix(b)}Ex.prototype.then=function(b,c,d){var e,f,g=new Ln(function(b,c){e=b;f=c});Kx(this,e,function(b){b instanceof Fx?g.cancel():f(b)});return g.then(b,c,d)};Cn(Ex);function Lx(b){return Va(b.g,function(b){return ka(b[1])})} +function Ix(b){if(b.i&&b.a&&Lx(b)){var c=b.i,d=Mx[c];d&&(ba.clearTimeout(d.ha),delete Mx[c]);b.i=0}b.b&&(b.b.l--,delete b.b);for(var c=b.c,e=d=!1;b.g.length&&!b.j;){var f=b.g.shift(),g=f[0],h=f[1],f=f[2];if(g=b.f?h:g)try{var k=g.call(f||b.A,c);m(k)&&(b.f=b.f&&(k==c||k instanceof Error),b.c=c=k);Dn(c)&&(e=!0,b.j=!0)}catch(n){c=n,b.f=!0,Lx(b)||(d=!0)}}b.c=c;e&&(k=ra(b.v,b,!0),e=ra(b.v,b,!1),c instanceof Ex?(Kx(c,k,e),c.G=!0):c.then(k,e));d&&(c=new Nx(c),Mx[c.ha]=c,b.i=c.ha)} +function Jx(){xa.call(this)}w(Jx,xa);Jx.prototype.message="Deferred has already fired";Jx.prototype.name="AlreadyCalledError";function Fx(){xa.call(this)}w(Fx,xa);Fx.prototype.message="Deferred was canceled";Fx.prototype.name="CanceledError";function Nx(b){this.ha=ba.setTimeout(ra(this.b,this),0);this.a=b}Nx.prototype.b=function(){delete Mx[this.ha];throw this.a;};var Mx={};function Ox(b,c){m(b.name)?(this.name=b.name,this.code=Px[b.name]):(this.code=b.code,this.name=Qx(b.code));xa.call(this,Ba("%s %s",this.name,c))}w(Ox,xa);function Qx(b){var c=wb(Px,function(c){return b==c});if(!m(c))throw Error("Invalid code: "+b);return c}var Px={AbortError:3,EncodingError:5,InvalidModificationError:9,InvalidStateError:7,NotFoundError:1,NotReadableError:4,NoModificationAllowedError:6,PathExistsError:12,QuotaExceededError:10,SecurityError:2,SyntaxError:8,TypeMismatchError:11};function Rx(b,c){sc.call(this,b.type,c)}w(Rx,sc);function Sx(){ad.call(this);this.gb=new FileReader;this.gb.onloadstart=ra(this.a,this);this.gb.onprogress=ra(this.a,this);this.gb.onload=ra(this.a,this);this.gb.onabort=ra(this.a,this);this.gb.onerror=ra(this.a,this);this.gb.onloadend=ra(this.a,this)}w(Sx,ad);Sx.prototype.getError=function(){return this.gb.error&&new Ox(this.gb.error,"reading file")};Sx.prototype.a=function(b){C(this,new Rx(b,this))};Sx.prototype.X=function(){Sx.aa.X.call(this);delete this.gb}; +function Tx(b){var c=new Ex;b.Ra("loadend",sa(function(b,c){var f=c.gb.result,g=c.getError();null==f||g?(Gx(b),Hx(b,!1,g)):b.$c(f);c.jd()},c,b));return c};function Ux(b){b=m(b)?b:{};Uk.call(this,{handleEvent:pe});this.i=m(b.formatConstructors)?b.formatConstructors:[];this.A=m(b.projection)?Ae(b.projection):null;this.g=null;this.f=void 0}w(Ux,Uk);Ux.prototype.X=function(){m(this.f)&&Xc(this.f);Ux.aa.X.call(this)};Ux.prototype.j=function(b){b=b.a.dataTransfer.files;var c,d,e;c=0;for(d=b.length;c<d;++c){var f=e=b[c],g=new Sx,h=Tx(g);g.gb.readAsText(f,"");Kx(h,sa(this.l,e),null,this)}}; +Ux.prototype.l=function(b,c){var d=this.v,e=this.A;null===e&&(e=d.Y().g);var d=this.i,f=[],g,h;g=0;for(h=d.length;g<h;++g){var k=new d[g],n;try{n=k.ra(c)}catch(p){n=null}if(null!==n){var k=k.Ga(c),k=Te(k,e),q,r;q=0;for(r=n.length;q<r;++q){var t=n[q],v=t.Z();null!=v&&v.va(k);f.push(t)}}}C(this,new Vx(Wx,this,b,f,e))}; +Ux.prototype.setMap=function(b){m(this.f)&&(Xc(this.f),this.f=void 0);null!==this.g&&(rc(this.g),this.g=null);Ux.aa.setMap.call(this,b);null!==b&&(this.g=new Dx(b.b),this.f=x(this.g,"drop",this.j,!1,this))};var Wx="addfeatures";function Vx(b,c,d,e,f){sc.call(this,b,c);this.features=e;this.file=d;this.projection=f}w(Vx,sc);function Xx(b,c){this.x=b;this.y=c}w(Xx,Dg);Xx.prototype.clone=function(){return new Xx(this.x,this.y)};Xx.prototype.scale=Dg.prototype.scale;Xx.prototype.add=function(b){this.x+=b.x;this.y+=b.y;return this};Xx.prototype.rotate=function(b){var c=Math.cos(b);b=Math.sin(b);var d=this.y*c+this.x*b;this.x=this.x*c-this.y*b;this.y=d;return this};function Yx(b){b=m(b)?b:{};gl.call(this,{handleDownEvent:Zx,handleDragEvent:$x,handleUpEvent:ay});this.l=m(b.condition)?b.condition:dl;this.f=this.g=void 0;this.j=0;this.u=m(b.duration)?b.duration:400}w(Yx,gl); +function $x(b){if(fl(b)){var c=b.map,d=c.Ca();b=b.pixel;b=new Xx(b[0]-d[0]/2,d[1]/2-b[1]);d=Math.atan2(b.y,b.x);b=Math.sqrt(b.x*b.x+b.y*b.y);var e=c.Y(),f=Rf(e);c.render();m(this.g)&&Vk(c,e,f.rotation-(d-this.g));this.g=d;m(this.f)&&Xk(c,e,f.resolution/b*this.f);m(this.f)&&(this.j=this.f/b);this.f=b}} +function ay(b){if(!fl(b))return!0;b=b.map;var c=b.Y();Uf(c,-1);var d=Rf(c),e=this.j-1,f=d.rotation,f=c.constrainRotation(f,0);Vk(b,c,f,void 0,void 0);d=d.resolution;f=this.u;d=c.constrainResolution(d,0,e);Xk(b,c,d,void 0,f);this.j=0;return!1}function Zx(b){return fl(b)&&this.l(b)?(Uf(b.map.Y(),1),this.f=this.g=void 0,!0):!1};function by(b,c){sc.call(this,b);this.feature=c}w(by,sc); +function cy(b){gl.call(this,{handleDownEvent:dy,handleEvent:ey,handleUpEvent:fy});this.ea=null;this.T=!1;this.tb=m(b.source)?b.source:null;this.rb=m(b.features)?b.features:null;this.Si=m(b.snapTolerance)?b.snapTolerance:12;this.ba=b.type;this.g=gy(this.ba);this.Xa=m(b.minPoints)?b.minPoints:this.g===hy?3:2;this.Na=m(b.maxPoints)?b.maxPoints:Infinity;var c=b.geometryFunction;if(!m(c))if("Circle"===this.ba)c=function(b,c){var d=m(c)?c:new Zm([NaN,NaN]);d.Lf(b[0],Math.sqrt(td(b[0],b[1])));return d}; +else{var d,c=this.g;c===iy?d=D:c===jy?d=N:c===hy&&(d=F);c=function(b,c){var g=c;m(g)?g.ja(b):g=new d(b);return g}}this.C=c;this.K=this.u=this.f=this.G=this.j=this.l=null;this.Ti=m(b.clickTolerance)?b.clickTolerance*b.clickTolerance:36;this.qa=new M({source:new W({useSpatialIndex:!1,wrapX:m(b.wrapX)?b.wrapX:!1}),style:m(b.style)?b.style:ky()});this.sb=b.geometryName;this.Ki=m(b.condition)?b.condition:cl;this.xa=m(b.freehandCondition)?b.freehandCondition:dl;x(this,id("active"),this.ub,!1,this)} +w(cy,gl);function ky(){var b=Nl();return function(c){return b[c.Z().V()]}}cy.prototype.setMap=function(b){cy.aa.setMap.call(this,b);this.ub()};function ey(b){var c=!this.T;this.T&&b.type===fk?(ly(this,b),c=!1):b.type===ek?c=my(this,b):b.type===Yj&&(c=!1);return hl.call(this,b)&&c}function dy(b){if(this.Ki(b))return this.ea=b.pixel,!0;if(this.g!==jy&&this.g!==hy||!this.xa(b))return!1;this.ea=b.pixel;this.T=!0;null===this.l&&ny(this,b);return!0} +function fy(b){this.T=!1;var c=this.ea,d=b.pixel,e=c[0]-d[0],c=c[1]-d[1],d=!0;e*e+c*c<=this.Ti&&(my(this,b),null===this.l?(ny(this,b),this.g===iy&&this.U()):this.g===oy?this.U():py(this,b)?this.U():ly(this,b),d=!1);return d} +function my(b,c){if(null===b.l){var d=c.coordinate.slice();null===b.G?(b.G=new Bn(new D(d)),qy(b)):b.G.Z().ja(d)}else{var d=c.coordinate,e=b.j.Z(),f;b.g===iy?f=b.f:b.g===hy?(f=b.f[0],f=f[f.length-1],py(b,c)&&(d=b.l.slice())):(f=b.f,f=f[f.length-1]);f[0]=d[0];f[1]=d[1];b.C(b.f,e);null===b.G||b.G.Z().ja(d);e instanceof F&&b.g!==hy?(null===b.u&&(b.u=new Bn(new N(null))),e=e.og(0),d=b.u.Z(),hn(d,e.b,e.o)):null!==b.K&&(d=b.u.Z(),d.ja(b.K));qy(b)}return!0} +function py(b,c){var d=!1;if(null!==b.j){var e=!1,f=[b.l];b.g===jy?e=b.f.length>b.Xa:b.g===hy&&(e=b.f[0].length>b.Xa,f=[b.f[0][0],b.f[0][b.f[0].length-2]]);if(e)for(var e=c.map,g=0,h=f.length;g<h;g++){var k=f[g],n=e.ya(k),p=c.pixel,d=p[0]-n[0],n=p[1]-n[1],p=b.T&&b.xa(c)?1:b.Si;if(d=Math.sqrt(d*d+n*n)<=p){b.l=k;break}}}return d} +function ny(b,c){var d=c.coordinate;b.l=d;b.g===iy?b.f=d.slice():b.g===hy?(b.f=[[d.slice(),d.slice()]],b.K=b.f[0]):(b.f=[d.slice(),d.slice()],b.g===oy&&(b.K=b.f));null!==b.K&&(b.u=new Bn(new N(b.K)));d=b.C(b.f);b.j=new Bn;m(b.sb)&&b.j.Sc(b.sb);b.j.Sa(d);qy(b);C(b,new by("drawstart",b.j))} +function ly(b,c){var d=c.coordinate,e=b.j.Z(),f,g;if(b.g===jy)b.l=d.slice(),g=b.f,g.push(d.slice()),f=g.length>b.Na,b.C(g,e);else if(b.g===hy){g=b.f[0];g.push(d.slice());if(f=g.length>b.Na)b.l=g[0];b.C(b.f,e)}qy(b);f&&b.U()} +cy.prototype.U=function(){var b=ry(this),c=this.f,d=b.Z();this.g===jy?(c.pop(),this.C(c,d)):this.g===hy&&(c[0].pop(),c[0].push(c[0][0]),this.C(c,d));"MultiPoint"===this.ba?b.Sa(new ln([c])):"MultiLineString"===this.ba?b.Sa(new O([c])):"MultiPolygon"===this.ba&&b.Sa(new P([c]));C(this,new by("drawend",b));null===this.rb||this.rb.push(b);null===this.tb||this.tb.vd(b)};function ry(b){b.l=null;var c=b.j;null!==c&&(b.j=null,b.G=null,b.u=null,b.qa.ca().clear(!0));return c}cy.prototype.wc=oe; +function qy(b){var c=[];null===b.j||c.push(b.j);null===b.u||c.push(b.u);null===b.G||c.push(b.G);b=b.qa.ca();b.clear(!0);b.Bc(c)}cy.prototype.ub=function(){var b=this.v,c=this.b();null!==b&&c||ry(this);this.qa.setMap(c?b:null)};function gy(b){var c;"Point"===b||"MultiPoint"===b?c=iy:"LineString"===b||"MultiLineString"===b?c=jy:"Polygon"===b||"MultiPolygon"===b?c=hy:"Circle"===b&&(c=oy);return c}var iy="Point",jy="LineString",hy="Polygon",oy="Circle";function sy(b,c,d){sc.call(this,b);this.features=c;this.mapBrowserPointerEvent=d}w(sy,sc); +function ty(b){gl.call(this,{handleDownEvent:uy,handleDragEvent:vy,handleEvent:wy,handleUpEvent:xy});this.ea=m(b.deleteCondition)?b.deleteCondition:ue(cl,bl);this.ba=this.g=null;this.U=[0,0];this.C=!1;this.f=new xp;this.u=m(b.pixelTolerance)?b.pixelTolerance:10;this.T=!1;this.j=null;this.G=new M({source:new W({useSpatialIndex:!1,wrapX:m(b.wrapX)?b.wrapX:!1}),style:m(b.style)?b.style:yy(),updateWhileAnimating:!0,updateWhileInteracting:!0});this.K={Point:this.Ol,LineString:this.Rg,LinearRing:this.Rg, +Polygon:this.Pl,MultiPoint:this.Ml,MultiLineString:this.Ll,MultiPolygon:this.Nl,GeometryCollection:this.Kl};this.l=b.features;this.l.forEach(this.Qg,this);x(this.l,"add",this.Il,!1,this);x(this.l,"remove",this.Jl,!1,this)}w(ty,gl);l=ty.prototype;l.Qg=function(b){var c=b.Z();m(this.K[c.V()])&&this.K[c.V()].call(this,b,c);b=this.v;null===b||zy(this,this.U,b)};l.setMap=function(b){this.G.setMap(b);ty.aa.setMap.call(this,b)};l.Il=function(b){this.Qg(b.element)}; +l.Jl=function(b){var c=b.element;b=this.f;var d,e=[];Bp(b,c.Z().R(),function(b){c===b.feature&&e.push(b)});for(d=e.length-1;0<=d;--d)b.remove(e[d]);null!==this.g&&0===this.l.Rb()&&(this.G.ca().Mc(this.g),this.g=null)};l.Ol=function(b,c){var d=c.W(),d={feature:b,geometry:c,ia:[d,d]};this.f.oa(c.R(),d)};l.Ml=function(b,c){var d=c.W(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:b,geometry:c,depth:[f],index:f,ia:[e,e]},this.f.oa(c.R(),e)}; +l.Rg=function(b,c){var d=c.W(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:b,geometry:c,index:e,ia:g},this.f.oa(Kd(g),h)};l.Ll=function(b,c){var d=c.W(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,geometry:c,depth:[h],index:f,ia:n},this.f.oa(Kd(n),p)}; +l.Pl=function(b,c){var d=c.W(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,geometry:c,depth:[h],index:f,ia:n},this.f.oa(Kd(n),p)};l.Nl=function(b,c){var d=c.W(),e,f,g,h,k,n,p,q,r,t;n=0;for(p=d.length;n<p;++n)for(q=d[n],h=0,k=q.length;h<k;++h)for(e=q[h],f=0,g=e.length-1;f<g;++f)r=e.slice(f,f+2),t={feature:b,geometry:c,depth:[h,n],index:f,ia:r},this.f.oa(Kd(r),t)}; +l.Kl=function(b,c){var d,e=c.f;for(d=0;d<e.length;++d)this.K[e[d].V()].call(this,b,e[d])};function Ay(b,c){var d=b.g;null===d?(d=new Bn(new D(c)),b.g=d,b.G.ca().vd(d)):d.Z().ja(c)}function By(b,c){return b.index-c.index} +function uy(b){zy(this,b.pixel,b.map);this.j=[];var c=this.g;if(null!==c){var d=[],c=c.Z().W(),e=Kd([c]),e=zp(this.f,e),f={};e.sort(By);for(var g=0,h=e.length;g<h;++g){var k=e[g],n=k.ia,p=ma(k.feature),q=k.depth;q&&(p+="-"+q.join("-"));f[p]||(f[p]=Array(2));if(rd(n[0],c)&&!f[p][0])this.j.push([k,0]),f[p][0]=k;else if(rd(n[1],c)&&!f[p][1]){if("LineString"!==k.geometry.V()&&"MultiLineString"!==k.geometry.V()||!f[p][0]||0!==f[p][0].index)this.j.push([k,1]),f[p][1]=k}else ma(n)in this.ba&&!f[p][0]&&!f[p][1]&& +d.push([k,c])}for(g=d.length-1;0<=g;--g)this.Gk.apply(this,d[g]);C(this,new sy("modifystart",this.l,b))}return null!==this.g} +function vy(b){this.C=!1;b=b.coordinate;for(var c=0,d=this.j.length;c<d;++c){for(var e=this.j[c],f=e[0],g=f.depth,h=f.geometry,k=h.W(),n=f.ia,e=e[1];b.length<h.H;)b.push(0);switch(h.V()){case "Point":k=b;n[0]=n[1]=b;break;case "MultiPoint":k[f.index]=b;n[0]=n[1]=b;break;case "LineString":k[f.index+e]=b;n[e]=b;break;case "MultiLineString":k[g[0]][f.index+e]=b;n[e]=b;break;case "Polygon":k[g[0]][f.index+e]=b;n[e]=b;break;case "MultiPolygon":k[g[1]][g[0]][f.index+e]=b,n[e]=b}h.ja(k)}Ay(this,b)} +function xy(b){for(var c,d=this.j.length-1;0<=d;--d)c=this.j[d][0],this.f.update(Kd(c.ia),c);C(this,new sy("modifyend",this.l,b));return!1} +function wy(b){var c;b.map.Y().c.slice()[1]||b.type!=ek||this.A||(this.U=b.pixel,zy(this,b.pixel,b.map));if(null!==this.g&&this.ea(b))if(b.type==Zj&&this.C)c=!0;else{this.g.Z();c=this.j;var d={},e,f,g,h,k,n,p,q,r;for(k=c.length-1;0<=k;--k)if(g=c[k],q=g[0],h=q.geometry,f=h.W(),r=ma(q.feature),q.depth&&(r+="-"+q.depth.join("-")),e=p=n=void 0,0===g[1]?(p=q,n=q.index):1==g[1]&&(e=q,n=q.index+1),r in d||(d[r]=[e,p,n]),g=d[r],m(e)&&(g[0]=e),m(p)&&(g[1]=p),m(g[0])&&m(g[1])){e=f;r=!1;p=n-1;switch(h.V()){case "MultiLineString":f[q.depth[0]].splice(n, +1);r=!0;break;case "LineString":f.splice(n,1);r=!0;break;case "MultiPolygon":e=e[q.depth[1]];case "Polygon":e=e[q.depth[0]],4<e.length&&(n==e.length-1&&(n=0),e.splice(n,1),r=!0,0===n&&(e.pop(),e.push(e[0]),p=e.length-1))}r&&(this.f.remove(g[0]),this.f.remove(g[1]),h.ja(f),f={depth:q.depth,feature:q.feature,geometry:q.geometry,index:p,ia:[g[0].ia[0],g[1].ia[1]]},this.f.oa(Kd(f.ia),f),Cy(this,h,n,q.depth,-1),null!==this.g&&(this.G.ca().Mc(this.g),this.g=null))}c=!0}b.type==Zj&&(this.C=!1);return hl.call(this, +b)&&!c} +function zy(b,c,d){function e(b,c){return ud(f,b.ia)-ud(f,c.ia)}var f=d.ta(c),g=d.ta([c[0]-b.u,c[1]+b.u]),h=d.ta([c[0]+b.u,c[1]-b.u]),g=Kd([g,h]),g=zp(b.f,g);if(0<g.length){g.sort(e);var h=g[0].ia,k=od(f,h),n=d.ya(k);if(Math.sqrt(td(c,n))<=b.u){c=d.ya(h[0]);d=d.ya(h[1]);c=td(n,c);d=td(n,d);b.T=Math.sqrt(Math.min(c,d))<=b.u;b.T&&(k=c>d?h[1]:h[0]);Ay(b,k);d={};d[ma(h)]=!0;c=1;for(n=g.length;c<n;++c)if(k=g[c].ia,rd(h[0],k[0])&&rd(h[1],k[1])||rd(h[0],k[1])&&rd(h[1],k[0]))d[ma(k)]=!0;else break;b.ba=d; +return}}null!==b.g&&(b.G.ca().Mc(b.g),b.g=null)} +l.Gk=function(b,c){for(var d=b.ia,e=b.feature,f=b.geometry,g=b.depth,h=b.index,k;c.length<f.H;)c.push(0);switch(f.V()){case "MultiLineString":k=f.W();k[g[0]].splice(h+1,0,c);break;case "Polygon":k=f.W();k[g[0]].splice(h+1,0,c);break;case "MultiPolygon":k=f.W();k[g[1]][g[0]].splice(h+1,0,c);break;case "LineString":k=f.W();k.splice(h+1,0,c);break;default:return}f.ja(k);k=this.f;k.remove(b);Cy(this,f,h,g,1);var n={ia:[d[0],c],feature:e,geometry:f,depth:g,index:h};k.oa(Kd(n.ia),n);this.j.push([n,1]); +d={ia:[c,d[1]],feature:e,geometry:f,depth:g,index:h+1};k.oa(Kd(d.ia),d);this.j.push([d,0]);this.C=!0};function Cy(b,c,d,e,f){Bp(b.f,c.R(),function(b){b.geometry===c&&(!m(e)||ib(b.depth,e))&&b.index>d&&(b.index+=f)})}function yy(){var b=Nl();return function(){return b.Point}};function Dy(b,c,d,e){sc.call(this,b);this.selected=c;this.deselected=d;this.mapBrowserEvent=e}w(Dy,sc); +function Ey(b){Uk.call(this,{handleEvent:Fy});b=m(b)?b:{};this.A=m(b.condition)?b.condition:bl;this.j=m(b.addCondition)?b.addCondition:oe;this.C=m(b.removeCondition)?b.removeCondition:oe;this.U=m(b.toggleCondition)?b.toggleCondition:dl;this.l=m(b.multi)?b.multi:!1;this.g=m(b.filter)?b.filter:pe;var c;if(m(b.layers))if(ka(b.layers))c=b.layers;else{var d=b.layers;c=function(b){return Ya(d,b)}}else c=pe;this.i=c;this.f=new M({source:new W({useSpatialIndex:!1,wrapX:b.wrapX}),style:m(b.style)?b.style: +Gy(),updateWhileAnimating:!0,updateWhileInteracting:!0});b=this.f.ca().c;x(b,"add",this.u,!1,this);x(b,"remove",this.K,!1,this)}w(Ey,Uk);Ey.prototype.G=function(){return this.f.ca().c}; +function Fy(b){if(!this.A(b))return!0;var c=this.j(b),d=this.C(b),e=this.U(b),f=!c&&!d&&!e,g=b.map,h=this.f.ca().c,k=[],n=[],p=!1;if(f)g.bf(b.pixel,function(b,c){if(this.g(b,c))return n.push(b),!this.l},this,this.i),0<n.length&&1==h.Rb()&&h.item(0)==n[0]||(p=!0,0!==h.Rb()&&(k=Array.prototype.concat(h.b),h.clear()),h.sf(n));else{g.bf(b.pixel,function(b,f){-1==Ra(h.b,b)?(c||e)&&this.g(b,f)&&n.push(b):(d||e)&&k.push(b)},this,this.i);for(f=k.length-1;0<=f;--f)h.remove(k[f]);h.sf(n);if(0<n.length||0<k.length)p= +!0}p&&C(this,new Dy("select",n,k,b));return al(b)}Ey.prototype.setMap=function(b){var c=this.v,d=this.f.ca().c;null===c||d.forEach(c.Uh,c);Ey.aa.setMap.call(this,b);this.f.setMap(b);null===b||d.forEach(b.Ph,b)};function Gy(){var b=Nl();cb(b.Polygon,b.LineString);cb(b.GeometryCollection,b.LineString);return function(c){return b[c.Z().V()]}}Ey.prototype.u=function(b){b=b.element;var c=this.v;null===c||c.Ph(b)};Ey.prototype.K=function(b){b=b.element;var c=this.v;null===c||c.Uh(b)};function Hy(b){gl.call(this,{handleEvent:Iy,handleDownEvent:pe,handleUpEvent:Jy});b=m(b)?b:{};this.l=m(b.source)?b.source:null;this.j=m(b.features)?b.features:null;this.ba=[];this.G={};this.C={};this.U={};this.u={};this.K=null;this.g=m(b.pixelTolerance)?b.pixelTolerance:10;this.ea=ra(Ky,this);this.f=new xp;this.T={Point:this.Vl,LineString:this.Ug,LinearRing:this.Ug,Polygon:this.Wl,MultiPoint:this.Tl,MultiLineString:this.Sl,MultiPolygon:this.Ul,GeometryCollection:this.Rl}}w(Hy,gl);l=Hy.prototype; +l.sd=function(b,c){var d=m(c)?c:!0,e=b.Z(),f=this.T[e.V()];if(m(f)){var g=ma(b);this.U[g]=e.R(Ld());f.call(this,b,e);d&&(this.C[g]=e.D("change",ra(this.dk,this,b),this),this.G[g]=b.D(id(b.b),this.Ql,this))}};l.$i=function(b){this.sd(b)};l.aj=function(b){this.td(b)};l.Sg=function(b){var c;b instanceof Gp?c=b.feature:b instanceof qg&&(c=b.element);this.sd(c)};l.Tg=function(b){var c;b instanceof Gp?c=b.feature:b instanceof qg&&(c=b.element);this.td(c)}; +l.Ql=function(b){b=b.c;this.td(b,!0);this.sd(b,!0)};l.dk=function(b){if(this.A){var c=ma(b);c in this.u||(this.u[c]=b)}else this.Vh(b)};l.td=function(b,c){var d=m(c)?c:!0,e=ma(b),f=this.U[e];if(f){var g=this.f,h=[];Bp(g,f,function(c){b===c.feature&&h.push(c)});for(f=h.length-1;0<=f;--f)g.remove(h[f]);d&&(Xc(this.C[e]),delete this.C[e],Xc(this.G[e]),delete this.G[e])}}; +l.setMap=function(b){var c=this.v,d=this.ba,e;null===this.j?null===this.l||(e=this.l.Lc()):e=this.j;c&&(Sa(d,ed),d.length=0,e.forEach(this.aj,this));Hy.aa.setMap.call(this,b);b&&(null!==this.j?(d.push(this.j.D("add",this.Sg,this)),d.push(this.j.D("remove",this.Tg,this))):null!==this.l&&(d.push(this.l.D("addfeature",this.Sg,this)),d.push(this.l.D("removefeature",this.Tg,this))),e.forEach(this.$i,this))};l.wc=oe;l.Vh=function(b){this.td(b,!1);this.sd(b,!1)}; +l.Rl=function(b,c){var d,e=c.f;for(d=0;d<e.length;++d)this.T[e[d].V()].call(this,b,e[d])};l.Ug=function(b,c){var d=c.W(),e,f,g,h;e=0;for(f=d.length-1;e<f;++e)g=d.slice(e,e+2),h={feature:b,ia:g},this.f.oa(Kd(g),h)};l.Sl=function(b,c){var d=c.W(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,ia:n},this.f.oa(Kd(n),p)};l.Tl=function(b,c){var d=c.W(),e,f,g;f=0;for(g=d.length;f<g;++f)e=d[f],e={feature:b,ia:[e,e]},this.f.oa(c.R(),e)}; +l.Ul=function(b,c){var d=c.W(),e,f,g,h,k,n,p,q,r,t;n=0;for(p=d.length;n<p;++n)for(q=d[n],h=0,k=q.length;h<k;++h)for(e=q[h],f=0,g=e.length-1;f<g;++f)r=e.slice(f,f+2),t={feature:b,ia:r},this.f.oa(Kd(r),t)};l.Vl=function(b,c){var d=c.W(),d={feature:b,ia:[d,d]};this.f.oa(c.R(),d)};l.Wl=function(b,c){var d=c.W(),e,f,g,h,k,n,p;h=0;for(k=d.length;h<k;++h)for(e=d[h],f=0,g=e.length-1;f<g;++f)n=e.slice(f,f+2),p={feature:b,ia:n},this.f.oa(Kd(n),p)}; +function Iy(b){var c,d,e=b.pixel,f=b.coordinate;c=b.map;var g=c.ta([e[0]-this.g,e[1]+this.g]);d=c.ta([e[0]+this.g,e[1]-this.g]);var g=Kd([g,d]),h=zp(this.f,g),k=!1,g=!1,n=null;d=null;0<h.length&&(this.K=f,h.sort(this.ea),h=h[0].ia,n=od(f,h),d=c.ya(n),Math.sqrt(td(e,d))<=this.g&&(g=!0,e=c.ya(h[0]),f=c.ya(h[1]),e=td(d,e),f=td(d,f),k=Math.sqrt(Math.min(e,f))<=this.g))&&(n=e>f?h[1]:h[0],d=c.ya(n),d=[Math.round(d[0]),Math.round(d[1])]);c=n;g&&(b.coordinate=c.slice(0,2),b.pixel=d);return hl.call(this,b)} +function Jy(){var b=sb(this.u);b.length&&(Sa(b,this.Vh,this),this.u={});return!1}function Ky(b,c){return ud(this.K,b.ia)-ud(this.K,c.ia)};function Z(b){b=m(b)?b:{};var c=Cb(b);delete c.gradient;delete c.radius;delete c.blur;delete c.shadow;delete c.weight;M.call(this,c);this.f=null;this.$=m(b.shadow)?b.shadow:250;this.G=void 0;this.v=null;x(this,id("gradient"),this.ek,!1,this);this.Jh(m(b.gradient)?b.gradient:Ly);this.Fh(m(b.blur)?b.blur:15);this.Wg(m(b.radius)?b.radius:8);x(this,[id("blur"),id("radius")],this.Bg,!1,this);this.Bg();var d=m(b.weight)?b.weight:"weight",e;ia(d)?e=function(b){return b.get(d)}:e=d;this.g(ra(function(b){b= +e(b);b=m(b)?Wb(b,0,1):1;var c=255*b|0,d=this.v[c];m(d)||(d=[new Jl({image:new Ck({opacity:b,src:this.G})})],this.v[c]=d);return d},this));this.set("renderOrder",null);x(this,"render",this.wk,!1,this)}w(Z,M);var Ly=["#00f","#0ff","#0f0","#ff0","#f00"];l=Z.prototype;l.jg=function(){return this.get("blur")};l.ng=function(){return this.get("gradient")};l.Vg=function(){return this.get("radius")}; +l.ek=function(){for(var b=this.ng(),c=Vi(1,256),d=c.createLinearGradient(0,0,1,256),e=1/(b.length-1),f=0,g=b.length;f<g;++f)d.addColorStop(f*e,b[f]);c.fillStyle=d;c.fillRect(0,0,1,256);this.f=c.getImageData(0,0,1,256).data};l.Bg=function(){var b=this.Vg(),c=this.jg(),d=b+c+1,e=2*d,e=Vi(e,e);e.shadowOffsetX=e.shadowOffsetY=this.$;e.shadowBlur=c;e.shadowColor="#000";e.beginPath();c=d-this.$;e.arc(c,c,b,0,2*Math.PI,!0);e.fill();this.G=e.canvas.toDataURL();this.v=Array(256);this.s()}; +l.wk=function(b){b=b.context;var c=b.canvas,c=b.getImageData(0,0,c.width,c.height),d=c.data,e,f,g;e=0;for(f=d.length;e<f;e+=4)if(g=4*d[e+3])d[e]=this.f[g],d[e+1]=this.f[g+1],d[e+2]=this.f[g+2];b.putImageData(c,0,0)};l.Fh=function(b){this.set("blur",b)};l.Jh=function(b){this.set("gradient",b)};l.Wg=function(b){this.set("radius",b)};function My(b,c){var d=c||{},e=d.document||document,f=Pg("SCRIPT"),g={Eh:f,xc:void 0},h=new Ex(Ny,g),k=null,n=null!=d.timeout?d.timeout:5E3;0<n&&(k=window.setTimeout(function(){Oy(f,!0);var c=new Py(Qy,"Timeout reached for loading script "+b);Gx(h);Hx(h,!1,c)},n),g.xc=k);f.onload=f.onreadystatechange=function(){f.readyState&&"loaded"!=f.readyState&&"complete"!=f.readyState||(Oy(f,d.Wi||!1,k),h.$c(null))};f.onerror=function(){Oy(f,!0,k);var c=new Py(Ry,"Error while loading script "+b);Gx(h);Hx(h,!1, +c)};Jg(f,{type:"text/javascript",charset:"UTF-8",src:b});Sy(e).appendChild(f);return h}function Sy(b){var c=b.getElementsByTagName("HEAD");return c&&0!=c.length?c[0]:b.documentElement}function Ny(){if(this&&this.Eh){var b=this.Eh;b&&"SCRIPT"==b.tagName&&Oy(b,!0,this.xc)}}function Oy(b,c,d){null!=d&&ba.clearTimeout(d);b.onload=ca;b.onerror=ca;b.onreadystatechange=ca;c&&window.setTimeout(function(){Tg(b)},0)}var Ry=0,Qy=1; +function Py(b,c){var d="Jsloader error (code #"+b+")";c&&(d+=": "+c);xa.call(this,d);this.code=b}w(Py,xa);function Ty(b,c){this.b=new kt(b);this.a=c?c:"callback";this.xc=5E3}var Uy=0; +Ty.prototype.send=function(b,c,d,e){b=b||null;e=e||"_"+(Uy++).toString(36)+ua().toString(36);ba._callbacks_||(ba._callbacks_={});var f=this.b.clone();if(b)for(var g in b)if(!b.hasOwnProperty||b.hasOwnProperty(g)){var h=f,k=g,n=b[g];ga(n)||(n=[String(n)]);Dt(h.a,k,n)}c&&(ba._callbacks_[e]=Vy(e,c),c=this.a,g="_callbacks_."+e,ga(g)||(g=[String(g)]),Dt(f.a,c,g));c=My(f.toString(),{timeout:this.xc,Wi:!0});Kx(c,null,Wy(e,b,d),void 0);return{ha:e,gg:c}}; +Ty.prototype.cancel=function(b){b&&(b.gg&&b.gg.cancel(),b.ha&&Xy(b.ha,!1))};function Wy(b,c,d){return function(){Xy(b,!1);d&&d(c)}}function Vy(b,c){return function(d){Xy(b,!0);c.apply(void 0,arguments)}}function Xy(b,c){ba._callbacks_[b]&&(c?delete ba._callbacks_[b]:ba._callbacks_[b]=ca)};function Yy(b){var c=/\{z\}/g,d=/\{x\}/g,e=/\{y\}/g,f=/\{-y\}/g;return function(g){return null===g?void 0:b.replace(c,g[0].toString()).replace(d,g[1].toString()).replace(e,function(){return(-g[2]-1).toString()}).replace(f,function(){return((1<<g[0])+g[2]).toString()})}}function Zy(b){return $y(Ua(b,Yy))}function $y(b){return 1===b.length?b[0]:function(c,d,e){return null===c?void 0:b[Xb((c[1]<<c[0])+c[2],b.length)](c,d,e)}}function az(){} +function bz(b){var c=[],d=/\{(\d)-(\d)\}/.exec(b)||/\{([a-z])-([a-z])\}/.exec(b);if(d){var e=d[2].charCodeAt(0),f;for(f=d[1].charCodeAt(0);f<=e;++f)c.push(b.replace(d[0],String.fromCharCode(f)))}else c.push(b);return c};function cz(b){Rh.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,opaque:b.opaque,projection:b.projection,state:m(b.state)?b.state:void 0,tileGrid:b.tileGrid,tilePixelRatio:b.tilePixelRatio,wrapX:b.wrapX});this.tileUrlFunction=m(b.tileUrlFunction)?b.tileUrlFunction:az;this.crossOrigin=m(b.crossOrigin)?b.crossOrigin:null;this.tileLoadFunction=m(b.tileLoadFunction)?b.tileLoadFunction:dz;this.tileClass=m(b.tileClass)?b.tileClass:zx}w(cz,Rh);function dz(b,c){b.Ta().src=c}l=cz.prototype; +l.cc=function(b,c,d,e,f){var g=this.lb(b,c,d);if(yh(this.b,g))return this.b.get(g);b=[b,c,d];c=Uh(this,b,f);e=null===c?void 0:this.tileUrlFunction(c,e,f);e=new this.tileClass(b,m(e)?0:4,m(e)?e:"",this.crossOrigin,this.tileLoadFunction);x(e,"change",this.zm,!1,this);this.b.set(g,e);return e};l.ab=function(){return this.tileLoadFunction};l.bb=function(){return this.tileUrlFunction}; +l.zm=function(b){b=b.target;switch(b.state){case 1:C(this,new Vh("tileloadstart",b));break;case 2:C(this,new Vh("tileloadend",b));break;case 3:C(this,new Vh("tileloaderror",b))}};l.ib=function(b){this.b.clear();this.tileLoadFunction=b;this.s()};l.Fa=function(b){this.b.clear();this.tileUrlFunction=b;this.s()};l.Pf=function(b,c,d){b=this.lb(b,c,d);yh(this.b,b)&&this.b.get(b)};function ez(b){cz.call(this,{crossOrigin:"anonymous",opaque:!0,projection:Ae("EPSG:3857"),state:"loading",tileLoadFunction:b.tileLoadFunction,wrapX:m(b.wrapX)?b.wrapX:!0});this.i=m(b.culture)?b.culture:"en-us";this.f=m(b.maxZoom)?b.maxZoom:-1;var c=new kt("https://dev.virtualearth.net/REST/v1/Imagery/Metadata/"+b.imagerySet);(new Ty(c,"jsonp")).send({include:"ImageryProviders",uriScheme:"https",key:b.key},ra(this.l,this))}w(ez,cz);var fz=new pg({html:'<a class="ol-attribution-bing-tos" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'}); +ez.prototype.l=function(b){if(200!=b.statusCode||"OK"!=b.statusDescription||"ValidCredentials"!=b.authenticationResultCode||1!=b.resourceSets.length||1!=b.resourceSets[0].resources.length)Fh(this,"error");else{var c=b.brandLogoUri;-1==c.indexOf("https")&&(c=c.replace("http","https"));var d=b.resourceSets[0].resources[0],e=-1==this.f?d.zoomMax:this.f;b=hg(this.j);var f=Ph({extent:b,minZoom:d.zoomMin,maxZoom:e,tileSize:d.imageWidth==d.imageHeight?d.imageWidth:[d.imageWidth,d.imageHeight]});this.tileGrid= +f;var g=this.i;this.tileUrlFunction=$y(Ua(d.imageUrlSubdomains,function(b){var c=[0,0,0],e=d.imageUrl.replace("{subdomain}",b).replace("{culture}",g);return function(b){if(null!==b)return bg(b[0],b[1],-b[2]-1,c),e.replace("{quadkey}",dg(c))}}));if(d.imageryProviders){var h=Ee(Ae("EPSG:4326"),this.j);b=Ua(d.imageryProviders,function(b){var c=b.attribution,d={};Sa(b.coverageAreas,function(b){var c=b.zoomMin,g=Math.min(b.zoomMax,e);b=b.bbox;b=me([b[1],b[0],b[3],b[2]],h);var k,n;for(k=c;k<=g;++k)n=k.toString(), +c=jg(f,b,k),n in d?d[n].push(c):d[n]=[c]});return new pg({html:c,tileRanges:d})});b.push(fz);this.g=b}this.U=c;Fh(this,"ready")}};function gz(b){W.call(this,{attributions:b.attributions,extent:b.extent,logo:b.logo,projection:b.projection});this.K=void 0;this.ba=m(b.distance)?b.distance:20;this.C=[];this.u=b.source;this.u.D("change",gz.prototype.qa,this)}w(gz,W);gz.prototype.ea=function(){return this.u};gz.prototype.hc=function(b,c,d){c!==this.K&&(this.clear(),this.K=c,this.u.hc(b,c,d),hz(this),this.Bc(this.C))};gz.prototype.qa=function(){this.clear();hz(this);this.Bc(this.C);this.s()}; +function hz(b){if(m(b.K)){b.C.length=0;for(var c=Ld(),d=b.ba*b.K,e=b.u.Lc(),f={},g=0,h=e.length;g<h;g++){var k=e[g];ub(f,ma(k).toString())||(k=k.Z().W(),Wd(k,c),Pd(c,d,c),k=b.u.hf(c),k=Ta(k,function(b){b=ma(b).toString();return b in f?!1:f[b]=!0}),b.C.push(iz(k)))}}}function iz(b){for(var c=b.length,d=[0,0],e=0;e<c;e++){var f=b[e].Z().W();nd(d,f)}c=1/c;d[0]*=c;d[1]*=c;d=new Bn(new D(d));d.set("features",b);return d};function jz(b){tn.call(this,{projection:b.projection,resolutions:b.resolutions});this.$=m(b.crossOrigin)?b.crossOrigin:null;this.i=m(b.displayDpi)?b.displayDpi:96;this.f=m(b.params)?b.params:{};var c;m(b.url)?c=Bx(b.url,this.f,ra(this.fm,this)):c=Cx;this.K=c;this.b=m(b.imageLoadFunction)?b.imageLoadFunction:zn;this.ba=m(b.hidpi)?b.hidpi:!0;this.T=m(b.metersPerUnit)?b.metersPerUnit:1;this.u=m(b.ratio)?b.ratio:1;this.ea=m(b.useOverlay)?b.useOverlay:!1;this.c=null;this.C=0}w(jz,tn);l=jz.prototype; +l.em=function(){return this.f};l.mc=function(b,c,d,e){c=un(this,c);d=this.ba?d:1;var f=this.c;if(null!==f&&this.C==this.a&&f.resolution==c&&f.g==d&&Ud(f.R(),b))return f;1!=this.u&&(b=b.slice(),le(b,this.u));e=this.K(b,[je(b)/c*d,ge(b)/c*d],e);m(e)?(f=new yx(b,c,d,this.g,e,this.$,this.b),x(f,"change",this.l,!1,this)):f=null;this.c=f;this.C=this.a;return f};l.dm=function(){return this.b};l.hm=function(b){Gb(this.f,b);this.s()}; +l.fm=function(b,c,d,e){var f;f=this.T;var g=je(d),h=ge(d),k=e[0],n=e[1],p=.0254/this.i;f=n*g>k*h?g*f/(k*p):h*f/(n*p);d=ee(d);e={OPERATION:this.ea?"GETDYNAMICMAPOVERLAYIMAGE":"GETMAPIMAGE",VERSION:"2.0.0",LOCALE:"en",CLIENTAGENT:"ol.source.ImageMapGuide source",CLIP:"1",SETDISPLAYDPI:this.i,SETDISPLAYWIDTH:Math.round(e[0]),SETDISPLAYHEIGHT:Math.round(e[1]),SETVIEWSCALE:f,SETVIEWCENTERX:d[0],SETVIEWCENTERY:d[1]};Gb(e,c);return mo(oo([b],e))};l.gm=function(b){this.c=null;this.b=b;this.s()};function kz(b){var c=m(b.attributions)?b.attributions:null,d=b.imageExtent,e,f;m(b.imageSize)&&(e=ge(d)/b.imageSize[1],f=[e]);var g=m(b.crossOrigin)?b.crossOrigin:null,h=m(b.imageLoadFunction)?b.imageLoadFunction:zn;tn.call(this,{attributions:c,logo:b.logo,projection:Ae(b.projection),resolutions:f});this.b=new yx(d,e,1,c,b.url,g,h);x(this.b,"change",this.l,!1,this)}w(kz,tn);kz.prototype.mc=function(b){return ie(b,this.b.R())?this.b:null};function lz(b){b=m(b)?b:{};tn.call(this,{attributions:b.attributions,logo:b.logo,projection:b.projection,resolutions:b.resolutions});this.ba=m(b.crossOrigin)?b.crossOrigin:null;this.f=b.url;this.u=m(b.imageLoadFunction)?b.imageLoadFunction:zn;this.c=b.params;this.i=!0;mz(this);this.$=b.serverType;this.ea=m(b.hidpi)?b.hidpi:!0;this.b=null;this.C=[0,0];this.T=0;this.K=m(b.ratio)?b.ratio:1.5}w(lz,tn);var nz=[101,101];l=lz.prototype; +l.nm=function(b,c,d,e){if(m(this.f)){var f=fe(b,c,0,nz),g={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.c.LAYERS};Gb(g,this.c,e);e=Math.floor((f[3]-b[1])/c);g[this.i?"I":"X"]=Math.floor((b[0]-f[0])/c);g[this.i?"J":"Y"]=e;return oz(this,f,nz,1,Ae(d),g)}};l.pm=function(){return this.c}; +l.mc=function(b,c,d,e){if(!m(this.f))return null;c=un(this,c);1==d||this.ea&&m(this.$)||(d=1);var f=this.b;if(null!==f&&this.T==this.a&&f.resolution==c&&f.g==d&&Ud(f.R(),b))return f;f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Gb(f,this.c);b=b.slice();var g=(b[0]+b[2])/2,h=(b[1]+b[3])/2;if(1!=this.K){var k=this.K*je(b)/2,n=this.K*ge(b)/2;b[0]=g-k;b[1]=h-n;b[2]=g+k;b[3]=h+n}var k=c/d,n=Math.ceil(je(b)/k),p=Math.ceil(ge(b)/k);b[0]=g-k*n/2;b[2]=g+k*n/2;b[1]=h- +k*p/2;b[3]=h+k*p/2;this.C[0]=n;this.C[1]=p;e=oz(this,b,this.C,d,e,f);this.b=new yx(b,c,d,this.g,e,this.ba,this.u);this.T=this.a;x(this.b,"change",this.l,!1,this);return this.b};l.om=function(){return this.u}; +function oz(b,c,d,e,f,g){g[b.i?"CRS":"SRS"]=f.a;"STYLES"in b.c||(g.STYLES=new String(""));if(1!=e)switch(b.$){case "geoserver":e=90*e+.5|0;g.FORMAT_OPTIONS=m(g.FORMAT_OPTIONS)?g.FORMAT_OPTIONS+(";dpi:"+e):"dpi:"+e;break;case "mapserver":g.MAP_RESOLUTION=90*e;break;case "carmentaserver":case "qgis":g.DPI=90*e}g.WIDTH=d[0];g.HEIGHT=d[1];d=f.f;var h;b.i&&"ne"==d.substr(0,2)?h=[c[1],c[0],c[3],c[2]]:h=c;g.BBOX=h.join(",");return mo(oo([b.f],g))}l.qm=function(){return this.f}; +l.rm=function(b){this.b=null;this.u=b;this.s()};l.sm=function(b){b!=this.f&&(this.f=b,this.b=null,this.s())};l.tm=function(b){Gb(this.c,b);mz(this);this.b=null;this.s()};function mz(b){b.i=0<=Na(Ab(b.c,"VERSION","1.3.0"),"1.3")};function pz(b){var c=m(b.projection)?b.projection:"EPSG:3857",d=m(b.tileGrid)?b.tileGrid:Ph({extent:hg(c),maxZoom:b.maxZoom,tileSize:b.tileSize});cz.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:c,tileGrid:d,tileLoadFunction:b.tileLoadFunction,tilePixelRatio:b.tilePixelRatio,tileUrlFunction:az,wrapX:m(b.wrapX)?b.wrapX:!0});m(b.tileUrlFunction)?this.Fa(b.tileUrlFunction):m(b.urls)?this.Fa(Zy(b.urls)):m(b.url)&&this.f(b.url)}w(pz,cz);pz.prototype.f=function(b){this.Fa(Zy(bz(b)))};function qz(b){b=m(b)?b:{};var c;m(b.attributions)?c=b.attributions:c=[rz];pz.call(this,{attributions:c,crossOrigin:m(b.crossOrigin)?b.crossOrigin:"anonymous",opaque:!0,maxZoom:m(b.maxZoom)?b.maxZoom:19,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:"https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png",wrapX:b.wrapX})}w(qz,pz);var rz=new pg({html:'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.'});function sz(b){b=m(b)?b:{};var c=tz[b.layer];this.i=b.layer;pz.call(this,{attributions:c.attributions,crossOrigin:"anonymous",logo:"https://developer.mapquest.com/content/osm/mq_logo.png",maxZoom:c.maxZoom,opaque:!0,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:"https://otile{1-4}-s.mqcdn.com/tiles/1.0.0/"+this.i+"/{z}/{x}/{y}.jpg"})}w(sz,pz); +var uz=new pg({html:'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a>'}),tz={osm:{maxZoom:19,attributions:[uz,rz]},sat:{maxZoom:18,attributions:[uz,new pg({html:"Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency"})]},hyb:{maxZoom:18,attributions:[uz,rz]}};sz.prototype.l=function(){return this.i};(function(){var b={},c={kb:b};(function(d){if("object"===typeof b&&"undefined"!==typeof c)c.kb=d();else{var e;"undefined"!==typeof window?e=window:"undefined"!==typeof global?e=global:"undefined"!==typeof self?e=self:e=this;e.cp=d()}})(function(){return function e(b,c,h){function k(p,r){if(!c[p]){if(!b[p]){var t="function"==typeof require&&require;if(!r&&t)return t(p,!0);if(n)return n(p,!0);t=Error("Cannot find module '"+p+"'");throw t.code="MODULE_NOT_FOUND",t;}t=c[p]={kb:{}};b[p][0].call(t.kb,function(c){var e= +b[p][1][c];return k(e?e:c)},t,t.kb,e,b,c,h)}return c[p].kb}for(var n="function"==typeof require&&require,p=0;p<h.length;p++)k(h[p]);return k}({1:[function(b,c,g){b=b("./processor");g.ui=b},{"./processor":2}],2:[function(b,c){function g(b){return function(c){var e=c.buffers,f=c.meta,g=c.width,h=c.height,k=e.length,n=e[0].byteLength,A;if(c.imageOps){n=Array(k);for(A=0;A<k;++A)n[A]=new ImageData(new Uint8ClampedArray(e[A]),g,h);g=b(n,f).data}else{g=new Uint8ClampedArray(n);h=Array(k);c=Array(k);for(A= +0;A<k;++A)h[A]=new Uint8ClampedArray(e[A]),c[A]=[0,0,0,0];for(e=0;e<n;e+=4){for(A=0;A<k;++A){var y=h[A];c[A][0]=y[e];c[A][1]=y[e+1];c[A][2]=y[e+2];c[A][3]=y[e+3]}A=b(c,f);g[e]=A[0];g[e+1]=A[1];g[e+2]=A[2];g[e+3]=A[3]}}return g.buffer}}function h(b,c){var e=Object.keys(b.lib||{}).map(function(c){return"var "+c+" = "+b.lib[c].toString()+";"}).concat(["var __minion__ = ("+g.toString()+")(",b.operation.toString(),");",'self.addEventListener("message", function(__event__) {',"var buffer = __minion__(__event__.data);", +"self.postMessage({buffer: buffer, meta: __event__.data.meta}, [buffer]);","});"]),e=URL.createObjectURL(new Blob(e,{type:"text/javascript"})),e=new Worker(e);e.addEventListener("message",c);return e}function k(b,c){var e=g(b.operation);return{postMessage:function(b){setTimeout(function(){c({data:{buffer:e(b),ke:b.ke}})},0)}}}function n(b){this.Re=!!b.Ek;var c;0===b.threads?c=0:this.Re?c=1:c=b.threads||1;var e=[];if(c)for(var f=0;f<c;++f)e[f]=h(b,this.$f.bind(this,f));else e[0]=k(b,this.$f.bind(this, +0));this.Jd=e;this.Yc=[];this.Hi=b.Kn||Infinity;this.Id=0;this.Ac={};this.Se=null}n.prototype.Jn=function(b,c,e){this.Fi({gc:b,ke:c,$c:e});this.Xf()};n.prototype.Fi=function(b){for(this.Yc.push(b);this.Yc.length>this.Hi;)this.Yc.shift().$c(null,null)};n.prototype.Xf=function(){if(0===this.Id&&0<this.Yc.length){var b=this.Se=this.Yc.shift(),c=b.gc[0].width,e=b.gc[0].height,f=b.gc.map(function(b){return b.data.buffer}),g=this.Jd.length;this.Id=g;if(1===g)this.Jd[0].postMessage({buffers:f,meta:b.ke, +imageOps:this.Re,width:c,height:e},f);else for(var h=4*Math.ceil(b.gc[0].data.length/4/g),k=0;k<g;++k){for(var n=k*h,A=[],y=0,J=f.length;y<J;++y)A.push(f[k].slice(n,n+h));this.Jd[k].postMessage({buffers:A,meta:b.ke,imageOps:this.Re,width:c,height:e},A)}}};n.prototype.$f=function(b,c){this.ap||(this.Ac[b]=c.data,--this.Id,0===this.Id&&this.Ii())};n.prototype.Ii=function(){var b=this.Se,c=this.Jd.length,e,f;if(1===c)e=new Uint8ClampedArray(this.Ac[0].buffer),f=this.Ac[0].meta;else{var g=b.gc[0].data.length; +e=new Uint8ClampedArray(g);f=Array(g);for(var g=4*Math.ceil(g/4/c),h=0;h<c;++h){var k=h*g;e.set(new Uint8ClampedArray(this.Ac[h].buffer),k);f[h]=this.Ac[h].meta}}this.Se=null;this.Ac={};b.$c(null,new ImageData(e,b.gc[0].width,b.gc[0].height),f);this.Xf()};c.kb=n},{}]},{},[1])(1)});wp=c.kb})();function vz(b){this.C=null;this.ea=m(b.operationType)?b.operationType:"pixel";this.qa=m(b.threads)?b.threads:1;for(var c=b.sources,d=c.length,e=Array(d),f=0;f<d;++f){var g=f,h=c[f],k=null;h instanceof Rh?(h=new K({source:h}),k=new Mp(h)):h instanceof tn&&(h=new I({source:h}),k=new Lp(h));e[g]=k}this.b=e;c=0;for(d=this.b.length;c<d;++c)x(this.b[c],"change",this.s,!1,this);this.c=Vi();this.$=new Pk(ne(1),ra(this.s,this));c=wz(this.b);d={};e=0;for(f=c.length;e<f;++e)d[ma(c[e].layer)]=c[e];this.f=this.i= +null;this.T={animate:!1,attributions:{},coordinateToPixelMatrix:Ad(),extent:null,focus:null,index:0,layerStates:d,layerStatesArray:c,logos:{},pixelRatio:1,pixelToCoordinateMatrix:Ad(),postRenderFunctions:[],size:[0,0],skippedFeatureUids:{},tileQueue:this.$,time:Date.now(),usedTiles:{},viewState:{rotation:0},viewHints:[],wantedTiles:{}};tn.call(this,{});m(b.operation)&&this.u(b.operation,b.lib)}w(vz,tn); +vz.prototype.u=function(b,c){this.C=new wp.ui({operation:b,Ek:"image"===this.ea,Kn:1,lib:c,threads:this.qa});this.s()};function xz(b,c,d){var e=b.i;return!e||b.a!==e.jo||d!==e.resolution||!Xd(c,e.extent)} +vz.prototype.mc=function(b,c,d,e){d=!0;for(var f,g=0,h=this.b.length;g<h;++g)if(f=this.b[g].b.ca(),"ready"!==f.A){d=!1;break}if(!d)return null;if(!xz(this,b,c))return this.f;d=this.c.canvas;f=Math.round(je(b)/c);g=Math.round(ge(b)/c);if(f!==d.width||g!==d.height)d.width=f,d.height=g;f=Cb(this.T);f.viewState=Cb(f.viewState);var g=ee(b),h=Math.round(je(b)/c),k=Math.round(ge(b)/c);f.extent=b;f.focus=ee(b);f.size[0]=h;f.size[1]=k;h=f.viewState;h.center=g;h.projection=e;h.resolution=c;this.f=e=new sn(b, +c,1,this.g,d,this.K.bind(this,f));this.i={extent:b,resolution:c,jo:this.a};return e}; +vz.prototype.K=function(b,c){for(var d=this.b.length,e=Array(d),f=0;f<d;++f){var g;var h=this.b[f],k=b;h.se(k,b.layerStatesArray[f]);if(g=h.ud()){var n=h.kf(),h=Math.round(n[12]),p=Math.round(n[13]),q=k.size[0],k=k.size[1];if(g instanceof Image){if(yz){var r=yz.canvas;r.width!==q||r.height!==k?yz=Vi(q,k):yz.clearRect(0,0,q,k)}else yz=Vi(q,k);r=Math.round(g.width*n[0]);n=Math.round(g.height*n[5]);yz.drawImage(g,h,p,r,n);g=yz.getImageData(0,0,q,k)}else g=g.getContext("2d").getImageData(-h,-p,q,k)}else g= +null;if(g)e[f]=g;else return}d={};C(this,new zz(Az,b,d));this.C.Jn(e,d,this.ba.bind(this,b,c));Qk(b.tileQueue,16,16)};vz.prototype.ba=function(b,c,d,e,f){d?c(d):null!==e&&(C(this,new zz(Bz,b,f)),xz(this,b.extent,b.viewState.resolution/b.pixelRatio)||this.c.putImageData(e,0,0),c(null))};var yz=null;function wz(b){return b.map(function(b){return ik(b.b)})}function zz(b,c,d){sc.call(this,b);this.extent=c.extent;this.resolution=c.viewState.resolution/c.pixelRatio;this.data=d}w(zz,sc); +var Az="beforeoperations",Bz="afteroperations";var Cz={terrain:{Za:"jpg",opaque:!0},"terrain-background":{Za:"jpg",opaque:!0},"terrain-labels":{Za:"png",opaque:!1},"terrain-lines":{Za:"png",opaque:!1},"toner-background":{Za:"png",opaque:!0},toner:{Za:"png",opaque:!0},"toner-hybrid":{Za:"png",opaque:!1},"toner-labels":{Za:"png",opaque:!1},"toner-lines":{Za:"png",opaque:!1},"toner-lite":{Za:"png",opaque:!0},watercolor:{Za:"jpg",opaque:!0}},Dz={terrain:{minZoom:4,maxZoom:18},toner:{minZoom:0,maxZoom:20},watercolor:{minZoom:3,maxZoom:16}}; +function Ez(b){var c=b.layer.indexOf("-"),d=Cz[b.layer];pz.call(this,{attributions:Fz,crossOrigin:"anonymous",maxZoom:Dz[-1==c?b.layer:b.layer.slice(0,c)].maxZoom,opaque:d.opaque,tileLoadFunction:b.tileLoadFunction,url:m(b.url)?b.url:"https://stamen-tiles-{a-d}.a.ssl.fastly.net/"+b.layer+"/{z}/{x}/{y}."+d.Za})}w(Ez,pz);var Fz=[new pg({html:'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.'}),rz];function Gz(b){b=m(b)?b:{};var c=m(b.params)?b.params:{};cz.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:b.projection,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction,tileUrlFunction:ra(this.xm,this),wrapX:m(b.wrapX)?b.wrapX:!0});var d=b.urls;!m(d)&&m(b.url)&&(d=bz(b.url));this.i=null!=d?d:[];this.f=c;this.l=Ld()}w(Gz,cz);l=Gz.prototype;l.um=function(){return this.f};l.dc=function(b,c,d){b=Gz.aa.dc.call(this,b,c,d);return 1==c?b:ld(b,c,this.c)}; +l.vm=function(){return this.i};l.wm=function(b){b=m(b)?bz(b):null;this.ah(b)};l.ah=function(b){this.i=null!=b?b:[];this.s()}; +l.xm=function(b,c,d){var e=this.tileGrid;null===e&&(e=Th(this,d));if(!(e.a.length<=b[0])){var f=Kh(e,b,this.l),g=md(e.Ja(b[0]),this.c);1!=c&&(g=ld(g,c,this.c));e={F:"image",FORMAT:"PNG32",TRANSPARENT:!0};Gb(e,this.f);var h=this.i;0==h.length?b=void 0:(d=d.a.split(":").pop(),e.SIZE=g[0]+","+g[1],e.BBOX=f.join(","),e.BBOXSR=d,e.IMAGESR=d,e.DPI=Math.round(90*c),b=1==h.length?h[0]:h[Xb((b[1]<<b[0])+b[2],h.length)],za(b,"/")||(b+="/"),za(b,"MapServer/")?b+="export":za(b,"ImageServer/")&&(b+="exportImage"), +b=mo(oo([b],e)));return b}};l.ym=function(b){Gb(this.f,b);this.s()};function Hz(b,c,d){Bh.call(this,b,2);this.f=c;this.c=d;this.b={}}w(Hz,Bh);Hz.prototype.Ta=function(b){b=m(b)?ma(b):-1;if(b in this.b)return this.b[b];var c=this.f,d=Vi(c[0],c[1]);d.strokeStyle="black";d.strokeRect(.5,.5,c[0]+.5,c[1]+.5);d.fillStyle="black";d.textAlign="center";d.textBaseline="middle";d.font="24px sans-serif";d.fillText(this.c,c[0]/2,c[1]/2);return this.b[b]=d.canvas};function Iz(b){Rh.call(this,{opaque:!1,projection:b.projection,tileGrid:b.tileGrid,wrapX:m(b.wrapX)?b.wrapX:!0})} +w(Iz,Rh);Iz.prototype.cc=function(b,c,d){var e=this.lb(b,c,d);if(yh(this.b,e))return this.b.get(e);var f=md(this.tileGrid.Ja(b));b=[b,c,d];c=Uh(this,b);c=null===c?"":eg(Uh(this,c));f=new Hz(b,f,c);this.b.set(e,f);return f};function Jz(b){cz.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,projection:Ae("EPSG:3857"),state:"loading",tileLoadFunction:b.tileLoadFunction,wrapX:m(b.wrapX)?b.wrapX:!0});(new Ty(b.url)).send(void 0,ra(this.f,this))}w(Jz,cz); +Jz.prototype.f=function(b){var c=Ae("EPSG:4326"),d=this.j,e;m(b.bounds)&&(e=me(b.bounds,Ee(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=Ph({extent:hg(d),maxZoom:g,minZoom:f});this.tileUrlFunction=Zy(b.tiles);if(m(b.attribution)&&null===this.g){c=m(e)?e:c.R();e={};for(var h;f<=g;++f)h=f.toString(),e[h]=[jg(d,c,f)];this.g=[new pg({html:b.attribution,tileRanges:e})]}Fh(this,"ready")};function Kz(b){Rh.call(this,{projection:Ae("EPSG:3857"),state:"loading"});this.l=m(b.preemptive)?b.preemptive:!0;this.f=az;this.i=void 0;(new Ty(b.url)).send(void 0,ra(this.Bm,this))}w(Kz,Rh);l=Kz.prototype;l.Oj=function(){return this.i};l.Zi=function(b,c,d,e,f){null===this.tileGrid?!0===f?qi(function(){d.call(e,null)}):d.call(e,null):(c=this.tileGrid.pd(b,c),Lz(this.cc(c[0],c[1],c[2],1,this.j),b,d,e,f))}; +l.Bm=function(b){var c=Ae("EPSG:4326"),d=this.j,e;m(b.bounds)&&(e=me(b.bounds,Ee(c,d)));var f=b.minzoom||0,g=b.maxzoom||22;this.tileGrid=d=Ph({extent:hg(d),maxZoom:g,minZoom:f});this.i=b.template;var h=b.grids;if(null!=h){this.f=Zy(h);if(m(b.attribution)){c=m(e)?e:c.R();for(e={};f<=g;++f)h=f.toString(),e[h]=[jg(d,c,f)];this.g=[new pg({html:b.attribution,tileRanges:e})]}Fh(this,"ready")}else Fh(this,"error")}; +l.cc=function(b,c,d,e,f){var g=this.lb(b,c,d);if(yh(this.b,g))return this.b.get(g);b=[b,c,d];c=Uh(this,b,f);e=this.f(c,e,f);e=new Mz(b,m(e)?0:4,m(e)?e:"",Kh(this.tileGrid,b),this.l);this.b.set(g,e);return e};l.Pf=function(b,c,d){b=this.lb(b,c,d);yh(this.b,b)&&this.b.get(b)};function Mz(b,c,d,e,f){Bh.call(this,b,c);this.j=d;this.b=e;this.l=f;this.f=this.g=this.c=null}w(Mz,Bh);l=Mz.prototype;l.Ta=function(){return null}; +function Nz(b,c){if(null===b.c||null===b.g||null==b.f)return null;var d=b.c[Math.floor((1-(c[1]-b.b[1])/(b.b[3]-b.b[1]))*b.c.length)];if(!ia(d))return null;d=d.charCodeAt(Math.floor((c[0]-b.b[0])/(b.b[2]-b.b[0])*d.length));93<=d&&d--;35<=d&&d--;d=b.g[d-32];return null!=d?b.f[d]:null}function Lz(b,c,d,e,f){0==b.state&&!0===f?(Vc(b,"change",function(){d.call(e,Nz(this,c))},!1,b),Oz(b)):!0===f?qi(function(){d.call(e,Nz(this,c))},b):d.call(e,Nz(b,c))}l.pb=function(){return this.j}; +l.ck=function(){this.state=3;Ch(this)};l.Am=function(b){this.c=b.grid;this.g=b.keys;this.f=b.data;this.state=4;Ch(this)};function Oz(b){0==b.state&&(b.state=1,(new Ty(b.j)).send(void 0,ra(b.Am,b),ra(b.ck,b)))}l.load=function(){this.l&&Oz(this)};function Pz(b){W.call(this,{attributions:b.attributions,logo:b.logo,projection:void 0,state:"ready",wrapX:b.wrapX});this.ea=m(b.format)?b.format:null;this.C=b.tileGrid;this.K=az;this.ba=m(b.tileLoadFunction)?b.tileLoadFunction:null;this.u={};m(b.tileUrlFunction)?(this.K=b.tileUrlFunction,this.s()):m(b.urls)?(this.K=Zy(b.urls),this.s()):m(b.url)&&(this.K=Zy(bz(b.url)),this.s())}w(Pz,W);l=Pz.prototype;l.clear=function(){yb(this.u)}; +function Qz(b,c,d,e){var f=b.u;b=b.C.pd(c,d);f=f[b[0]+"/"+b[1]+"/"+b[2]];if(m(f))for(b=0,d=f.length;b<d;++b){var g=f[b];if(g.Z().Ye(c)&&e.call(void 0,g))break}}l.Ib=function(b,c,d,e){var f=this.C,g=this.u;c=Oh(f,c);b=jg(f,b,c);for(var h,f=b.a;f<=b.f;++f)for(h=b.b;h<=b.c;++h){var k=g[c+"/"+f+"/"+h];if(m(k)){var n,p;n=0;for(p=k.length;n<p;++n){var q=d.call(e,k[n]);if(q)return q}}}};l.Lc=function(){var b=this.u,c=[],d;for(d in b)cb(c,b[d]);return c}; +l.nj=function(b,c){var d=[];Qz(this,b,c,function(b){d.push(b)});return d};function Rz(b,c,d){var e=b.C;b.G&&d.c&&(c=fg(c,e,d));return ig(c,e)?c:null}l.hc=function(b,c,d){function e(b,c){h[b]=c;this.s()}var f=this.C,g=this.K,h=this.u,k=Oh(f,c),f=jg(f,b,k),n=[k,0,0],p,q;for(p=f.a;p<=f.f;++p)for(q=f.b;q<=f.c;++q){var r=k+"/"+p+"/"+q;if(!(r in h)){n[1]=p;n[2]=q;var t=Rz(this,n,d),t=null===t?void 0:g(t,1,d);m(t)&&(h[r]=[],r=sa(e,r),null===this.ba?sp(t,this.ea,r).call(this,b,c,d):this.ba(t,ra(r,this)))}}};function Sz(b){b=m(b)?b:{};var c=m(b.params)?b.params:{};cz.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,opaque:!Ab(c,"TRANSPARENT",!0),projection:b.projection,tileGrid:b.tileGrid,tileLoadFunction:b.tileLoadFunction,tileUrlFunction:ra(this.Gm,this),wrapX:m(b.wrapX)?b.wrapX:!0});var d=b.urls;!m(d)&&m(b.url)&&(d=bz(b.url));this.i=null!=d?d:[];this.v=m(b.gutter)?b.gutter:0;this.f=c;this.l=!0;this.u=b.serverType;this.K=m(b.hidpi)?b.hidpi:!0;this.C="";Tz(this);this.T=Ld(); +Uz(this)}w(Sz,cz);l=Sz.prototype; +l.Cm=function(b,c,d,e){d=Ae(d);var f=this.tileGrid;null===f&&(f=Th(this,d));c=f.pd(b,c);if(!(f.a.length<=c[0])){var g=f.ua(c[0]),h=Kh(f,c,this.T),f=md(f.Ja(c[0]),this.c),k=this.v;0!==k&&(f=kd(f,k,this.c),h=Pd(h,g*k,h));k={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetFeatureInfo",FORMAT:"image/png",TRANSPARENT:!0,QUERY_LAYERS:this.f.LAYERS};Gb(k,this.f,e);e=Math.floor((h[3]-b[1])/g);k[this.l?"I":"X"]=Math.floor((b[0]-h[0])/g);k[this.l?"J":"Y"]=e;return Vz(this,c,f,h,1,d,k)}};l.Sd=function(){return this.v}; +l.lb=function(b,c,d){return this.C+Sz.aa.lb.call(this,b,c,d)};l.Dm=function(){return this.f}; +function Vz(b,c,d,e,f,g,h){var k=b.i;if(0!=k.length){h.WIDTH=d[0];h.HEIGHT=d[1];h[b.l?"CRS":"SRS"]=g.a;"STYLES"in b.f||(h.STYLES=new String(""));if(1!=f)switch(b.u){case "geoserver":d=90*f+.5|0;h.FORMAT_OPTIONS=m(h.FORMAT_OPTIONS)?h.FORMAT_OPTIONS+(";dpi:"+d):"dpi:"+d;break;case "mapserver":h.MAP_RESOLUTION=90*f;break;case "carmentaserver":case "qgis":h.DPI=90*f}g=g.f;b.l&&"ne"==g.substr(0,2)&&(b=e[0],e[0]=e[1],e[1]=b,b=e[2],e[2]=e[3],e[3]=b);h.BBOX=e.join(",");return mo(oo([1==k.length?k[0]:k[Xb((c[1]<< +c[0])+c[2],k.length)]],h))}}l.dc=function(b,c,d){b=Sz.aa.dc.call(this,b,c,d);return 1!=c&&this.K&&m(this.u)?ld(b,c,this.c):b};l.Em=function(){return this.i};function Tz(b){var c=0,d=[],e,f;e=0;for(f=b.i.length;e<f;++e)d[c++]=b.i[e];for(var g in b.f)d[c++]=g+"-"+b.f[g];b.C=d.join("#")}l.Fm=function(b){b=m(b)?bz(b):null;this.bh(b)};l.bh=function(b){this.i=null!=b?b:[];Tz(this);this.s()}; +l.Gm=function(b,c,d){var e=this.tileGrid;null===e&&(e=Th(this,d));if(!(e.a.length<=b[0])){1==c||this.K&&m(this.u)||(c=1);var f=e.ua(b[0]),g=Kh(e,b,this.T),e=md(e.Ja(b[0]),this.c),h=this.v;0!==h&&(e=kd(e,h,this.c),g=Pd(g,f*h,g));1!=c&&(e=ld(e,c,this.c));f={SERVICE:"WMS",VERSION:"1.3.0",REQUEST:"GetMap",FORMAT:"image/png",TRANSPARENT:!0};Gb(f,this.f);return Vz(this,b,e,g,c,d,f)}};l.Hm=function(b){Gb(this.f,b);Tz(this);Uz(this);this.s()};function Uz(b){b.l=0<=Na(Ab(b.f,"VERSION","1.3.0"),"1.3")};function Wz(b){this.j=b.matrixIds;Gh.call(this,{extent:b.extent,origin:b.origin,origins:b.origins,resolutions:b.resolutions,tileSize:b.tileSize,tileSizes:b.tileSizes,sizes:b.sizes})}w(Wz,Gh);Wz.prototype.B=function(){return this.j}; +function Xz(b,c){var d=[],e=[],f=[],g=[],h=[],k;k=Ae(b.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var n=k.Vd(),p="ne"==k.f.substr(0,2);gb(b.TileMatrix,function(b,c){return c.ScaleDenominator-b.ScaleDenominator});Sa(b.TileMatrix,function(b){e.push(b.Identifier);var c=2.8E-4*b.ScaleDenominator/n,k=b.TileWidth,v=b.TileHeight;p?f.push([b.TopLeftCorner[1],b.TopLeftCorner[0]]):f.push(b.TopLeftCorner);d.push(c);g.push(k==v?k:[k,v]);h.push([b.MatrixWidth,-b.MatrixHeight])});return new Wz({extent:c, +origins:f,resolutions:d,matrixIds:e,tileSizes:g,sizes:h})};function Yz(b){function c(b){b="KVP"==e?mo(oo([b],g)):b.replace(/\{(\w+?)\}/g,function(b,c){return c.toLowerCase()in g?g[c.toLowerCase()]:b});return function(c){if(null!==c){var d={TileMatrix:f.j[c[0]],TileCol:c[1],TileRow:-c[2]-1};Gb(d,h);c=b;return c="KVP"==e?mo(oo([c],d)):c.replace(/\{(\w+?)\}/g,function(b,c){return d[c]})}}}this.T=m(b.version)?b.version:"1.0.0";this.u=m(b.format)?b.format:"image/jpeg";this.f=m(b.dimensions)?b.dimensions:{};this.v="";Zz(this);this.C=b.layer;this.l=b.matrixSet; +this.K=b.style;var d=b.urls;!m(d)&&m(b.url)&&(d=bz(b.url));this.i=null!=d?d:[];var e=this.$=m(b.requestEncoding)?b.requestEncoding:"KVP",f=b.tileGrid,g={layer:this.C,style:this.K,tilematrixset:this.l};"KVP"==e&&Gb(g,{Service:"WMTS",Request:"GetTile",Version:this.T,Format:this.u});var h=this.f,d=0<this.i.length?$y(Ua(this.i,c)):az;cz.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,projection:b.projection,tileClass:b.tileClass,tileGrid:f,tileLoadFunction:b.tileLoadFunction, +tilePixelRatio:b.tilePixelRatio,tileUrlFunction:d,wrapX:m(b.wrapX)?b.wrapX:!1})}w(Yz,cz);l=Yz.prototype;l.lj=function(){return this.f};l.pj=function(){return this.u};l.lb=function(b,c,d){return this.v+Yz.aa.lb.call(this,b,c,d)};l.Im=function(){return this.C};l.Bj=function(){return this.l};l.Mj=function(){return this.$};l.Jm=function(){return this.K};l.Km=function(){return this.i};l.Sj=function(){return this.T};function Zz(b){var c=0,d=[],e;for(e in b.f)d[c++]=e+"-"+b.f[e];b.v=d.join("/")} +l.Ko=function(b){Gb(this.f,b);Zz(this);this.s()};function $z(b){b=m(b)?b:{};var c=b.size,d=c[0],e=c[1],f=[],g=256;switch(m(b.tierSizeCalculation)?b.tierSizeCalculation:"default"){case "default":for(;d>g||e>g;)f.push([Math.ceil(d/g),Math.ceil(e/g)]),g+=g;break;case "truncated":for(;d>g||e>g;)f.push([Math.ceil(d/g),Math.ceil(e/g)]),d>>=1,e>>=1}f.push([1,1]);f.reverse();for(var g=[1],h=[0],e=1,d=f.length;e<d;e++)g.push(1<<e),h.push(f[e-1][0]*f[e-1][1]+h[e-1]);g.reverse();var c=[0,-c[1],c[0],0],c=new Gh({extent:c,origin:de(c),resolutions:g}),k=b.url; +cz.call(this,{attributions:b.attributions,crossOrigin:b.crossOrigin,logo:b.logo,tileClass:aA,tileGrid:c,tileUrlFunction:function(b){if(null!==b){var c=b[0],d=b[1];b=-b[2]-1;return k+"TileGroup"+((d+b*f[c][0]+h[c])/256|0)+"/"+c+"-"+d+"-"+b+".jpg"}}})}w($z,cz);function aA(b,c,d,e,f){zx.call(this,b,c,d,e,f);this.f={}}w(aA,zx); +aA.prototype.Ta=function(b){var c=m(b)?ma(b).toString():"";if(c in this.f)return this.f[c];b=aA.aa.Ta.call(this,b);if(2==this.state){if(256==b.width&&256==b.height)return this.f[c]=b;var d=Vi(256,256);d.drawImage(b,0,0);return this.f[c]=d.canvas}return b};function bA(b){b=m(b)?b:{};this.b=m(b.initialSize)?b.initialSize:256;this.c=m(b.maxSize)?b.maxSize:m(va)?va:2048;this.a=m(b.space)?b.space:1;this.g=[new cA(this.b,this.a)];this.f=this.b;this.i=[new cA(this.f,this.a)]}bA.prototype.add=function(b,c,d,e,f,g){if(c+this.a>this.c||d+this.a>this.c)return null;e=dA(this,!1,b,c,d,e,g);if(null===e)return null;b=dA(this,!0,b,c,d,m(f)?f:qe,g);return{offsetX:e.offsetX,offsetY:e.offsetY,image:e.image,Cg:b.image}}; +function dA(b,c,d,e,f,g,h){var k=c?b.i:b.g,n,p,q;p=0;for(q=k.length;p<q;++p){n=k[p];n=n.add(d,e,f,g,h);if(null!==n)return n;null===n&&p===q-1&&(c?(n=Math.min(2*b.f,b.c),b.f=n):(n=Math.min(2*b.b,b.c),b.b=n),n=new cA(n,b.a),k.push(n),++q)}}function cA(b,c){this.a=c;this.b=[{x:0,y:0,width:b,height:b}];this.f={};this.c=Pg("CANVAS");this.c.width=b;this.c.height=b;this.g=this.c.getContext("2d")}cA.prototype.get=function(b){return Ab(this.f,b,null)}; +cA.prototype.add=function(b,c,d,e,f){var g,h,k;h=0;for(k=this.b.length;h<k;++h)if(g=this.b[h],g.width>=c+this.a&&g.height>=d+this.a)return k={offsetX:g.x+this.a,offsetY:g.y+this.a,image:this.c},this.f[b]=k,e.call(f,this.g,g.x+this.a,g.y+this.a),b=h,c=c+this.a,d=d+this.a,f=e=void 0,g.width-c>g.height-d?(e={x:g.x+c,y:g.y,width:g.width-c,height:g.height},f={x:g.x,y:g.y+d,width:c,height:g.height-d},eA(this,b,e,f)):(e={x:g.x+c,y:g.y,width:g.width-c,height:d},f={x:g.x,y:g.y+d,width:g.width,height:g.height- +d},eA(this,b,e,f)),k;return null};function eA(b,c,d,e){c=[c,1];0<d.width&&0<d.height&&c.push(d);0<e.width&&0<e.height&&c.push(e);b.b.splice.apply(b.b,c)};function fA(b){this.A=this.f=this.g=null;this.l=m(b.fill)?b.fill:null;this.K=[0,0];this.a=b.points;this.c=m(b.radius)?b.radius:b.radius1;this.i=m(b.radius2)?b.radius2:this.c;this.j=m(b.angle)?b.angle:0;this.b=m(b.stroke)?b.stroke:null;this.C=this.U=this.da=null;var c=b.atlasManager,d="",e="",f=0,g=null,h,k=0;null!==this.b&&(h=yg(this.b.a),k=this.b.b,m(k)||(k=1),g=this.b.c,dj||(g=null),e=this.b.g,m(e)||(e="round"),d=this.b.f,m(d)||(d="round"),f=this.b.i,m(f)||(f=10));var n=2*(this.c+k)+1,d={strokeStyle:h, +Ad:k,size:n,lineCap:d,lineDash:g,lineJoin:e,miterLimit:f};if(m(c)){var n=Math.round(n),e=null===this.l,p;e&&(p=ra(this.fh,this,d));f=this.Ab();p=c.add(f,n,n,ra(this.gh,this,d),p);this.f=p.image;this.K=[p.offsetX,p.offsetY];c=p.image.width;this.A=e?p.Cg:this.f}else this.f=Pg("CANVAS"),this.f.height=n,this.f.width=n,c=n=this.f.width,p=this.f.getContext("2d"),this.gh(d,p,0,0),null===this.l?(p=this.A=Pg("CANVAS"),p.height=d.size,p.width=d.size,p=p.getContext("2d"),this.fh(d,p,0,0)):this.A=this.f;this.da= +[n/2,n/2];this.U=[n,n];this.C=[c,c];Bk.call(this,{opacity:1,rotateWithView:!1,rotation:m(b.rotation)?b.rotation:0,scale:1,snapToPixel:m(b.snapToPixel)?b.snapToPixel:!0})}w(fA,Bk);l=fA.prototype;l.zb=function(){return this.da};l.Pm=function(){return this.j};l.Qm=function(){return this.l};l.ue=function(){return this.A};l.Tb=function(){return this.f};l.Td=function(){return this.C};l.wd=function(){return 2};l.Eb=function(){return this.K};l.Rm=function(){return this.a};l.Sm=function(){return this.c}; +l.Lj=function(){return this.i};l.fb=function(){return this.U};l.Tm=function(){return this.b};l.rf=ca;l.load=ca;l.Of=ca; +l.gh=function(b,c,d,e){var f;c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.i!==this.c&&(this.a*=2);for(d=0;d<=this.a;d++)e=2*d*Math.PI/this.a-Math.PI/2+this.j,f=0===d%2?this.c:this.i,c.lineTo(b.size/2+f*Math.cos(e),b.size/2+f*Math.sin(e));null!==this.l&&(c.fillStyle=yg(this.l.a),c.fill());null!==this.b&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Ad,null===b.lineDash||c.setLineDash(b.lineDash),c.lineCap=b.lineCap,c.lineJoin=b.lineJoin,c.miterLimit=b.miterLimit,c.stroke());c.closePath()}; +l.fh=function(b,c,d,e){c.setTransform(1,0,0,1,0,0);c.translate(d,e);c.beginPath();this.i!==this.c&&(this.a*=2);var f;for(d=0;d<=this.a;d++)f=2*d*Math.PI/this.a-Math.PI/2+this.j,e=0===d%2?this.c:this.i,c.lineTo(b.size/2+e*Math.cos(f),b.size/2+e*Math.sin(f));c.fillStyle=El;c.fill();null!==this.b&&(c.strokeStyle=b.strokeStyle,c.lineWidth=b.Ad,null===b.lineDash||c.setLineDash(b.lineDash),c.stroke());c.closePath()}; +l.Ab=function(){var b=null===this.b?"-":this.b.Ab(),c=null===this.l?"-":this.l.Ab();if(null===this.g||b!=this.g[1]||c!=this.g[2]||this.c!=this.g[3]||this.i!=this.g[4]||this.j!=this.g[5]||this.a!=this.g[6])this.g=["r"+b+c+(m(this.c)?this.c.toString():"-")+(m(this.i)?this.i.toString():"-")+(m(this.j)?this.j.toString():"-")+(m(this.a)?this.a.toString():"-"),b,c,this.c,this.i,this.j,this.a];return this.g[0]};u("ol.animation.bounce",function(b){var c=b.resolution,d=m(b.start)?b.start:ua(),e=m(b.duration)?b.duration:1E3,f=m(b.easing)?b.easing:Yf;return function(b,h){if(h.time<d)return h.animate=!0,h.viewHints[0]+=1,!0;if(h.time<d+e){var k=f((h.time-d)/e),n=c-h.viewState.resolution;h.animate=!0;h.viewState.resolution+=k*n;h.viewHints[0]+=1;return!0}return!1}},OPENLAYERS);u("ol.animation.pan",Zf,OPENLAYERS);u("ol.animation.rotate",$f,OPENLAYERS);u("ol.animation.zoom",ag,OPENLAYERS); +u("ol.Attribution",pg,OPENLAYERS);pg.prototype.getHTML=pg.prototype.c;qg.prototype.element=qg.prototype.element;u("ol.Collection",rg,OPENLAYERS);rg.prototype.clear=rg.prototype.clear;rg.prototype.extend=rg.prototype.sf;rg.prototype.forEach=rg.prototype.forEach;rg.prototype.getArray=rg.prototype.Yk;rg.prototype.item=rg.prototype.item;rg.prototype.getLength=rg.prototype.Rb;rg.prototype.insertAt=rg.prototype.fe;rg.prototype.pop=rg.prototype.pop;rg.prototype.push=rg.prototype.push; +rg.prototype.remove=rg.prototype.remove;rg.prototype.removeAt=rg.prototype.Kf;rg.prototype.setAt=rg.prototype.lo;u("ol.coordinate.add",nd,OPENLAYERS);u("ol.coordinate.createStringXY",function(b){return function(c){return vd(c,b)}},OPENLAYERS);u("ol.coordinate.format",qd,OPENLAYERS);u("ol.coordinate.rotate",sd,OPENLAYERS);u("ol.coordinate.toStringHDMS",function(b){return m(b)?pd(b[1],"NS")+" "+pd(b[0],"EW"):""},OPENLAYERS);u("ol.coordinate.toStringXY",vd,OPENLAYERS);u("ol.DeviceOrientation",Fr,OPENLAYERS); +Fr.prototype.getAlpha=Fr.prototype.ej;Fr.prototype.getBeta=Fr.prototype.hj;Fr.prototype.getGamma=Fr.prototype.qj;Fr.prototype.getHeading=Fr.prototype.Zk;Fr.prototype.getTracking=Fr.prototype.Ig;Fr.prototype.setTracking=Fr.prototype.tf;u("ol.easing.easeIn",function(b){return Math.pow(b,3)},OPENLAYERS);u("ol.easing.easeOut",Vf,OPENLAYERS);u("ol.easing.inAndOut",Wf,OPENLAYERS);u("ol.easing.linear",Xf,OPENLAYERS);u("ol.easing.upAndDown",Yf,OPENLAYERS);u("ol.extent.boundingExtent",Kd,OPENLAYERS); +u("ol.extent.buffer",Pd,OPENLAYERS);u("ol.extent.containsCoordinate",Sd,OPENLAYERS);u("ol.extent.containsExtent",Ud,OPENLAYERS);u("ol.extent.containsXY",Td,OPENLAYERS);u("ol.extent.createEmpty",Ld,OPENLAYERS);u("ol.extent.equals",Xd,OPENLAYERS);u("ol.extent.extend",Yd,OPENLAYERS);u("ol.extent.getBottomLeft",ae,OPENLAYERS);u("ol.extent.getBottomRight",be,OPENLAYERS);u("ol.extent.getCenter",ee,OPENLAYERS);u("ol.extent.getHeight",ge,OPENLAYERS);u("ol.extent.getIntersection",he,OPENLAYERS); +u("ol.extent.getSize",function(b){return[b[2]-b[0],b[3]-b[1]]},OPENLAYERS);u("ol.extent.getTopLeft",de,OPENLAYERS);u("ol.extent.getTopRight",ce,OPENLAYERS);u("ol.extent.getWidth",je,OPENLAYERS);u("ol.extent.intersects",ie,OPENLAYERS);u("ol.extent.isEmpty",ke,OPENLAYERS);u("ol.extent.applyTransform",me,OPENLAYERS);u("ol.Feature",Bn,OPENLAYERS);Bn.prototype.clone=Bn.prototype.clone;Bn.prototype.getGeometry=Bn.prototype.Z;Bn.prototype.getId=Bn.prototype.tj;Bn.prototype.getGeometryName=Bn.prototype.sj; +Bn.prototype.getStyle=Bn.prototype.al;Bn.prototype.getStyleFunction=Bn.prototype.bl;Bn.prototype.setGeometry=Bn.prototype.Sa;Bn.prototype.setStyle=Bn.prototype.uf;Bn.prototype.setId=Bn.prototype.Xb;Bn.prototype.setGeometryName=Bn.prototype.Sc;u("ol.featureloader.xhr",tp,OPENLAYERS);u("ol.Geolocation",px,OPENLAYERS);px.prototype.getAccuracy=px.prototype.cj;px.prototype.getAccuracyGeometry=px.prototype.dj;px.prototype.getAltitude=px.prototype.fj;px.prototype.getAltitudeAccuracy=px.prototype.gj; +px.prototype.getHeading=px.prototype.dl;px.prototype.getPosition=px.prototype.el;px.prototype.getProjection=px.prototype.Jg;px.prototype.getSpeed=px.prototype.Nj;px.prototype.getTracking=px.prototype.Kg;px.prototype.getTrackingOptions=px.prototype.vg;px.prototype.setProjection=px.prototype.Lg;px.prototype.setTracking=px.prototype.le;px.prototype.setTrackingOptions=px.prototype.Oh;u("ol.Graticule",tx,OPENLAYERS);tx.prototype.getMap=tx.prototype.hl;tx.prototype.getMeridians=tx.prototype.Cj; +tx.prototype.getParallels=tx.prototype.Hj;tx.prototype.setMap=tx.prototype.setMap;u("ol.has.DEVICE_PIXEL_RATIO",cj,OPENLAYERS);u("ol.has.CANVAS",ej,OPENLAYERS);u("ol.has.DEVICE_ORIENTATION",fj,OPENLAYERS);u("ol.has.GEOLOCATION",gj,OPENLAYERS);u("ol.has.TOUCH",hj,OPENLAYERS);u("ol.has.WEBGL",bj,OPENLAYERS);yx.prototype.getImage=yx.prototype.a;zx.prototype.getImage=zx.prototype.Ta;u("ol.Kinetic",Rk,OPENLAYERS);u("ol.loadingstrategy.all",up,OPENLAYERS); +u("ol.loadingstrategy.bbox",function(b){return[b]},OPENLAYERS);u("ol.loadingstrategy.tile",function(b){return function(c,d){var e=Oh(b,d),f=jg(b,c,e),g=[],e=[e,0,0];for(e[1]=f.a;e[1]<=f.f;++e[1])for(e[2]=f.b;e[2]<=f.c;++e[2])g.push(Kh(b,e));return g}},OPENLAYERS);u("ol.Map",X,OPENLAYERS);X.prototype.addControl=X.prototype.Li;X.prototype.addInteraction=X.prototype.Mi;X.prototype.addLayer=X.prototype.bg;X.prototype.addOverlay=X.prototype.cg;X.prototype.beforeRender=X.prototype.Oa; +X.prototype.forEachFeatureAtPixel=X.prototype.bf;X.prototype.forEachLayerAtPixel=X.prototype.ll;X.prototype.hasFeatureAtPixel=X.prototype.Dk;X.prototype.getEventCoordinate=X.prototype.mj;X.prototype.getEventPixel=X.prototype.Rd;X.prototype.getTarget=X.prototype.vf;X.prototype.getTargetElement=X.prototype.od;X.prototype.getCoordinateFromPixel=X.prototype.ta;X.prototype.getControls=X.prototype.kj;X.prototype.getOverlays=X.prototype.Gj;X.prototype.getInteractions=X.prototype.uj; +X.prototype.getLayerGroup=X.prototype.bc;X.prototype.getLayers=X.prototype.Mg;X.prototype.getPixelFromCoordinate=X.prototype.ya;X.prototype.getSize=X.prototype.Ca;X.prototype.getView=X.prototype.Y;X.prototype.getViewport=X.prototype.Tj;X.prototype.renderSync=X.prototype.ho;X.prototype.render=X.prototype.render;X.prototype.removeControl=X.prototype.$n;X.prototype.removeInteraction=X.prototype.ao;X.prototype.removeLayer=X.prototype.bo;X.prototype.removeOverlay=X.prototype.co; +X.prototype.setLayerGroup=X.prototype.Kh;X.prototype.setSize=X.prototype.Mf;X.prototype.setTarget=X.prototype.nl;X.prototype.setView=X.prototype.zo;X.prototype.updateSize=X.prototype.Uc;Tj.prototype.originalEvent=Tj.prototype.originalEvent;Tj.prototype.pixel=Tj.prototype.pixel;Tj.prototype.coordinate=Tj.prototype.coordinate;Tj.prototype.dragging=Tj.prototype.dragging;Tj.prototype.preventDefault=Tj.prototype.preventDefault;Tj.prototype.stopPropagation=Tj.prototype.ob;vh.prototype.map=vh.prototype.map; +vh.prototype.frameState=vh.prototype.frameState;fd.prototype.key=fd.prototype.key;fd.prototype.oldValue=fd.prototype.oldValue;u("ol.Object",gd,OPENLAYERS);gd.prototype.get=gd.prototype.get;gd.prototype.getKeys=gd.prototype.O;gd.prototype.getProperties=gd.prototype.P;gd.prototype.set=gd.prototype.set;gd.prototype.setProperties=gd.prototype.I;gd.prototype.unset=gd.prototype.S;u("ol.Observable",dd,OPENLAYERS);u("ol.Observable.unByKey",ed,OPENLAYERS);dd.prototype.changed=dd.prototype.s; +dd.prototype.getRevision=dd.prototype.L;dd.prototype.on=dd.prototype.D;dd.prototype.once=dd.prototype.M;dd.prototype.un=dd.prototype.J;dd.prototype.unByKey=dd.prototype.N;u("ol.inherits",w,OPENLAYERS);u("ol.Overlay",cr,OPENLAYERS);cr.prototype.getElement=cr.prototype.me;cr.prototype.getMap=cr.prototype.ne;cr.prototype.getOffset=cr.prototype.rg;cr.prototype.getPosition=cr.prototype.Ng;cr.prototype.getPositioning=cr.prototype.ug;cr.prototype.setElement=cr.prototype.Hh;cr.prototype.setMap=cr.prototype.setMap; +cr.prototype.setOffset=cr.prototype.Mh;cr.prototype.setPosition=cr.prototype.wf;cr.prototype.setPositioning=cr.prototype.Nh;u("ol.size.toSize",md,OPENLAYERS);Bh.prototype.getTileCoord=Bh.prototype.i;u("ol.View",Of,OPENLAYERS);Of.prototype.constrainCenter=Of.prototype.Nd;Of.prototype.constrainResolution=Of.prototype.constrainResolution;Of.prototype.constrainRotation=Of.prototype.constrainRotation;Of.prototype.getCenter=Of.prototype.Ka;Of.prototype.calculateExtent=Of.prototype.Zc; +Of.prototype.getProjection=Of.prototype.ol;Of.prototype.getResolution=Of.prototype.Da;Of.prototype.getRotation=Of.prototype.Ea;Of.prototype.getZoom=Of.prototype.Wj;Of.prototype.fit=Of.prototype.af;Of.prototype.centerOn=Of.prototype.Vi;Of.prototype.rotate=Of.prototype.rotate;Of.prototype.setCenter=Of.prototype.eb;Of.prototype.setResolution=Of.prototype.Yb;Of.prototype.setRotation=Of.prototype.oe;Of.prototype.setZoom=Of.prototype.Do;u("ol.xml.getAllTextContent",Mo,OPENLAYERS);u("ol.xml.parse",fp,OPENLAYERS); +u("ol.webgl.Context",hq,OPENLAYERS);hq.prototype.getGL=hq.prototype.kn;hq.prototype.getHitDetectionFramebuffer=hq.prototype.jf;hq.prototype.useProgram=hq.prototype.Ae;u("ol.tilegrid.TileGrid",Gh,OPENLAYERS);Gh.prototype.getMaxZoom=Gh.prototype.pg;Gh.prototype.getMinZoom=Gh.prototype.qg;Gh.prototype.getOrigin=Gh.prototype.Nc;Gh.prototype.getResolution=Gh.prototype.ua;Gh.prototype.getResolutions=Gh.prototype.ih;Gh.prototype.getTileCoordForCoordAndResolution=Gh.prototype.pd; +Gh.prototype.getTileCoordForCoordAndZ=Gh.prototype.Zd;Gh.prototype.getTileSize=Gh.prototype.Ja;u("ol.tilegrid.createXYZ",Ph,OPENLAYERS);u("ol.tilegrid.WMTS",Wz,OPENLAYERS);Wz.prototype.getMatrixIds=Wz.prototype.B;u("ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet",Xz,OPENLAYERS);u("ol.style.AtlasManager",bA,OPENLAYERS);u("ol.style.Circle",Il,OPENLAYERS);Il.prototype.getAnchor=Il.prototype.zb;Il.prototype.getFill=Il.prototype.Lm;Il.prototype.getImage=Il.prototype.Tb;Il.prototype.getOrigin=Il.prototype.Eb; +Il.prototype.getRadius=Il.prototype.Mm;Il.prototype.getSize=Il.prototype.fb;Il.prototype.getStroke=Il.prototype.Nm;u("ol.style.Fill",Hl,OPENLAYERS);Hl.prototype.getColor=Hl.prototype.c;Hl.prototype.setColor=Hl.prototype.f;u("ol.style.Icon",Ck,OPENLAYERS);Ck.prototype.getAnchor=Ck.prototype.zb;Ck.prototype.getImage=Ck.prototype.Tb;Ck.prototype.getOrigin=Ck.prototype.Eb;Ck.prototype.getSrc=Ck.prototype.Om;Ck.prototype.getSize=Ck.prototype.fb;u("ol.style.Image",Bk,OPENLAYERS); +Bk.prototype.getOpacity=Bk.prototype.ve;Bk.prototype.getRotateWithView=Bk.prototype.Xd;Bk.prototype.getRotation=Bk.prototype.we;Bk.prototype.getScale=Bk.prototype.xe;Bk.prototype.getSnapToPixel=Bk.prototype.Yd;Bk.prototype.setRotation=Bk.prototype.ye;Bk.prototype.setScale=Bk.prototype.ze;u("ol.style.RegularShape",fA,OPENLAYERS);fA.prototype.getAnchor=fA.prototype.zb;fA.prototype.getAngle=fA.prototype.Pm;fA.prototype.getFill=fA.prototype.Qm;fA.prototype.getImage=fA.prototype.Tb; +fA.prototype.getOrigin=fA.prototype.Eb;fA.prototype.getPoints=fA.prototype.Rm;fA.prototype.getRadius=fA.prototype.Sm;fA.prototype.getRadius2=fA.prototype.Lj;fA.prototype.getSize=fA.prototype.fb;fA.prototype.getStroke=fA.prototype.Tm;u("ol.style.Stroke",Dl,OPENLAYERS);Dl.prototype.getColor=Dl.prototype.Um;Dl.prototype.getLineCap=Dl.prototype.xj;Dl.prototype.getLineDash=Dl.prototype.Vm;Dl.prototype.getLineJoin=Dl.prototype.yj;Dl.prototype.getMiterLimit=Dl.prototype.Dj;Dl.prototype.getWidth=Dl.prototype.Wm; +Dl.prototype.setColor=Dl.prototype.Xm;Dl.prototype.setLineCap=Dl.prototype.qo;Dl.prototype.setLineDash=Dl.prototype.Ym;Dl.prototype.setLineJoin=Dl.prototype.ro;Dl.prototype.setMiterLimit=Dl.prototype.so;Dl.prototype.setWidth=Dl.prototype.Ao;u("ol.style.Style",Jl,OPENLAYERS);Jl.prototype.getGeometry=Jl.prototype.Z;Jl.prototype.getGeometryFunction=Jl.prototype.rj;Jl.prototype.getFill=Jl.prototype.Zm;Jl.prototype.getImage=Jl.prototype.$m;Jl.prototype.getStroke=Jl.prototype.an;Jl.prototype.getText=Jl.prototype.bn; +Jl.prototype.getZIndex=Jl.prototype.Vj;Jl.prototype.setGeometry=Jl.prototype.hh;Jl.prototype.setZIndex=Jl.prototype.Co;u("ol.style.Text",Et,OPENLAYERS);Et.prototype.getFont=Et.prototype.oj;Et.prototype.getOffsetX=Et.prototype.Ej;Et.prototype.getOffsetY=Et.prototype.Fj;Et.prototype.getFill=Et.prototype.cn;Et.prototype.getRotation=Et.prototype.dn;Et.prototype.getScale=Et.prototype.en;Et.prototype.getStroke=Et.prototype.fn;Et.prototype.getText=Et.prototype.gn;Et.prototype.getTextAlign=Et.prototype.Pj; +Et.prototype.getTextBaseline=Et.prototype.Qj;Et.prototype.setFont=Et.prototype.no;Et.prototype.setFill=Et.prototype.mo;Et.prototype.setRotation=Et.prototype.hn;Et.prototype.setScale=Et.prototype.jn;Et.prototype.setStroke=Et.prototype.vo;Et.prototype.setText=Et.prototype.wo;Et.prototype.setTextAlign=Et.prototype.xo;Et.prototype.setTextBaseline=Et.prototype.yo;u("ol.Sphere",ve,OPENLAYERS);ve.prototype.geodesicArea=ve.prototype.b;ve.prototype.haversineDistance=ve.prototype.a; +u("ol.source.BingMaps",ez,OPENLAYERS);u("ol.source.BingMaps.TOS_ATTRIBUTION",fz,OPENLAYERS);u("ol.source.Cluster",gz,OPENLAYERS);gz.prototype.getSource=gz.prototype.ea;u("ol.source.ImageCanvas",An,OPENLAYERS);u("ol.source.ImageMapGuide",jz,OPENLAYERS);jz.prototype.getParams=jz.prototype.em;jz.prototype.getImageLoadFunction=jz.prototype.dm;jz.prototype.updateParams=jz.prototype.hm;jz.prototype.setImageLoadFunction=jz.prototype.gm;u("ol.source.Image",tn,OPENLAYERS);vn.prototype.image=vn.prototype.image; +u("ol.source.ImageStatic",kz,OPENLAYERS);u("ol.source.ImageVector",Jp,OPENLAYERS);Jp.prototype.getSource=Jp.prototype.im;Jp.prototype.getStyle=Jp.prototype.jm;Jp.prototype.getStyleFunction=Jp.prototype.km;Jp.prototype.setStyle=Jp.prototype.$g;u("ol.source.ImageWMS",lz,OPENLAYERS);lz.prototype.getGetFeatureInfoUrl=lz.prototype.nm;lz.prototype.getParams=lz.prototype.pm;lz.prototype.getImageLoadFunction=lz.prototype.om;lz.prototype.getUrl=lz.prototype.qm;lz.prototype.setImageLoadFunction=lz.prototype.rm; +lz.prototype.setUrl=lz.prototype.sm;lz.prototype.updateParams=lz.prototype.tm;u("ol.source.MapQuest",sz,OPENLAYERS);sz.prototype.getLayer=sz.prototype.l;u("ol.source.OSM",qz,OPENLAYERS);u("ol.source.OSM.ATTRIBUTION",rz,OPENLAYERS);u("ol.source.Raster",vz,OPENLAYERS);vz.prototype.setOperation=vz.prototype.u;zz.prototype.extent=zz.prototype.extent;zz.prototype.resolution=zz.prototype.resolution;zz.prototype.data=zz.prototype.data;u("ol.source.Source",Dh,OPENLAYERS);Dh.prototype.getAttributions=Dh.prototype.la; +Dh.prototype.getLogo=Dh.prototype.ka;Dh.prototype.getProjection=Dh.prototype.ma;Dh.prototype.getState=Dh.prototype.na;u("ol.source.Stamen",Ez,OPENLAYERS);u("ol.source.TileArcGISRest",Gz,OPENLAYERS);Gz.prototype.getParams=Gz.prototype.um;Gz.prototype.getUrls=Gz.prototype.vm;Gz.prototype.setUrl=Gz.prototype.wm;Gz.prototype.setUrls=Gz.prototype.ah;Gz.prototype.updateParams=Gz.prototype.ym;u("ol.source.TileDebug",Iz,OPENLAYERS);u("ol.source.TileImage",cz,OPENLAYERS);cz.prototype.getTileLoadFunction=cz.prototype.ab; +cz.prototype.getTileUrlFunction=cz.prototype.bb;cz.prototype.setTileLoadFunction=cz.prototype.ib;cz.prototype.setTileUrlFunction=cz.prototype.Fa;u("ol.source.TileJSON",Jz,OPENLAYERS);u("ol.source.Tile",Rh,OPENLAYERS);Rh.prototype.getTileGrid=Rh.prototype.za;Vh.prototype.tile=Vh.prototype.tile;u("ol.source.TileUTFGrid",Kz,OPENLAYERS);Kz.prototype.getTemplate=Kz.prototype.Oj;Kz.prototype.forDataAtCoordinateAndResolution=Kz.prototype.Zi;u("ol.source.TileVector",Pz,OPENLAYERS); +Pz.prototype.getFeatures=Pz.prototype.Lc;Pz.prototype.getFeaturesAtCoordinateAndResolution=Pz.prototype.nj;u("ol.source.TileWMS",Sz,OPENLAYERS);Sz.prototype.getGetFeatureInfoUrl=Sz.prototype.Cm;Sz.prototype.getParams=Sz.prototype.Dm;Sz.prototype.getUrls=Sz.prototype.Em;Sz.prototype.setUrl=Sz.prototype.Fm;Sz.prototype.setUrls=Sz.prototype.bh;Sz.prototype.updateParams=Sz.prototype.Hm;u("ol.source.Vector",W,OPENLAYERS);W.prototype.addFeature=W.prototype.vd;W.prototype.addFeatures=W.prototype.Bc; +W.prototype.clear=W.prototype.clear;W.prototype.forEachFeature=W.prototype.ig;W.prototype.forEachFeatureInExtent=W.prototype.ld;W.prototype.forEachFeatureIntersectingExtent=W.prototype.cf;W.prototype.getFeaturesCollection=W.prototype.gf;W.prototype.getFeatures=W.prototype.Lc;W.prototype.getFeaturesAtCoordinate=W.prototype.ff;W.prototype.getFeaturesInExtent=W.prototype.hf;W.prototype.getClosestFeatureToCoordinate=W.prototype.kg;W.prototype.getExtent=W.prototype.R;W.prototype.getFeatureById=W.prototype.ef; +W.prototype.removeFeature=W.prototype.Mc;Gp.prototype.feature=Gp.prototype.feature;u("ol.source.WMTS",Yz,OPENLAYERS);Yz.prototype.getDimensions=Yz.prototype.lj;Yz.prototype.getFormat=Yz.prototype.pj;Yz.prototype.getLayer=Yz.prototype.Im;Yz.prototype.getMatrixSet=Yz.prototype.Bj;Yz.prototype.getRequestEncoding=Yz.prototype.Mj;Yz.prototype.getStyle=Yz.prototype.Jm;Yz.prototype.getUrls=Yz.prototype.Km;Yz.prototype.getVersion=Yz.prototype.Sj;Yz.prototype.updateDimensions=Yz.prototype.Ko; +u("ol.source.WMTS.optionsFromCapabilities",function(b,c){var d=Wa(b.Contents.Layer,function(b){return b.Identifier==c.layer}),e=b.Contents.TileMatrixSet,f,g;f=1<d.TileMatrixSetLink.length?m(c.projection)?Xa(d.TileMatrixSetLink,function(b){return Wa(e,function(c){return c.Identifier==b.TileMatrixSet}).SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3")==c.projection}):Xa(d.TileMatrixSetLink,function(b){return b.TileMatrixSet==c.matrixSet}):0;0>f&&(f=0);g=d.TileMatrixSetLink[f].TileMatrixSet; +var h=d.Format[0];m(c.format)&&(h=c.format);f=Xa(d.Style,function(b){return m(c.style)?b.Title==c.style:b.isDefault});0>f&&(f=0);f=d.Style[f].Identifier;var k={};m(d.Dimension)&&Sa(d.Dimension,function(b){var c=b.Identifier,d=b["default"];m(d)||(d=b.values[0]);k[c]=d});var n=Wa(b.Contents.TileMatrixSet,function(b){return b.Identifier==g}),p;p=m(c.projection)?Ae(c.projection):Ae(n.SupportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"));var q=d.WGS84BoundingBox,r,t;m(q)&&(t=Ae("EPSG:4326").R(), +t=q[0]==t[0]&&q[2]==t[2],r=We(q,"EPSG:4326",p),q=p.R(),null===q||Ud(q,r)||(r=void 0));var n=Xz(n,r),v=[];r=c.requestEncoding;r=m(r)?r:"";if(b.hasOwnProperty("OperationsMetadata")&&b.OperationsMetadata.hasOwnProperty("GetTile")&&0!=r.lastIndexOf("REST",0))for(var d=b.OperationsMetadata.GetTile.DCP.HTTP.Get,q=0,B=d.length;q<B;++q){var z=Wa(d[q].Constraint,function(b){return"GetEncoding"==b.name}).AllowedValues.Value;0<z.length&&Ya(z,"KVP")&&(r="KVP",v.push(d[q].href))}else r="REST",Sa(d.ResourceURL, +function(b){"tile"==b.resourceType&&(h=b.format,v.push(b.template))});return{urls:v,layer:c.layer,matrixSet:g,format:h,projection:p,requestEncoding:r,tileGrid:n,style:f,dimensions:k,wrapX:t}},OPENLAYERS);u("ol.source.XYZ",pz,OPENLAYERS);pz.prototype.setUrl=pz.prototype.f;u("ol.source.Zoomify",$z,OPENLAYERS);kk.prototype.vectorContext=kk.prototype.vectorContext;kk.prototype.frameState=kk.prototype.frameState;kk.prototype.context=kk.prototype.context;kk.prototype.glContext=kk.prototype.glContext; +u("ol.render.VectorContext",jk,OPENLAYERS);Eq.prototype.drawAsync=Eq.prototype.Cc;Eq.prototype.drawCircleGeometry=Eq.prototype.Dc;Eq.prototype.drawFeature=Eq.prototype.$e;Eq.prototype.drawGeometryCollectionGeometry=Eq.prototype.Pd;Eq.prototype.drawPointGeometry=Eq.prototype.xb;Eq.prototype.drawLineStringGeometry=Eq.prototype.Hb;Eq.prototype.drawMultiLineStringGeometry=Eq.prototype.Ec;Eq.prototype.drawMultiPointGeometry=Eq.prototype.wb;Eq.prototype.drawMultiPolygonGeometry=Eq.prototype.Fc; +Eq.prototype.drawPolygonGeometry=Eq.prototype.Zb;Eq.prototype.drawText=Eq.prototype.yb;Eq.prototype.setFillStrokeStyle=Eq.prototype.Ha;Eq.prototype.setImageStyle=Eq.prototype.hb;Eq.prototype.setTextStyle=Eq.prototype.Ia;lm.prototype.drawAsync=lm.prototype.Cc;lm.prototype.drawCircleGeometry=lm.prototype.Dc;lm.prototype.drawFeature=lm.prototype.$e;lm.prototype.drawPointGeometry=lm.prototype.xb;lm.prototype.drawMultiPointGeometry=lm.prototype.wb;lm.prototype.drawLineStringGeometry=lm.prototype.Hb; +lm.prototype.drawMultiLineStringGeometry=lm.prototype.Ec;lm.prototype.drawPolygonGeometry=lm.prototype.Zb;lm.prototype.drawMultiPolygonGeometry=lm.prototype.Fc;lm.prototype.setFillStrokeStyle=lm.prototype.Ha;lm.prototype.setImageStyle=lm.prototype.hb;lm.prototype.setTextStyle=lm.prototype.Ia;u("ol.proj.common.add",km,OPENLAYERS);u("ol.proj.METERS_PER_UNIT",xe,OPENLAYERS);u("ol.proj.Projection",ye,OPENLAYERS);ye.prototype.getCode=ye.prototype.jj;ye.prototype.getExtent=ye.prototype.R; +ye.prototype.getUnits=ye.prototype.Yl;ye.prototype.getMetersPerUnit=ye.prototype.Vd;ye.prototype.getWorldExtent=ye.prototype.Uj;ye.prototype.isGlobal=ye.prototype.Ik;ye.prototype.setGlobal=ye.prototype.po;ye.prototype.setExtent=ye.prototype.Zl;ye.prototype.setWorldExtent=ye.prototype.Bo;ye.prototype.setGetPointResolution=ye.prototype.oo;ye.prototype.getPointResolution=ye.prototype.getPointResolution;u("ol.proj.addEquivalentProjections",Be,OPENLAYERS);u("ol.proj.addProjection",Oe,OPENLAYERS); +u("ol.proj.addCoordinateTransforms",Ce,OPENLAYERS);u("ol.proj.fromLonLat",function(b,c){return Ve(b,"EPSG:4326",m(c)?c:"EPSG:3857")},OPENLAYERS);u("ol.proj.toLonLat",function(b,c){return Ve(b,m(c)?c:"EPSG:3857","EPSG:4326")},OPENLAYERS);u("ol.proj.get",Ae,OPENLAYERS);u("ol.proj.getTransform",Te,OPENLAYERS);u("ol.proj.transform",Ve,OPENLAYERS);u("ol.proj.transformExtent",We,OPENLAYERS);u("ol.layer.Heatmap",Z,OPENLAYERS);Z.prototype.getBlur=Z.prototype.jg;Z.prototype.getGradient=Z.prototype.ng; +Z.prototype.getRadius=Z.prototype.Vg;Z.prototype.setBlur=Z.prototype.Fh;Z.prototype.setGradient=Z.prototype.Jh;Z.prototype.setRadius=Z.prototype.Wg;u("ol.layer.Image",I,OPENLAYERS);I.prototype.getSource=I.prototype.ca;u("ol.layer.Layer",G,OPENLAYERS);G.prototype.getSource=G.prototype.ca;G.prototype.setMap=G.prototype.setMap;G.prototype.setSource=G.prototype.Tc;u("ol.layer.Base",hk,OPENLAYERS);hk.prototype.getBrightness=hk.prototype.Jb;hk.prototype.getContrast=hk.prototype.Kb;hk.prototype.getHue=hk.prototype.Lb; +hk.prototype.getExtent=hk.prototype.R;hk.prototype.getMaxResolution=hk.prototype.Mb;hk.prototype.getMinResolution=hk.prototype.Nb;hk.prototype.getOpacity=hk.prototype.Sb;hk.prototype.getSaturation=hk.prototype.Ob;hk.prototype.getVisible=hk.prototype.nb;hk.prototype.setBrightness=hk.prototype.pc;hk.prototype.setContrast=hk.prototype.qc;hk.prototype.setHue=hk.prototype.rc;hk.prototype.setExtent=hk.prototype.jc;hk.prototype.setMaxResolution=hk.prototype.sc;hk.prototype.setMinResolution=hk.prototype.tc; +hk.prototype.setOpacity=hk.prototype.kc;hk.prototype.setSaturation=hk.prototype.uc;hk.prototype.setVisible=hk.prototype.vc;u("ol.layer.Group",dm,OPENLAYERS);dm.prototype.getLayers=dm.prototype.Kc;dm.prototype.setLayers=dm.prototype.Lh;u("ol.layer.Tile",K,OPENLAYERS);K.prototype.getPreload=K.prototype.b;K.prototype.getSource=K.prototype.ca;K.prototype.setPreload=K.prototype.f;K.prototype.getUseInterimTilesOnError=K.prototype.c;K.prototype.setUseInterimTilesOnError=K.prototype.g; +u("ol.layer.Vector",M,OPENLAYERS);M.prototype.getSource=M.prototype.ca;M.prototype.getStyle=M.prototype.U;M.prototype.getStyleFunction=M.prototype.T;M.prototype.setStyle=M.prototype.g;u("ol.interaction.DoubleClickZoom",Yk,OPENLAYERS);u("ol.interaction.DoubleClickZoom.handleEvent",Zk,OPENLAYERS);u("ol.interaction.DragAndDrop",Ux,OPENLAYERS);u("ol.interaction.DragAndDrop.handleEvent",pe,OPENLAYERS);Vx.prototype.features=Vx.prototype.features;Vx.prototype.file=Vx.prototype.file; +Vx.prototype.projection=Vx.prototype.projection;vl.prototype.coordinate=vl.prototype.coordinate;u("ol.interaction.DragBox",wl,OPENLAYERS);wl.prototype.getGeometry=wl.prototype.Z;u("ol.interaction.DragPan",jl,OPENLAYERS);u("ol.interaction.DragRotateAndZoom",Yx,OPENLAYERS);u("ol.interaction.DragRotate",nl,OPENLAYERS);u("ol.interaction.DragZoom",Ol,OPENLAYERS);by.prototype.feature=by.prototype.feature;u("ol.interaction.Draw",cy,OPENLAYERS);u("ol.interaction.Draw.handleEvent",ey,OPENLAYERS); +cy.prototype.finishDrawing=cy.prototype.U;u("ol.interaction.Draw.createRegularPolygon",function(b,c){return function(d,e){var f=d[0],g=d[1],h=Math.sqrt(td(f,g)),k=m(e)?e:Mf(new Zm(f),b);Nf(k,f,h,m(c)?c:Math.atan((g[1]-f[1])/(g[0]-f[0])));return k}},OPENLAYERS);u("ol.interaction.Interaction",Uk,OPENLAYERS);Uk.prototype.getActive=Uk.prototype.b;Uk.prototype.setActive=Uk.prototype.c;u("ol.interaction.defaults",cm,OPENLAYERS);u("ol.interaction.KeyboardPan",Pl,OPENLAYERS); +u("ol.interaction.KeyboardPan.handleEvent",Ql,OPENLAYERS);u("ol.interaction.KeyboardZoom",Rl,OPENLAYERS);u("ol.interaction.KeyboardZoom.handleEvent",Sl,OPENLAYERS);sy.prototype.features=sy.prototype.features;sy.prototype.mapBrowserPointerEvent=sy.prototype.mapBrowserPointerEvent;u("ol.interaction.Modify",ty,OPENLAYERS);u("ol.interaction.Modify.handleEvent",wy,OPENLAYERS);u("ol.interaction.MouseWheelZoom",Tl,OPENLAYERS);u("ol.interaction.MouseWheelZoom.handleEvent",Ul,OPENLAYERS); +u("ol.interaction.PinchRotate",Vl,OPENLAYERS);u("ol.interaction.PinchZoom",Zl,OPENLAYERS);u("ol.interaction.Pointer",gl,OPENLAYERS);u("ol.interaction.Pointer.handleEvent",hl,OPENLAYERS);Dy.prototype.selected=Dy.prototype.selected;Dy.prototype.deselected=Dy.prototype.deselected;Dy.prototype.mapBrowserEvent=Dy.prototype.mapBrowserEvent;u("ol.interaction.Select",Ey,OPENLAYERS);Ey.prototype.getFeatures=Ey.prototype.G;u("ol.interaction.Select.handleEvent",Fy,OPENLAYERS);Ey.prototype.setMap=Ey.prototype.setMap; +u("ol.interaction.Snap",Hy,OPENLAYERS);Hy.prototype.addFeature=Hy.prototype.sd;Hy.prototype.removeFeature=Hy.prototype.td;u("ol.geom.Circle",Zm,OPENLAYERS);Zm.prototype.clone=Zm.prototype.clone;Zm.prototype.getCenter=Zm.prototype.rd;Zm.prototype.getRadius=Zm.prototype.xf;Zm.prototype.getType=Zm.prototype.V;Zm.prototype.intersectsExtent=Zm.prototype.sa;Zm.prototype.setCenter=Zm.prototype.Al;Zm.prototype.setCenterAndRadius=Zm.prototype.Lf;Zm.prototype.setRadius=Zm.prototype.Bl; +Zm.prototype.transform=Zm.prototype.transform;u("ol.geom.Geometry",Xe,OPENLAYERS);Xe.prototype.getClosestPoint=Xe.prototype.$a;Xe.prototype.getExtent=Xe.prototype.R;Xe.prototype.transform=Xe.prototype.transform;u("ol.geom.GeometryCollection",an,OPENLAYERS);an.prototype.clone=an.prototype.clone;an.prototype.getGeometries=an.prototype.mg;an.prototype.getType=an.prototype.V;an.prototype.intersectsExtent=an.prototype.sa;an.prototype.setGeometries=an.prototype.Ih;an.prototype.applyTransform=an.prototype.va; +an.prototype.translate=an.prototype.Ua;u("ol.geom.LinearRing",tf,OPENLAYERS);tf.prototype.clone=tf.prototype.clone;tf.prototype.getArea=tf.prototype.El;tf.prototype.getCoordinates=tf.prototype.W;tf.prototype.getType=tf.prototype.V;tf.prototype.setCoordinates=tf.prototype.ja;u("ol.geom.LineString",N,OPENLAYERS);N.prototype.appendCoordinate=N.prototype.Ni;N.prototype.clone=N.prototype.clone;N.prototype.forEachSegment=N.prototype.bj;N.prototype.getCoordinateAtM=N.prototype.Cl; +N.prototype.getCoordinates=N.prototype.W;N.prototype.getLength=N.prototype.Dl;N.prototype.getType=N.prototype.V;N.prototype.intersectsExtent=N.prototype.sa;N.prototype.setCoordinates=N.prototype.ja;u("ol.geom.MultiLineString",O,OPENLAYERS);O.prototype.appendLineString=O.prototype.Oi;O.prototype.clone=O.prototype.clone;O.prototype.getCoordinateAtM=O.prototype.Fl;O.prototype.getCoordinates=O.prototype.W;O.prototype.getLineString=O.prototype.zj;O.prototype.getLineStrings=O.prototype.md; +O.prototype.getType=O.prototype.V;O.prototype.intersectsExtent=O.prototype.sa;O.prototype.setCoordinates=O.prototype.ja;u("ol.geom.MultiPoint",ln,OPENLAYERS);ln.prototype.appendPoint=ln.prototype.Qi;ln.prototype.clone=ln.prototype.clone;ln.prototype.getCoordinates=ln.prototype.W;ln.prototype.getPoint=ln.prototype.Ij;ln.prototype.getPoints=ln.prototype.pe;ln.prototype.getType=ln.prototype.V;ln.prototype.intersectsExtent=ln.prototype.sa;ln.prototype.setCoordinates=ln.prototype.ja; +u("ol.geom.MultiPolygon",P,OPENLAYERS);P.prototype.appendPolygon=P.prototype.Ri;P.prototype.clone=P.prototype.clone;P.prototype.getArea=P.prototype.Gl;P.prototype.getCoordinates=P.prototype.W;P.prototype.getInteriorPoints=P.prototype.wj;P.prototype.getPolygon=P.prototype.Kj;P.prototype.getPolygons=P.prototype.Wd;P.prototype.getType=P.prototype.V;P.prototype.intersectsExtent=P.prototype.sa;P.prototype.setCoordinates=P.prototype.ja;u("ol.geom.Point",D,OPENLAYERS);D.prototype.clone=D.prototype.clone; +D.prototype.getCoordinates=D.prototype.W;D.prototype.getType=D.prototype.V;D.prototype.intersectsExtent=D.prototype.sa;D.prototype.setCoordinates=D.prototype.ja;u("ol.geom.Polygon",F,OPENLAYERS);F.prototype.appendLinearRing=F.prototype.Pi;F.prototype.clone=F.prototype.clone;F.prototype.getArea=F.prototype.Hl;F.prototype.getCoordinates=F.prototype.W;F.prototype.getInteriorPoint=F.prototype.vj;F.prototype.getLinearRingCount=F.prototype.Aj;F.prototype.getLinearRing=F.prototype.og; +F.prototype.getLinearRings=F.prototype.Ud;F.prototype.getType=F.prototype.V;F.prototype.intersectsExtent=F.prototype.sa;F.prototype.setCoordinates=F.prototype.ja;u("ol.geom.Polygon.circular",Kf,OPENLAYERS);u("ol.geom.Polygon.fromExtent",Lf,OPENLAYERS);u("ol.geom.Polygon.fromCircle",Mf,OPENLAYERS);u("ol.geom.SimpleGeometry",Ze,OPENLAYERS);Ze.prototype.getFirstCoordinate=Ze.prototype.Bb;Ze.prototype.getLastCoordinate=Ze.prototype.Cb;Ze.prototype.getLayout=Ze.prototype.Db; +Ze.prototype.applyTransform=Ze.prototype.va;Ze.prototype.translate=Ze.prototype.Ua;u("ol.format.EsriJSON",Mr,OPENLAYERS);Mr.prototype.readFeature=Mr.prototype.Fb;Mr.prototype.readFeatures=Mr.prototype.ra;Mr.prototype.readGeometry=Mr.prototype.Qc;Mr.prototype.readProjection=Mr.prototype.Ga;Mr.prototype.writeGeometry=Mr.prototype.Wc;Mr.prototype.writeGeometryObject=Mr.prototype.Ne;Mr.prototype.writeFeature=Mr.prototype.Dd;Mr.prototype.writeFeatureObject=Mr.prototype.Vc;Mr.prototype.writeFeatures=Mr.prototype.Gb; +Mr.prototype.writeFeaturesObject=Mr.prototype.Le;u("ol.format.Feature",Gr,OPENLAYERS);u("ol.format.GeoJSON",Tr,OPENLAYERS);Tr.prototype.readFeature=Tr.prototype.Fb;Tr.prototype.readFeatures=Tr.prototype.ra;Tr.prototype.readGeometry=Tr.prototype.Qc;Tr.prototype.readProjection=Tr.prototype.Ga;Tr.prototype.writeFeature=Tr.prototype.Dd;Tr.prototype.writeFeatureObject=Tr.prototype.Vc;Tr.prototype.writeFeatures=Tr.prototype.Gb;Tr.prototype.writeFeaturesObject=Tr.prototype.Le; +Tr.prototype.writeGeometry=Tr.prototype.Wc;Tr.prototype.writeGeometryObject=Tr.prototype.Ne;u("ol.format.GPX",ws,OPENLAYERS);ws.prototype.readFeature=ws.prototype.Fb;ws.prototype.readFeatures=ws.prototype.ra;ws.prototype.readProjection=ws.prototype.Ga;ws.prototype.writeFeatures=ws.prototype.Gb;ws.prototype.writeFeaturesNode=ws.prototype.b;u("ol.format.IGC",gt,OPENLAYERS);gt.prototype.readFeature=gt.prototype.Fb;gt.prototype.readFeatures=gt.prototype.ra;gt.prototype.readProjection=gt.prototype.Ga; +u("ol.format.KML",Ft,OPENLAYERS);Ft.prototype.readFeature=Ft.prototype.Fb;Ft.prototype.readFeatures=Ft.prototype.ra;Ft.prototype.readName=Ft.prototype.Sn;Ft.prototype.readNetworkLinks=Ft.prototype.Tn;Ft.prototype.readProjection=Ft.prototype.Ga;Ft.prototype.writeFeatures=Ft.prototype.Gb;Ft.prototype.writeFeaturesNode=Ft.prototype.b;u("ol.format.OSMXML",qv,OPENLAYERS);qv.prototype.readFeatures=qv.prototype.ra;qv.prototype.readProjection=qv.prototype.Ga;u("ol.format.Polyline",Pv,OPENLAYERS); +u("ol.format.Polyline.encodeDeltas",Qv,OPENLAYERS);u("ol.format.Polyline.decodeDeltas",Sv,OPENLAYERS);u("ol.format.Polyline.encodeFloats",Rv,OPENLAYERS);u("ol.format.Polyline.decodeFloats",Tv,OPENLAYERS);Pv.prototype.readFeature=Pv.prototype.Fb;Pv.prototype.readFeatures=Pv.prototype.ra;Pv.prototype.readGeometry=Pv.prototype.Qc;Pv.prototype.readProjection=Pv.prototype.Ga;Pv.prototype.writeGeometry=Pv.prototype.Wc;u("ol.format.TopoJSON",Uv,OPENLAYERS);Uv.prototype.readFeatures=Uv.prototype.ra; +Uv.prototype.readProjection=Uv.prototype.Ga;u("ol.format.WFS",$v,OPENLAYERS);$v.prototype.readFeatures=$v.prototype.ra;$v.prototype.readTransactionResponse=$v.prototype.j;$v.prototype.readFeatureCollectionMetadata=$v.prototype.i;$v.prototype.writeGetFeature=$v.prototype.l;$v.prototype.writeTransaction=$v.prototype.A;$v.prototype.readProjection=$v.prototype.Ga;u("ol.format.WKT",mw,OPENLAYERS);mw.prototype.readFeature=mw.prototype.Fb;mw.prototype.readFeatures=mw.prototype.ra; +mw.prototype.readGeometry=mw.prototype.Qc;mw.prototype.writeFeature=mw.prototype.Dd;mw.prototype.writeFeatures=mw.prototype.Gb;mw.prototype.writeGeometry=mw.prototype.Wc;u("ol.format.WMSCapabilities",Ew,OPENLAYERS);Ew.prototype.read=Ew.prototype.c;u("ol.format.WMSGetFeatureInfo",ax,OPENLAYERS);ax.prototype.readFeatures=ax.prototype.ra;u("ol.format.WMTSCapabilities",cx,OPENLAYERS);cx.prototype.read=cx.prototype.c;u("ol.format.GML2",vs,OPENLAYERS);u("ol.format.GML3",ms,OPENLAYERS); +ms.prototype.writeGeometryNode=ms.prototype.B;ms.prototype.writeFeatures=ms.prototype.Gb;ms.prototype.writeFeaturesNode=ms.prototype.b;u("ol.format.GML",ms,OPENLAYERS);ms.prototype.writeFeatures=ms.prototype.Gb;ms.prototype.writeFeaturesNode=ms.prototype.b;as.prototype.readFeatures=as.prototype.ra;u("ol.events.condition.altKeyOnly",function(b){b=b.a;return b.b&&!b.j&&!b.f},OPENLAYERS);u("ol.events.condition.altShiftKeysOnly",$k,OPENLAYERS);u("ol.events.condition.always",pe,OPENLAYERS); +u("ol.events.condition.click",function(b){return b.type==Xj},OPENLAYERS);u("ol.events.condition.never",oe,OPENLAYERS);u("ol.events.condition.pointerMove",al,OPENLAYERS);u("ol.events.condition.singleClick",bl,OPENLAYERS);u("ol.events.condition.doubleClick",function(b){return b.type==Yj},OPENLAYERS);u("ol.events.condition.noModifierKeys",cl,OPENLAYERS);u("ol.events.condition.platformModifierKeyOnly",function(b){b=b.a;return!b.b&&b.j&&!b.f},OPENLAYERS);u("ol.events.condition.shiftKeyOnly",dl,OPENLAYERS); +u("ol.events.condition.targetNotEditable",el,OPENLAYERS);u("ol.events.condition.mouseOnly",fl,OPENLAYERS);u("ol.control.Attribution",Wh,OPENLAYERS);u("ol.control.Attribution.render",Xh,OPENLAYERS);Wh.prototype.getCollapsible=Wh.prototype.ql;Wh.prototype.setCollapsible=Wh.prototype.tl;Wh.prototype.setCollapsed=Wh.prototype.sl;Wh.prototype.getCollapsed=Wh.prototype.pl;u("ol.control.Control",wh,OPENLAYERS);wh.prototype.getMap=wh.prototype.g;wh.prototype.setMap=wh.prototype.setMap; +wh.prototype.setTarget=wh.prototype.c;u("ol.control.defaults",ci,OPENLAYERS);u("ol.control.FullScreen",hi,OPENLAYERS);u("ol.control.MousePosition",ii,OPENLAYERS);u("ol.control.MousePosition.render",ji,OPENLAYERS);ii.prototype.getCoordinateFormat=ii.prototype.lg;ii.prototype.getProjection=ii.prototype.Og;ii.prototype.setMap=ii.prototype.setMap;ii.prototype.setCoordinateFormat=ii.prototype.Gh;ii.prototype.setProjection=ii.prototype.Pg;u("ol.control.OverviewMap",fr,OPENLAYERS);fr.prototype.setMap=fr.prototype.setMap; +u("ol.control.OverviewMap.render",gr,OPENLAYERS);fr.prototype.getCollapsible=fr.prototype.wl;fr.prototype.setCollapsible=fr.prototype.zl;fr.prototype.setCollapsed=fr.prototype.yl;fr.prototype.getCollapsed=fr.prototype.vl;u("ol.control.Rotate",Zh,OPENLAYERS);u("ol.control.Rotate.render",$h,OPENLAYERS);u("ol.control.ScaleLine",kr,OPENLAYERS);kr.prototype.getUnits=kr.prototype.C;u("ol.control.ScaleLine.render",lr,OPENLAYERS);kr.prototype.setUnits=kr.prototype.T;u("ol.control.Zoom",bi,OPENLAYERS); +u("ol.control.ZoomSlider",zr,OPENLAYERS);u("ol.control.ZoomSlider.render",Br,OPENLAYERS);u("ol.control.ZoomToExtent",Er,OPENLAYERS);u("ol.color.asArray",wg,OPENLAYERS);u("ol.color.asString",yg,OPENLAYERS);gd.prototype.changed=gd.prototype.s;gd.prototype.getRevision=gd.prototype.L;gd.prototype.on=gd.prototype.D;gd.prototype.once=gd.prototype.M;gd.prototype.un=gd.prototype.J;gd.prototype.unByKey=gd.prototype.N;rg.prototype.get=rg.prototype.get;rg.prototype.getKeys=rg.prototype.O; +rg.prototype.getProperties=rg.prototype.P;rg.prototype.set=rg.prototype.set;rg.prototype.setProperties=rg.prototype.I;rg.prototype.unset=rg.prototype.S;rg.prototype.changed=rg.prototype.s;rg.prototype.getRevision=rg.prototype.L;rg.prototype.on=rg.prototype.D;rg.prototype.once=rg.prototype.M;rg.prototype.un=rg.prototype.J;rg.prototype.unByKey=rg.prototype.N;Fr.prototype.get=Fr.prototype.get;Fr.prototype.getKeys=Fr.prototype.O;Fr.prototype.getProperties=Fr.prototype.P;Fr.prototype.set=Fr.prototype.set; +Fr.prototype.setProperties=Fr.prototype.I;Fr.prototype.unset=Fr.prototype.S;Fr.prototype.changed=Fr.prototype.s;Fr.prototype.getRevision=Fr.prototype.L;Fr.prototype.on=Fr.prototype.D;Fr.prototype.once=Fr.prototype.M;Fr.prototype.un=Fr.prototype.J;Fr.prototype.unByKey=Fr.prototype.N;Bn.prototype.get=Bn.prototype.get;Bn.prototype.getKeys=Bn.prototype.O;Bn.prototype.getProperties=Bn.prototype.P;Bn.prototype.set=Bn.prototype.set;Bn.prototype.setProperties=Bn.prototype.I;Bn.prototype.unset=Bn.prototype.S; +Bn.prototype.changed=Bn.prototype.s;Bn.prototype.getRevision=Bn.prototype.L;Bn.prototype.on=Bn.prototype.D;Bn.prototype.once=Bn.prototype.M;Bn.prototype.un=Bn.prototype.J;Bn.prototype.unByKey=Bn.prototype.N;px.prototype.get=px.prototype.get;px.prototype.getKeys=px.prototype.O;px.prototype.getProperties=px.prototype.P;px.prototype.set=px.prototype.set;px.prototype.setProperties=px.prototype.I;px.prototype.unset=px.prototype.S;px.prototype.changed=px.prototype.s;px.prototype.getRevision=px.prototype.L; +px.prototype.on=px.prototype.D;px.prototype.once=px.prototype.M;px.prototype.un=px.prototype.J;px.prototype.unByKey=px.prototype.N;zx.prototype.getTileCoord=zx.prototype.i;X.prototype.get=X.prototype.get;X.prototype.getKeys=X.prototype.O;X.prototype.getProperties=X.prototype.P;X.prototype.set=X.prototype.set;X.prototype.setProperties=X.prototype.I;X.prototype.unset=X.prototype.S;X.prototype.changed=X.prototype.s;X.prototype.getRevision=X.prototype.L;X.prototype.on=X.prototype.D;X.prototype.once=X.prototype.M; +X.prototype.un=X.prototype.J;X.prototype.unByKey=X.prototype.N;Tj.prototype.map=Tj.prototype.map;Tj.prototype.frameState=Tj.prototype.frameState;Uj.prototype.originalEvent=Uj.prototype.originalEvent;Uj.prototype.pixel=Uj.prototype.pixel;Uj.prototype.coordinate=Uj.prototype.coordinate;Uj.prototype.dragging=Uj.prototype.dragging;Uj.prototype.preventDefault=Uj.prototype.preventDefault;Uj.prototype.stopPropagation=Uj.prototype.ob;Uj.prototype.map=Uj.prototype.map;Uj.prototype.frameState=Uj.prototype.frameState; +cr.prototype.get=cr.prototype.get;cr.prototype.getKeys=cr.prototype.O;cr.prototype.getProperties=cr.prototype.P;cr.prototype.set=cr.prototype.set;cr.prototype.setProperties=cr.prototype.I;cr.prototype.unset=cr.prototype.S;cr.prototype.changed=cr.prototype.s;cr.prototype.getRevision=cr.prototype.L;cr.prototype.on=cr.prototype.D;cr.prototype.once=cr.prototype.M;cr.prototype.un=cr.prototype.J;cr.prototype.unByKey=cr.prototype.N;Of.prototype.get=Of.prototype.get;Of.prototype.getKeys=Of.prototype.O; +Of.prototype.getProperties=Of.prototype.P;Of.prototype.set=Of.prototype.set;Of.prototype.setProperties=Of.prototype.I;Of.prototype.unset=Of.prototype.S;Of.prototype.changed=Of.prototype.s;Of.prototype.getRevision=Of.prototype.L;Of.prototype.on=Of.prototype.D;Of.prototype.once=Of.prototype.M;Of.prototype.un=Of.prototype.J;Of.prototype.unByKey=Of.prototype.N;Wz.prototype.getMaxZoom=Wz.prototype.pg;Wz.prototype.getMinZoom=Wz.prototype.qg;Wz.prototype.getOrigin=Wz.prototype.Nc; +Wz.prototype.getResolution=Wz.prototype.ua;Wz.prototype.getResolutions=Wz.prototype.ih;Wz.prototype.getTileCoordForCoordAndResolution=Wz.prototype.pd;Wz.prototype.getTileCoordForCoordAndZ=Wz.prototype.Zd;Wz.prototype.getTileSize=Wz.prototype.Ja;Il.prototype.getOpacity=Il.prototype.ve;Il.prototype.getRotateWithView=Il.prototype.Xd;Il.prototype.getRotation=Il.prototype.we;Il.prototype.getScale=Il.prototype.xe;Il.prototype.getSnapToPixel=Il.prototype.Yd;Il.prototype.setRotation=Il.prototype.ye; +Il.prototype.setScale=Il.prototype.ze;Ck.prototype.getOpacity=Ck.prototype.ve;Ck.prototype.getRotateWithView=Ck.prototype.Xd;Ck.prototype.getRotation=Ck.prototype.we;Ck.prototype.getScale=Ck.prototype.xe;Ck.prototype.getSnapToPixel=Ck.prototype.Yd;Ck.prototype.setRotation=Ck.prototype.ye;Ck.prototype.setScale=Ck.prototype.ze;fA.prototype.getOpacity=fA.prototype.ve;fA.prototype.getRotateWithView=fA.prototype.Xd;fA.prototype.getRotation=fA.prototype.we;fA.prototype.getScale=fA.prototype.xe; +fA.prototype.getSnapToPixel=fA.prototype.Yd;fA.prototype.setRotation=fA.prototype.ye;fA.prototype.setScale=fA.prototype.ze;Dh.prototype.get=Dh.prototype.get;Dh.prototype.getKeys=Dh.prototype.O;Dh.prototype.getProperties=Dh.prototype.P;Dh.prototype.set=Dh.prototype.set;Dh.prototype.setProperties=Dh.prototype.I;Dh.prototype.unset=Dh.prototype.S;Dh.prototype.changed=Dh.prototype.s;Dh.prototype.getRevision=Dh.prototype.L;Dh.prototype.on=Dh.prototype.D;Dh.prototype.once=Dh.prototype.M; +Dh.prototype.un=Dh.prototype.J;Dh.prototype.unByKey=Dh.prototype.N;Rh.prototype.getAttributions=Rh.prototype.la;Rh.prototype.getLogo=Rh.prototype.ka;Rh.prototype.getProjection=Rh.prototype.ma;Rh.prototype.getState=Rh.prototype.na;Rh.prototype.get=Rh.prototype.get;Rh.prototype.getKeys=Rh.prototype.O;Rh.prototype.getProperties=Rh.prototype.P;Rh.prototype.set=Rh.prototype.set;Rh.prototype.setProperties=Rh.prototype.I;Rh.prototype.unset=Rh.prototype.S;Rh.prototype.changed=Rh.prototype.s; +Rh.prototype.getRevision=Rh.prototype.L;Rh.prototype.on=Rh.prototype.D;Rh.prototype.once=Rh.prototype.M;Rh.prototype.un=Rh.prototype.J;Rh.prototype.unByKey=Rh.prototype.N;cz.prototype.getTileGrid=cz.prototype.za;cz.prototype.getAttributions=cz.prototype.la;cz.prototype.getLogo=cz.prototype.ka;cz.prototype.getProjection=cz.prototype.ma;cz.prototype.getState=cz.prototype.na;cz.prototype.get=cz.prototype.get;cz.prototype.getKeys=cz.prototype.O;cz.prototype.getProperties=cz.prototype.P; +cz.prototype.set=cz.prototype.set;cz.prototype.setProperties=cz.prototype.I;cz.prototype.unset=cz.prototype.S;cz.prototype.changed=cz.prototype.s;cz.prototype.getRevision=cz.prototype.L;cz.prototype.on=cz.prototype.D;cz.prototype.once=cz.prototype.M;cz.prototype.un=cz.prototype.J;cz.prototype.unByKey=cz.prototype.N;ez.prototype.getTileLoadFunction=ez.prototype.ab;ez.prototype.getTileUrlFunction=ez.prototype.bb;ez.prototype.setTileLoadFunction=ez.prototype.ib;ez.prototype.setTileUrlFunction=ez.prototype.Fa; +ez.prototype.getTileGrid=ez.prototype.za;ez.prototype.getAttributions=ez.prototype.la;ez.prototype.getLogo=ez.prototype.ka;ez.prototype.getProjection=ez.prototype.ma;ez.prototype.getState=ez.prototype.na;ez.prototype.get=ez.prototype.get;ez.prototype.getKeys=ez.prototype.O;ez.prototype.getProperties=ez.prototype.P;ez.prototype.set=ez.prototype.set;ez.prototype.setProperties=ez.prototype.I;ez.prototype.unset=ez.prototype.S;ez.prototype.changed=ez.prototype.s;ez.prototype.getRevision=ez.prototype.L; +ez.prototype.on=ez.prototype.D;ez.prototype.once=ez.prototype.M;ez.prototype.un=ez.prototype.J;ez.prototype.unByKey=ez.prototype.N;W.prototype.getAttributions=W.prototype.la;W.prototype.getLogo=W.prototype.ka;W.prototype.getProjection=W.prototype.ma;W.prototype.getState=W.prototype.na;W.prototype.get=W.prototype.get;W.prototype.getKeys=W.prototype.O;W.prototype.getProperties=W.prototype.P;W.prototype.set=W.prototype.set;W.prototype.setProperties=W.prototype.I;W.prototype.unset=W.prototype.S; +W.prototype.changed=W.prototype.s;W.prototype.getRevision=W.prototype.L;W.prototype.on=W.prototype.D;W.prototype.once=W.prototype.M;W.prototype.un=W.prototype.J;W.prototype.unByKey=W.prototype.N;gz.prototype.addFeature=gz.prototype.vd;gz.prototype.addFeatures=gz.prototype.Bc;gz.prototype.clear=gz.prototype.clear;gz.prototype.forEachFeature=gz.prototype.ig;gz.prototype.forEachFeatureInExtent=gz.prototype.ld;gz.prototype.forEachFeatureIntersectingExtent=gz.prototype.cf; +gz.prototype.getFeaturesCollection=gz.prototype.gf;gz.prototype.getFeatures=gz.prototype.Lc;gz.prototype.getFeaturesAtCoordinate=gz.prototype.ff;gz.prototype.getFeaturesInExtent=gz.prototype.hf;gz.prototype.getClosestFeatureToCoordinate=gz.prototype.kg;gz.prototype.getExtent=gz.prototype.R;gz.prototype.getFeatureById=gz.prototype.ef;gz.prototype.removeFeature=gz.prototype.Mc;gz.prototype.getAttributions=gz.prototype.la;gz.prototype.getLogo=gz.prototype.ka;gz.prototype.getProjection=gz.prototype.ma; +gz.prototype.getState=gz.prototype.na;gz.prototype.get=gz.prototype.get;gz.prototype.getKeys=gz.prototype.O;gz.prototype.getProperties=gz.prototype.P;gz.prototype.set=gz.prototype.set;gz.prototype.setProperties=gz.prototype.I;gz.prototype.unset=gz.prototype.S;gz.prototype.changed=gz.prototype.s;gz.prototype.getRevision=gz.prototype.L;gz.prototype.on=gz.prototype.D;gz.prototype.once=gz.prototype.M;gz.prototype.un=gz.prototype.J;gz.prototype.unByKey=gz.prototype.N;tn.prototype.getAttributions=tn.prototype.la; +tn.prototype.getLogo=tn.prototype.ka;tn.prototype.getProjection=tn.prototype.ma;tn.prototype.getState=tn.prototype.na;tn.prototype.get=tn.prototype.get;tn.prototype.getKeys=tn.prototype.O;tn.prototype.getProperties=tn.prototype.P;tn.prototype.set=tn.prototype.set;tn.prototype.setProperties=tn.prototype.I;tn.prototype.unset=tn.prototype.S;tn.prototype.changed=tn.prototype.s;tn.prototype.getRevision=tn.prototype.L;tn.prototype.on=tn.prototype.D;tn.prototype.once=tn.prototype.M;tn.prototype.un=tn.prototype.J; +tn.prototype.unByKey=tn.prototype.N;An.prototype.getAttributions=An.prototype.la;An.prototype.getLogo=An.prototype.ka;An.prototype.getProjection=An.prototype.ma;An.prototype.getState=An.prototype.na;An.prototype.get=An.prototype.get;An.prototype.getKeys=An.prototype.O;An.prototype.getProperties=An.prototype.P;An.prototype.set=An.prototype.set;An.prototype.setProperties=An.prototype.I;An.prototype.unset=An.prototype.S;An.prototype.changed=An.prototype.s;An.prototype.getRevision=An.prototype.L; +An.prototype.on=An.prototype.D;An.prototype.once=An.prototype.M;An.prototype.un=An.prototype.J;An.prototype.unByKey=An.prototype.N;jz.prototype.getAttributions=jz.prototype.la;jz.prototype.getLogo=jz.prototype.ka;jz.prototype.getProjection=jz.prototype.ma;jz.prototype.getState=jz.prototype.na;jz.prototype.get=jz.prototype.get;jz.prototype.getKeys=jz.prototype.O;jz.prototype.getProperties=jz.prototype.P;jz.prototype.set=jz.prototype.set;jz.prototype.setProperties=jz.prototype.I; +jz.prototype.unset=jz.prototype.S;jz.prototype.changed=jz.prototype.s;jz.prototype.getRevision=jz.prototype.L;jz.prototype.on=jz.prototype.D;jz.prototype.once=jz.prototype.M;jz.prototype.un=jz.prototype.J;jz.prototype.unByKey=jz.prototype.N;kz.prototype.getAttributions=kz.prototype.la;kz.prototype.getLogo=kz.prototype.ka;kz.prototype.getProjection=kz.prototype.ma;kz.prototype.getState=kz.prototype.na;kz.prototype.get=kz.prototype.get;kz.prototype.getKeys=kz.prototype.O; +kz.prototype.getProperties=kz.prototype.P;kz.prototype.set=kz.prototype.set;kz.prototype.setProperties=kz.prototype.I;kz.prototype.unset=kz.prototype.S;kz.prototype.changed=kz.prototype.s;kz.prototype.getRevision=kz.prototype.L;kz.prototype.on=kz.prototype.D;kz.prototype.once=kz.prototype.M;kz.prototype.un=kz.prototype.J;kz.prototype.unByKey=kz.prototype.N;Jp.prototype.getAttributions=Jp.prototype.la;Jp.prototype.getLogo=Jp.prototype.ka;Jp.prototype.getProjection=Jp.prototype.ma; +Jp.prototype.getState=Jp.prototype.na;Jp.prototype.get=Jp.prototype.get;Jp.prototype.getKeys=Jp.prototype.O;Jp.prototype.getProperties=Jp.prototype.P;Jp.prototype.set=Jp.prototype.set;Jp.prototype.setProperties=Jp.prototype.I;Jp.prototype.unset=Jp.prototype.S;Jp.prototype.changed=Jp.prototype.s;Jp.prototype.getRevision=Jp.prototype.L;Jp.prototype.on=Jp.prototype.D;Jp.prototype.once=Jp.prototype.M;Jp.prototype.un=Jp.prototype.J;Jp.prototype.unByKey=Jp.prototype.N;lz.prototype.getAttributions=lz.prototype.la; +lz.prototype.getLogo=lz.prototype.ka;lz.prototype.getProjection=lz.prototype.ma;lz.prototype.getState=lz.prototype.na;lz.prototype.get=lz.prototype.get;lz.prototype.getKeys=lz.prototype.O;lz.prototype.getProperties=lz.prototype.P;lz.prototype.set=lz.prototype.set;lz.prototype.setProperties=lz.prototype.I;lz.prototype.unset=lz.prototype.S;lz.prototype.changed=lz.prototype.s;lz.prototype.getRevision=lz.prototype.L;lz.prototype.on=lz.prototype.D;lz.prototype.once=lz.prototype.M;lz.prototype.un=lz.prototype.J; +lz.prototype.unByKey=lz.prototype.N;pz.prototype.getTileLoadFunction=pz.prototype.ab;pz.prototype.getTileUrlFunction=pz.prototype.bb;pz.prototype.setTileLoadFunction=pz.prototype.ib;pz.prototype.setTileUrlFunction=pz.prototype.Fa;pz.prototype.getTileGrid=pz.prototype.za;pz.prototype.getAttributions=pz.prototype.la;pz.prototype.getLogo=pz.prototype.ka;pz.prototype.getProjection=pz.prototype.ma;pz.prototype.getState=pz.prototype.na;pz.prototype.get=pz.prototype.get;pz.prototype.getKeys=pz.prototype.O; +pz.prototype.getProperties=pz.prototype.P;pz.prototype.set=pz.prototype.set;pz.prototype.setProperties=pz.prototype.I;pz.prototype.unset=pz.prototype.S;pz.prototype.changed=pz.prototype.s;pz.prototype.getRevision=pz.prototype.L;pz.prototype.on=pz.prototype.D;pz.prototype.once=pz.prototype.M;pz.prototype.un=pz.prototype.J;pz.prototype.unByKey=pz.prototype.N;sz.prototype.setUrl=sz.prototype.f;sz.prototype.getTileLoadFunction=sz.prototype.ab;sz.prototype.getTileUrlFunction=sz.prototype.bb; +sz.prototype.setTileLoadFunction=sz.prototype.ib;sz.prototype.setTileUrlFunction=sz.prototype.Fa;sz.prototype.getTileGrid=sz.prototype.za;sz.prototype.getAttributions=sz.prototype.la;sz.prototype.getLogo=sz.prototype.ka;sz.prototype.getProjection=sz.prototype.ma;sz.prototype.getState=sz.prototype.na;sz.prototype.get=sz.prototype.get;sz.prototype.getKeys=sz.prototype.O;sz.prototype.getProperties=sz.prototype.P;sz.prototype.set=sz.prototype.set;sz.prototype.setProperties=sz.prototype.I; +sz.prototype.unset=sz.prototype.S;sz.prototype.changed=sz.prototype.s;sz.prototype.getRevision=sz.prototype.L;sz.prototype.on=sz.prototype.D;sz.prototype.once=sz.prototype.M;sz.prototype.un=sz.prototype.J;sz.prototype.unByKey=sz.prototype.N;qz.prototype.setUrl=qz.prototype.f;qz.prototype.getTileLoadFunction=qz.prototype.ab;qz.prototype.getTileUrlFunction=qz.prototype.bb;qz.prototype.setTileLoadFunction=qz.prototype.ib;qz.prototype.setTileUrlFunction=qz.prototype.Fa;qz.prototype.getTileGrid=qz.prototype.za; +qz.prototype.getAttributions=qz.prototype.la;qz.prototype.getLogo=qz.prototype.ka;qz.prototype.getProjection=qz.prototype.ma;qz.prototype.getState=qz.prototype.na;qz.prototype.get=qz.prototype.get;qz.prototype.getKeys=qz.prototype.O;qz.prototype.getProperties=qz.prototype.P;qz.prototype.set=qz.prototype.set;qz.prototype.setProperties=qz.prototype.I;qz.prototype.unset=qz.prototype.S;qz.prototype.changed=qz.prototype.s;qz.prototype.getRevision=qz.prototype.L;qz.prototype.on=qz.prototype.D; +qz.prototype.once=qz.prototype.M;qz.prototype.un=qz.prototype.J;qz.prototype.unByKey=qz.prototype.N;vz.prototype.getAttributions=vz.prototype.la;vz.prototype.getLogo=vz.prototype.ka;vz.prototype.getProjection=vz.prototype.ma;vz.prototype.getState=vz.prototype.na;vz.prototype.get=vz.prototype.get;vz.prototype.getKeys=vz.prototype.O;vz.prototype.getProperties=vz.prototype.P;vz.prototype.set=vz.prototype.set;vz.prototype.setProperties=vz.prototype.I;vz.prototype.unset=vz.prototype.S; +vz.prototype.changed=vz.prototype.s;vz.prototype.getRevision=vz.prototype.L;vz.prototype.on=vz.prototype.D;vz.prototype.once=vz.prototype.M;vz.prototype.un=vz.prototype.J;vz.prototype.unByKey=vz.prototype.N;Ez.prototype.setUrl=Ez.prototype.f;Ez.prototype.getTileLoadFunction=Ez.prototype.ab;Ez.prototype.getTileUrlFunction=Ez.prototype.bb;Ez.prototype.setTileLoadFunction=Ez.prototype.ib;Ez.prototype.setTileUrlFunction=Ez.prototype.Fa;Ez.prototype.getTileGrid=Ez.prototype.za; +Ez.prototype.getAttributions=Ez.prototype.la;Ez.prototype.getLogo=Ez.prototype.ka;Ez.prototype.getProjection=Ez.prototype.ma;Ez.prototype.getState=Ez.prototype.na;Ez.prototype.get=Ez.prototype.get;Ez.prototype.getKeys=Ez.prototype.O;Ez.prototype.getProperties=Ez.prototype.P;Ez.prototype.set=Ez.prototype.set;Ez.prototype.setProperties=Ez.prototype.I;Ez.prototype.unset=Ez.prototype.S;Ez.prototype.changed=Ez.prototype.s;Ez.prototype.getRevision=Ez.prototype.L;Ez.prototype.on=Ez.prototype.D; +Ez.prototype.once=Ez.prototype.M;Ez.prototype.un=Ez.prototype.J;Ez.prototype.unByKey=Ez.prototype.N;Gz.prototype.getTileLoadFunction=Gz.prototype.ab;Gz.prototype.getTileUrlFunction=Gz.prototype.bb;Gz.prototype.setTileLoadFunction=Gz.prototype.ib;Gz.prototype.setTileUrlFunction=Gz.prototype.Fa;Gz.prototype.getTileGrid=Gz.prototype.za;Gz.prototype.getAttributions=Gz.prototype.la;Gz.prototype.getLogo=Gz.prototype.ka;Gz.prototype.getProjection=Gz.prototype.ma;Gz.prototype.getState=Gz.prototype.na; +Gz.prototype.get=Gz.prototype.get;Gz.prototype.getKeys=Gz.prototype.O;Gz.prototype.getProperties=Gz.prototype.P;Gz.prototype.set=Gz.prototype.set;Gz.prototype.setProperties=Gz.prototype.I;Gz.prototype.unset=Gz.prototype.S;Gz.prototype.changed=Gz.prototype.s;Gz.prototype.getRevision=Gz.prototype.L;Gz.prototype.on=Gz.prototype.D;Gz.prototype.once=Gz.prototype.M;Gz.prototype.un=Gz.prototype.J;Gz.prototype.unByKey=Gz.prototype.N;Iz.prototype.getTileGrid=Iz.prototype.za;Iz.prototype.getAttributions=Iz.prototype.la; +Iz.prototype.getLogo=Iz.prototype.ka;Iz.prototype.getProjection=Iz.prototype.ma;Iz.prototype.getState=Iz.prototype.na;Iz.prototype.get=Iz.prototype.get;Iz.prototype.getKeys=Iz.prototype.O;Iz.prototype.getProperties=Iz.prototype.P;Iz.prototype.set=Iz.prototype.set;Iz.prototype.setProperties=Iz.prototype.I;Iz.prototype.unset=Iz.prototype.S;Iz.prototype.changed=Iz.prototype.s;Iz.prototype.getRevision=Iz.prototype.L;Iz.prototype.on=Iz.prototype.D;Iz.prototype.once=Iz.prototype.M;Iz.prototype.un=Iz.prototype.J; +Iz.prototype.unByKey=Iz.prototype.N;Jz.prototype.getTileLoadFunction=Jz.prototype.ab;Jz.prototype.getTileUrlFunction=Jz.prototype.bb;Jz.prototype.setTileLoadFunction=Jz.prototype.ib;Jz.prototype.setTileUrlFunction=Jz.prototype.Fa;Jz.prototype.getTileGrid=Jz.prototype.za;Jz.prototype.getAttributions=Jz.prototype.la;Jz.prototype.getLogo=Jz.prototype.ka;Jz.prototype.getProjection=Jz.prototype.ma;Jz.prototype.getState=Jz.prototype.na;Jz.prototype.get=Jz.prototype.get;Jz.prototype.getKeys=Jz.prototype.O; +Jz.prototype.getProperties=Jz.prototype.P;Jz.prototype.set=Jz.prototype.set;Jz.prototype.setProperties=Jz.prototype.I;Jz.prototype.unset=Jz.prototype.S;Jz.prototype.changed=Jz.prototype.s;Jz.prototype.getRevision=Jz.prototype.L;Jz.prototype.on=Jz.prototype.D;Jz.prototype.once=Jz.prototype.M;Jz.prototype.un=Jz.prototype.J;Jz.prototype.unByKey=Jz.prototype.N;Kz.prototype.getTileGrid=Kz.prototype.za;Kz.prototype.getAttributions=Kz.prototype.la;Kz.prototype.getLogo=Kz.prototype.ka; +Kz.prototype.getProjection=Kz.prototype.ma;Kz.prototype.getState=Kz.prototype.na;Kz.prototype.get=Kz.prototype.get;Kz.prototype.getKeys=Kz.prototype.O;Kz.prototype.getProperties=Kz.prototype.P;Kz.prototype.set=Kz.prototype.set;Kz.prototype.setProperties=Kz.prototype.I;Kz.prototype.unset=Kz.prototype.S;Kz.prototype.changed=Kz.prototype.s;Kz.prototype.getRevision=Kz.prototype.L;Kz.prototype.on=Kz.prototype.D;Kz.prototype.once=Kz.prototype.M;Kz.prototype.un=Kz.prototype.J;Kz.prototype.unByKey=Kz.prototype.N; +Pz.prototype.forEachFeatureIntersectingExtent=Pz.prototype.cf;Pz.prototype.getFeaturesCollection=Pz.prototype.gf;Pz.prototype.getFeaturesAtCoordinate=Pz.prototype.ff;Pz.prototype.getFeatureById=Pz.prototype.ef;Pz.prototype.getAttributions=Pz.prototype.la;Pz.prototype.getLogo=Pz.prototype.ka;Pz.prototype.getProjection=Pz.prototype.ma;Pz.prototype.getState=Pz.prototype.na;Pz.prototype.get=Pz.prototype.get;Pz.prototype.getKeys=Pz.prototype.O;Pz.prototype.getProperties=Pz.prototype.P; +Pz.prototype.set=Pz.prototype.set;Pz.prototype.setProperties=Pz.prototype.I;Pz.prototype.unset=Pz.prototype.S;Pz.prototype.changed=Pz.prototype.s;Pz.prototype.getRevision=Pz.prototype.L;Pz.prototype.on=Pz.prototype.D;Pz.prototype.once=Pz.prototype.M;Pz.prototype.un=Pz.prototype.J;Pz.prototype.unByKey=Pz.prototype.N;Sz.prototype.getTileLoadFunction=Sz.prototype.ab;Sz.prototype.getTileUrlFunction=Sz.prototype.bb;Sz.prototype.setTileLoadFunction=Sz.prototype.ib;Sz.prototype.setTileUrlFunction=Sz.prototype.Fa; +Sz.prototype.getTileGrid=Sz.prototype.za;Sz.prototype.getAttributions=Sz.prototype.la;Sz.prototype.getLogo=Sz.prototype.ka;Sz.prototype.getProjection=Sz.prototype.ma;Sz.prototype.getState=Sz.prototype.na;Sz.prototype.get=Sz.prototype.get;Sz.prototype.getKeys=Sz.prototype.O;Sz.prototype.getProperties=Sz.prototype.P;Sz.prototype.set=Sz.prototype.set;Sz.prototype.setProperties=Sz.prototype.I;Sz.prototype.unset=Sz.prototype.S;Sz.prototype.changed=Sz.prototype.s;Sz.prototype.getRevision=Sz.prototype.L; +Sz.prototype.on=Sz.prototype.D;Sz.prototype.once=Sz.prototype.M;Sz.prototype.un=Sz.prototype.J;Sz.prototype.unByKey=Sz.prototype.N;Yz.prototype.getTileLoadFunction=Yz.prototype.ab;Yz.prototype.getTileUrlFunction=Yz.prototype.bb;Yz.prototype.setTileLoadFunction=Yz.prototype.ib;Yz.prototype.setTileUrlFunction=Yz.prototype.Fa;Yz.prototype.getTileGrid=Yz.prototype.za;Yz.prototype.getAttributions=Yz.prototype.la;Yz.prototype.getLogo=Yz.prototype.ka;Yz.prototype.getProjection=Yz.prototype.ma; +Yz.prototype.getState=Yz.prototype.na;Yz.prototype.get=Yz.prototype.get;Yz.prototype.getKeys=Yz.prototype.O;Yz.prototype.getProperties=Yz.prototype.P;Yz.prototype.set=Yz.prototype.set;Yz.prototype.setProperties=Yz.prototype.I;Yz.prototype.unset=Yz.prototype.S;Yz.prototype.changed=Yz.prototype.s;Yz.prototype.getRevision=Yz.prototype.L;Yz.prototype.on=Yz.prototype.D;Yz.prototype.once=Yz.prototype.M;Yz.prototype.un=Yz.prototype.J;Yz.prototype.unByKey=Yz.prototype.N;$z.prototype.getTileLoadFunction=$z.prototype.ab; +$z.prototype.getTileUrlFunction=$z.prototype.bb;$z.prototype.setTileLoadFunction=$z.prototype.ib;$z.prototype.setTileUrlFunction=$z.prototype.Fa;$z.prototype.getTileGrid=$z.prototype.za;$z.prototype.getAttributions=$z.prototype.la;$z.prototype.getLogo=$z.prototype.ka;$z.prototype.getProjection=$z.prototype.ma;$z.prototype.getState=$z.prototype.na;$z.prototype.get=$z.prototype.get;$z.prototype.getKeys=$z.prototype.O;$z.prototype.getProperties=$z.prototype.P;$z.prototype.set=$z.prototype.set; +$z.prototype.setProperties=$z.prototype.I;$z.prototype.unset=$z.prototype.S;$z.prototype.changed=$z.prototype.s;$z.prototype.getRevision=$z.prototype.L;$z.prototype.on=$z.prototype.D;$z.prototype.once=$z.prototype.M;$z.prototype.un=$z.prototype.J;$z.prototype.unByKey=$z.prototype.N;rk.prototype.changed=rk.prototype.s;rk.prototype.getRevision=rk.prototype.L;rk.prototype.on=rk.prototype.D;rk.prototype.once=rk.prototype.M;rk.prototype.un=rk.prototype.J;rk.prototype.unByKey=rk.prototype.N; +Mq.prototype.changed=Mq.prototype.s;Mq.prototype.getRevision=Mq.prototype.L;Mq.prototype.on=Mq.prototype.D;Mq.prototype.once=Mq.prototype.M;Mq.prototype.un=Mq.prototype.J;Mq.prototype.unByKey=Mq.prototype.N;Pq.prototype.changed=Pq.prototype.s;Pq.prototype.getRevision=Pq.prototype.L;Pq.prototype.on=Pq.prototype.D;Pq.prototype.once=Pq.prototype.M;Pq.prototype.un=Pq.prototype.J;Pq.prototype.unByKey=Pq.prototype.N;Vq.prototype.changed=Vq.prototype.s;Vq.prototype.getRevision=Vq.prototype.L; +Vq.prototype.on=Vq.prototype.D;Vq.prototype.once=Vq.prototype.M;Vq.prototype.un=Vq.prototype.J;Vq.prototype.unByKey=Vq.prototype.N;Xq.prototype.changed=Xq.prototype.s;Xq.prototype.getRevision=Xq.prototype.L;Xq.prototype.on=Xq.prototype.D;Xq.prototype.once=Xq.prototype.M;Xq.prototype.un=Xq.prototype.J;Xq.prototype.unByKey=Xq.prototype.N;Qp.prototype.changed=Qp.prototype.s;Qp.prototype.getRevision=Qp.prototype.L;Qp.prototype.on=Qp.prototype.D;Qp.prototype.once=Qp.prototype.M;Qp.prototype.un=Qp.prototype.J; +Qp.prototype.unByKey=Qp.prototype.N;Rp.prototype.changed=Rp.prototype.s;Rp.prototype.getRevision=Rp.prototype.L;Rp.prototype.on=Rp.prototype.D;Rp.prototype.once=Rp.prototype.M;Rp.prototype.un=Rp.prototype.J;Rp.prototype.unByKey=Rp.prototype.N;Sp.prototype.changed=Sp.prototype.s;Sp.prototype.getRevision=Sp.prototype.L;Sp.prototype.on=Sp.prototype.D;Sp.prototype.once=Sp.prototype.M;Sp.prototype.un=Sp.prototype.J;Sp.prototype.unByKey=Sp.prototype.N;Up.prototype.changed=Up.prototype.s; +Up.prototype.getRevision=Up.prototype.L;Up.prototype.on=Up.prototype.D;Up.prototype.once=Up.prototype.M;Up.prototype.un=Up.prototype.J;Up.prototype.unByKey=Up.prototype.N;zm.prototype.changed=zm.prototype.s;zm.prototype.getRevision=zm.prototype.L;zm.prototype.on=zm.prototype.D;zm.prototype.once=zm.prototype.M;zm.prototype.un=zm.prototype.J;zm.prototype.unByKey=zm.prototype.N;Lp.prototype.changed=Lp.prototype.s;Lp.prototype.getRevision=Lp.prototype.L;Lp.prototype.on=Lp.prototype.D; +Lp.prototype.once=Lp.prototype.M;Lp.prototype.un=Lp.prototype.J;Lp.prototype.unByKey=Lp.prototype.N;Mp.prototype.changed=Mp.prototype.s;Mp.prototype.getRevision=Mp.prototype.L;Mp.prototype.on=Mp.prototype.D;Mp.prototype.once=Mp.prototype.M;Mp.prototype.un=Mp.prototype.J;Mp.prototype.unByKey=Mp.prototype.N;Np.prototype.changed=Np.prototype.s;Np.prototype.getRevision=Np.prototype.L;Np.prototype.on=Np.prototype.D;Np.prototype.once=Np.prototype.M;Np.prototype.un=Np.prototype.J;Np.prototype.unByKey=Np.prototype.N; +hk.prototype.get=hk.prototype.get;hk.prototype.getKeys=hk.prototype.O;hk.prototype.getProperties=hk.prototype.P;hk.prototype.set=hk.prototype.set;hk.prototype.setProperties=hk.prototype.I;hk.prototype.unset=hk.prototype.S;hk.prototype.changed=hk.prototype.s;hk.prototype.getRevision=hk.prototype.L;hk.prototype.on=hk.prototype.D;hk.prototype.once=hk.prototype.M;hk.prototype.un=hk.prototype.J;hk.prototype.unByKey=hk.prototype.N;G.prototype.getBrightness=G.prototype.Jb;G.prototype.getContrast=G.prototype.Kb; +G.prototype.getHue=G.prototype.Lb;G.prototype.getExtent=G.prototype.R;G.prototype.getMaxResolution=G.prototype.Mb;G.prototype.getMinResolution=G.prototype.Nb;G.prototype.getOpacity=G.prototype.Sb;G.prototype.getSaturation=G.prototype.Ob;G.prototype.getVisible=G.prototype.nb;G.prototype.setBrightness=G.prototype.pc;G.prototype.setContrast=G.prototype.qc;G.prototype.setHue=G.prototype.rc;G.prototype.setExtent=G.prototype.jc;G.prototype.setMaxResolution=G.prototype.sc;G.prototype.setMinResolution=G.prototype.tc; +G.prototype.setOpacity=G.prototype.kc;G.prototype.setSaturation=G.prototype.uc;G.prototype.setVisible=G.prototype.vc;G.prototype.get=G.prototype.get;G.prototype.getKeys=G.prototype.O;G.prototype.getProperties=G.prototype.P;G.prototype.set=G.prototype.set;G.prototype.setProperties=G.prototype.I;G.prototype.unset=G.prototype.S;G.prototype.changed=G.prototype.s;G.prototype.getRevision=G.prototype.L;G.prototype.on=G.prototype.D;G.prototype.once=G.prototype.M;G.prototype.un=G.prototype.J; +G.prototype.unByKey=G.prototype.N;M.prototype.setMap=M.prototype.setMap;M.prototype.setSource=M.prototype.Tc;M.prototype.getBrightness=M.prototype.Jb;M.prototype.getContrast=M.prototype.Kb;M.prototype.getHue=M.prototype.Lb;M.prototype.getExtent=M.prototype.R;M.prototype.getMaxResolution=M.prototype.Mb;M.prototype.getMinResolution=M.prototype.Nb;M.prototype.getOpacity=M.prototype.Sb;M.prototype.getSaturation=M.prototype.Ob;M.prototype.getVisible=M.prototype.nb;M.prototype.setBrightness=M.prototype.pc; +M.prototype.setContrast=M.prototype.qc;M.prototype.setHue=M.prototype.rc;M.prototype.setExtent=M.prototype.jc;M.prototype.setMaxResolution=M.prototype.sc;M.prototype.setMinResolution=M.prototype.tc;M.prototype.setOpacity=M.prototype.kc;M.prototype.setSaturation=M.prototype.uc;M.prototype.setVisible=M.prototype.vc;M.prototype.get=M.prototype.get;M.prototype.getKeys=M.prototype.O;M.prototype.getProperties=M.prototype.P;M.prototype.set=M.prototype.set;M.prototype.setProperties=M.prototype.I; +M.prototype.unset=M.prototype.S;M.prototype.changed=M.prototype.s;M.prototype.getRevision=M.prototype.L;M.prototype.on=M.prototype.D;M.prototype.once=M.prototype.M;M.prototype.un=M.prototype.J;M.prototype.unByKey=M.prototype.N;Z.prototype.getSource=Z.prototype.ca;Z.prototype.getStyle=Z.prototype.U;Z.prototype.getStyleFunction=Z.prototype.T;Z.prototype.setStyle=Z.prototype.g;Z.prototype.setMap=Z.prototype.setMap;Z.prototype.setSource=Z.prototype.Tc;Z.prototype.getBrightness=Z.prototype.Jb; +Z.prototype.getContrast=Z.prototype.Kb;Z.prototype.getHue=Z.prototype.Lb;Z.prototype.getExtent=Z.prototype.R;Z.prototype.getMaxResolution=Z.prototype.Mb;Z.prototype.getMinResolution=Z.prototype.Nb;Z.prototype.getOpacity=Z.prototype.Sb;Z.prototype.getSaturation=Z.prototype.Ob;Z.prototype.getVisible=Z.prototype.nb;Z.prototype.setBrightness=Z.prototype.pc;Z.prototype.setContrast=Z.prototype.qc;Z.prototype.setHue=Z.prototype.rc;Z.prototype.setExtent=Z.prototype.jc;Z.prototype.setMaxResolution=Z.prototype.sc; +Z.prototype.setMinResolution=Z.prototype.tc;Z.prototype.setOpacity=Z.prototype.kc;Z.prototype.setSaturation=Z.prototype.uc;Z.prototype.setVisible=Z.prototype.vc;Z.prototype.get=Z.prototype.get;Z.prototype.getKeys=Z.prototype.O;Z.prototype.getProperties=Z.prototype.P;Z.prototype.set=Z.prototype.set;Z.prototype.setProperties=Z.prototype.I;Z.prototype.unset=Z.prototype.S;Z.prototype.changed=Z.prototype.s;Z.prototype.getRevision=Z.prototype.L;Z.prototype.on=Z.prototype.D;Z.prototype.once=Z.prototype.M; +Z.prototype.un=Z.prototype.J;Z.prototype.unByKey=Z.prototype.N;I.prototype.setMap=I.prototype.setMap;I.prototype.setSource=I.prototype.Tc;I.prototype.getBrightness=I.prototype.Jb;I.prototype.getContrast=I.prototype.Kb;I.prototype.getHue=I.prototype.Lb;I.prototype.getExtent=I.prototype.R;I.prototype.getMaxResolution=I.prototype.Mb;I.prototype.getMinResolution=I.prototype.Nb;I.prototype.getOpacity=I.prototype.Sb;I.prototype.getSaturation=I.prototype.Ob;I.prototype.getVisible=I.prototype.nb; +I.prototype.setBrightness=I.prototype.pc;I.prototype.setContrast=I.prototype.qc;I.prototype.setHue=I.prototype.rc;I.prototype.setExtent=I.prototype.jc;I.prototype.setMaxResolution=I.prototype.sc;I.prototype.setMinResolution=I.prototype.tc;I.prototype.setOpacity=I.prototype.kc;I.prototype.setSaturation=I.prototype.uc;I.prototype.setVisible=I.prototype.vc;I.prototype.get=I.prototype.get;I.prototype.getKeys=I.prototype.O;I.prototype.getProperties=I.prototype.P;I.prototype.set=I.prototype.set; +I.prototype.setProperties=I.prototype.I;I.prototype.unset=I.prototype.S;I.prototype.changed=I.prototype.s;I.prototype.getRevision=I.prototype.L;I.prototype.on=I.prototype.D;I.prototype.once=I.prototype.M;I.prototype.un=I.prototype.J;I.prototype.unByKey=I.prototype.N;dm.prototype.getBrightness=dm.prototype.Jb;dm.prototype.getContrast=dm.prototype.Kb;dm.prototype.getHue=dm.prototype.Lb;dm.prototype.getExtent=dm.prototype.R;dm.prototype.getMaxResolution=dm.prototype.Mb; +dm.prototype.getMinResolution=dm.prototype.Nb;dm.prototype.getOpacity=dm.prototype.Sb;dm.prototype.getSaturation=dm.prototype.Ob;dm.prototype.getVisible=dm.prototype.nb;dm.prototype.setBrightness=dm.prototype.pc;dm.prototype.setContrast=dm.prototype.qc;dm.prototype.setHue=dm.prototype.rc;dm.prototype.setExtent=dm.prototype.jc;dm.prototype.setMaxResolution=dm.prototype.sc;dm.prototype.setMinResolution=dm.prototype.tc;dm.prototype.setOpacity=dm.prototype.kc;dm.prototype.setSaturation=dm.prototype.uc; +dm.prototype.setVisible=dm.prototype.vc;dm.prototype.get=dm.prototype.get;dm.prototype.getKeys=dm.prototype.O;dm.prototype.getProperties=dm.prototype.P;dm.prototype.set=dm.prototype.set;dm.prototype.setProperties=dm.prototype.I;dm.prototype.unset=dm.prototype.S;dm.prototype.changed=dm.prototype.s;dm.prototype.getRevision=dm.prototype.L;dm.prototype.on=dm.prototype.D;dm.prototype.once=dm.prototype.M;dm.prototype.un=dm.prototype.J;dm.prototype.unByKey=dm.prototype.N;K.prototype.setMap=K.prototype.setMap; +K.prototype.setSource=K.prototype.Tc;K.prototype.getBrightness=K.prototype.Jb;K.prototype.getContrast=K.prototype.Kb;K.prototype.getHue=K.prototype.Lb;K.prototype.getExtent=K.prototype.R;K.prototype.getMaxResolution=K.prototype.Mb;K.prototype.getMinResolution=K.prototype.Nb;K.prototype.getOpacity=K.prototype.Sb;K.prototype.getSaturation=K.prototype.Ob;K.prototype.getVisible=K.prototype.nb;K.prototype.setBrightness=K.prototype.pc;K.prototype.setContrast=K.prototype.qc;K.prototype.setHue=K.prototype.rc; +K.prototype.setExtent=K.prototype.jc;K.prototype.setMaxResolution=K.prototype.sc;K.prototype.setMinResolution=K.prototype.tc;K.prototype.setOpacity=K.prototype.kc;K.prototype.setSaturation=K.prototype.uc;K.prototype.setVisible=K.prototype.vc;K.prototype.get=K.prototype.get;K.prototype.getKeys=K.prototype.O;K.prototype.getProperties=K.prototype.P;K.prototype.set=K.prototype.set;K.prototype.setProperties=K.prototype.I;K.prototype.unset=K.prototype.S;K.prototype.changed=K.prototype.s; +K.prototype.getRevision=K.prototype.L;K.prototype.on=K.prototype.D;K.prototype.once=K.prototype.M;K.prototype.un=K.prototype.J;K.prototype.unByKey=K.prototype.N;Uk.prototype.get=Uk.prototype.get;Uk.prototype.getKeys=Uk.prototype.O;Uk.prototype.getProperties=Uk.prototype.P;Uk.prototype.set=Uk.prototype.set;Uk.prototype.setProperties=Uk.prototype.I;Uk.prototype.unset=Uk.prototype.S;Uk.prototype.changed=Uk.prototype.s;Uk.prototype.getRevision=Uk.prototype.L;Uk.prototype.on=Uk.prototype.D; +Uk.prototype.once=Uk.prototype.M;Uk.prototype.un=Uk.prototype.J;Uk.prototype.unByKey=Uk.prototype.N;Yk.prototype.getActive=Yk.prototype.b;Yk.prototype.setActive=Yk.prototype.c;Yk.prototype.get=Yk.prototype.get;Yk.prototype.getKeys=Yk.prototype.O;Yk.prototype.getProperties=Yk.prototype.P;Yk.prototype.set=Yk.prototype.set;Yk.prototype.setProperties=Yk.prototype.I;Yk.prototype.unset=Yk.prototype.S;Yk.prototype.changed=Yk.prototype.s;Yk.prototype.getRevision=Yk.prototype.L;Yk.prototype.on=Yk.prototype.D; +Yk.prototype.once=Yk.prototype.M;Yk.prototype.un=Yk.prototype.J;Yk.prototype.unByKey=Yk.prototype.N;Ux.prototype.getActive=Ux.prototype.b;Ux.prototype.setActive=Ux.prototype.c;Ux.prototype.get=Ux.prototype.get;Ux.prototype.getKeys=Ux.prototype.O;Ux.prototype.getProperties=Ux.prototype.P;Ux.prototype.set=Ux.prototype.set;Ux.prototype.setProperties=Ux.prototype.I;Ux.prototype.unset=Ux.prototype.S;Ux.prototype.changed=Ux.prototype.s;Ux.prototype.getRevision=Ux.prototype.L;Ux.prototype.on=Ux.prototype.D; +Ux.prototype.once=Ux.prototype.M;Ux.prototype.un=Ux.prototype.J;Ux.prototype.unByKey=Ux.prototype.N;gl.prototype.getActive=gl.prototype.b;gl.prototype.setActive=gl.prototype.c;gl.prototype.get=gl.prototype.get;gl.prototype.getKeys=gl.prototype.O;gl.prototype.getProperties=gl.prototype.P;gl.prototype.set=gl.prototype.set;gl.prototype.setProperties=gl.prototype.I;gl.prototype.unset=gl.prototype.S;gl.prototype.changed=gl.prototype.s;gl.prototype.getRevision=gl.prototype.L;gl.prototype.on=gl.prototype.D; +gl.prototype.once=gl.prototype.M;gl.prototype.un=gl.prototype.J;gl.prototype.unByKey=gl.prototype.N;wl.prototype.getActive=wl.prototype.b;wl.prototype.setActive=wl.prototype.c;wl.prototype.get=wl.prototype.get;wl.prototype.getKeys=wl.prototype.O;wl.prototype.getProperties=wl.prototype.P;wl.prototype.set=wl.prototype.set;wl.prototype.setProperties=wl.prototype.I;wl.prototype.unset=wl.prototype.S;wl.prototype.changed=wl.prototype.s;wl.prototype.getRevision=wl.prototype.L;wl.prototype.on=wl.prototype.D; +wl.prototype.once=wl.prototype.M;wl.prototype.un=wl.prototype.J;wl.prototype.unByKey=wl.prototype.N;jl.prototype.getActive=jl.prototype.b;jl.prototype.setActive=jl.prototype.c;jl.prototype.get=jl.prototype.get;jl.prototype.getKeys=jl.prototype.O;jl.prototype.getProperties=jl.prototype.P;jl.prototype.set=jl.prototype.set;jl.prototype.setProperties=jl.prototype.I;jl.prototype.unset=jl.prototype.S;jl.prototype.changed=jl.prototype.s;jl.prototype.getRevision=jl.prototype.L;jl.prototype.on=jl.prototype.D; +jl.prototype.once=jl.prototype.M;jl.prototype.un=jl.prototype.J;jl.prototype.unByKey=jl.prototype.N;Yx.prototype.getActive=Yx.prototype.b;Yx.prototype.setActive=Yx.prototype.c;Yx.prototype.get=Yx.prototype.get;Yx.prototype.getKeys=Yx.prototype.O;Yx.prototype.getProperties=Yx.prototype.P;Yx.prototype.set=Yx.prototype.set;Yx.prototype.setProperties=Yx.prototype.I;Yx.prototype.unset=Yx.prototype.S;Yx.prototype.changed=Yx.prototype.s;Yx.prototype.getRevision=Yx.prototype.L;Yx.prototype.on=Yx.prototype.D; +Yx.prototype.once=Yx.prototype.M;Yx.prototype.un=Yx.prototype.J;Yx.prototype.unByKey=Yx.prototype.N;nl.prototype.getActive=nl.prototype.b;nl.prototype.setActive=nl.prototype.c;nl.prototype.get=nl.prototype.get;nl.prototype.getKeys=nl.prototype.O;nl.prototype.getProperties=nl.prototype.P;nl.prototype.set=nl.prototype.set;nl.prototype.setProperties=nl.prototype.I;nl.prototype.unset=nl.prototype.S;nl.prototype.changed=nl.prototype.s;nl.prototype.getRevision=nl.prototype.L;nl.prototype.on=nl.prototype.D; +nl.prototype.once=nl.prototype.M;nl.prototype.un=nl.prototype.J;nl.prototype.unByKey=nl.prototype.N;Ol.prototype.getGeometry=Ol.prototype.Z;Ol.prototype.getActive=Ol.prototype.b;Ol.prototype.setActive=Ol.prototype.c;Ol.prototype.get=Ol.prototype.get;Ol.prototype.getKeys=Ol.prototype.O;Ol.prototype.getProperties=Ol.prototype.P;Ol.prototype.set=Ol.prototype.set;Ol.prototype.setProperties=Ol.prototype.I;Ol.prototype.unset=Ol.prototype.S;Ol.prototype.changed=Ol.prototype.s;Ol.prototype.getRevision=Ol.prototype.L; +Ol.prototype.on=Ol.prototype.D;Ol.prototype.once=Ol.prototype.M;Ol.prototype.un=Ol.prototype.J;Ol.prototype.unByKey=Ol.prototype.N;cy.prototype.getActive=cy.prototype.b;cy.prototype.setActive=cy.prototype.c;cy.prototype.get=cy.prototype.get;cy.prototype.getKeys=cy.prototype.O;cy.prototype.getProperties=cy.prototype.P;cy.prototype.set=cy.prototype.set;cy.prototype.setProperties=cy.prototype.I;cy.prototype.unset=cy.prototype.S;cy.prototype.changed=cy.prototype.s;cy.prototype.getRevision=cy.prototype.L; +cy.prototype.on=cy.prototype.D;cy.prototype.once=cy.prototype.M;cy.prototype.un=cy.prototype.J;cy.prototype.unByKey=cy.prototype.N;Pl.prototype.getActive=Pl.prototype.b;Pl.prototype.setActive=Pl.prototype.c;Pl.prototype.get=Pl.prototype.get;Pl.prototype.getKeys=Pl.prototype.O;Pl.prototype.getProperties=Pl.prototype.P;Pl.prototype.set=Pl.prototype.set;Pl.prototype.setProperties=Pl.prototype.I;Pl.prototype.unset=Pl.prototype.S;Pl.prototype.changed=Pl.prototype.s;Pl.prototype.getRevision=Pl.prototype.L; +Pl.prototype.on=Pl.prototype.D;Pl.prototype.once=Pl.prototype.M;Pl.prototype.un=Pl.prototype.J;Pl.prototype.unByKey=Pl.prototype.N;Rl.prototype.getActive=Rl.prototype.b;Rl.prototype.setActive=Rl.prototype.c;Rl.prototype.get=Rl.prototype.get;Rl.prototype.getKeys=Rl.prototype.O;Rl.prototype.getProperties=Rl.prototype.P;Rl.prototype.set=Rl.prototype.set;Rl.prototype.setProperties=Rl.prototype.I;Rl.prototype.unset=Rl.prototype.S;Rl.prototype.changed=Rl.prototype.s;Rl.prototype.getRevision=Rl.prototype.L; +Rl.prototype.on=Rl.prototype.D;Rl.prototype.once=Rl.prototype.M;Rl.prototype.un=Rl.prototype.J;Rl.prototype.unByKey=Rl.prototype.N;ty.prototype.getActive=ty.prototype.b;ty.prototype.setActive=ty.prototype.c;ty.prototype.get=ty.prototype.get;ty.prototype.getKeys=ty.prototype.O;ty.prototype.getProperties=ty.prototype.P;ty.prototype.set=ty.prototype.set;ty.prototype.setProperties=ty.prototype.I;ty.prototype.unset=ty.prototype.S;ty.prototype.changed=ty.prototype.s;ty.prototype.getRevision=ty.prototype.L; +ty.prototype.on=ty.prototype.D;ty.prototype.once=ty.prototype.M;ty.prototype.un=ty.prototype.J;ty.prototype.unByKey=ty.prototype.N;Tl.prototype.getActive=Tl.prototype.b;Tl.prototype.setActive=Tl.prototype.c;Tl.prototype.get=Tl.prototype.get;Tl.prototype.getKeys=Tl.prototype.O;Tl.prototype.getProperties=Tl.prototype.P;Tl.prototype.set=Tl.prototype.set;Tl.prototype.setProperties=Tl.prototype.I;Tl.prototype.unset=Tl.prototype.S;Tl.prototype.changed=Tl.prototype.s;Tl.prototype.getRevision=Tl.prototype.L; +Tl.prototype.on=Tl.prototype.D;Tl.prototype.once=Tl.prototype.M;Tl.prototype.un=Tl.prototype.J;Tl.prototype.unByKey=Tl.prototype.N;Vl.prototype.getActive=Vl.prototype.b;Vl.prototype.setActive=Vl.prototype.c;Vl.prototype.get=Vl.prototype.get;Vl.prototype.getKeys=Vl.prototype.O;Vl.prototype.getProperties=Vl.prototype.P;Vl.prototype.set=Vl.prototype.set;Vl.prototype.setProperties=Vl.prototype.I;Vl.prototype.unset=Vl.prototype.S;Vl.prototype.changed=Vl.prototype.s;Vl.prototype.getRevision=Vl.prototype.L; +Vl.prototype.on=Vl.prototype.D;Vl.prototype.once=Vl.prototype.M;Vl.prototype.un=Vl.prototype.J;Vl.prototype.unByKey=Vl.prototype.N;Zl.prototype.getActive=Zl.prototype.b;Zl.prototype.setActive=Zl.prototype.c;Zl.prototype.get=Zl.prototype.get;Zl.prototype.getKeys=Zl.prototype.O;Zl.prototype.getProperties=Zl.prototype.P;Zl.prototype.set=Zl.prototype.set;Zl.prototype.setProperties=Zl.prototype.I;Zl.prototype.unset=Zl.prototype.S;Zl.prototype.changed=Zl.prototype.s;Zl.prototype.getRevision=Zl.prototype.L; +Zl.prototype.on=Zl.prototype.D;Zl.prototype.once=Zl.prototype.M;Zl.prototype.un=Zl.prototype.J;Zl.prototype.unByKey=Zl.prototype.N;Ey.prototype.getActive=Ey.prototype.b;Ey.prototype.setActive=Ey.prototype.c;Ey.prototype.get=Ey.prototype.get;Ey.prototype.getKeys=Ey.prototype.O;Ey.prototype.getProperties=Ey.prototype.P;Ey.prototype.set=Ey.prototype.set;Ey.prototype.setProperties=Ey.prototype.I;Ey.prototype.unset=Ey.prototype.S;Ey.prototype.changed=Ey.prototype.s;Ey.prototype.getRevision=Ey.prototype.L; +Ey.prototype.on=Ey.prototype.D;Ey.prototype.once=Ey.prototype.M;Ey.prototype.un=Ey.prototype.J;Ey.prototype.unByKey=Ey.prototype.N;Hy.prototype.getActive=Hy.prototype.b;Hy.prototype.setActive=Hy.prototype.c;Hy.prototype.get=Hy.prototype.get;Hy.prototype.getKeys=Hy.prototype.O;Hy.prototype.getProperties=Hy.prototype.P;Hy.prototype.set=Hy.prototype.set;Hy.prototype.setProperties=Hy.prototype.I;Hy.prototype.unset=Hy.prototype.S;Hy.prototype.changed=Hy.prototype.s;Hy.prototype.getRevision=Hy.prototype.L; +Hy.prototype.on=Hy.prototype.D;Hy.prototype.once=Hy.prototype.M;Hy.prototype.un=Hy.prototype.J;Hy.prototype.unByKey=Hy.prototype.N;Xe.prototype.get=Xe.prototype.get;Xe.prototype.getKeys=Xe.prototype.O;Xe.prototype.getProperties=Xe.prototype.P;Xe.prototype.set=Xe.prototype.set;Xe.prototype.setProperties=Xe.prototype.I;Xe.prototype.unset=Xe.prototype.S;Xe.prototype.changed=Xe.prototype.s;Xe.prototype.getRevision=Xe.prototype.L;Xe.prototype.on=Xe.prototype.D;Xe.prototype.once=Xe.prototype.M; +Xe.prototype.un=Xe.prototype.J;Xe.prototype.unByKey=Xe.prototype.N;Ze.prototype.getClosestPoint=Ze.prototype.$a;Ze.prototype.getExtent=Ze.prototype.R;Ze.prototype.transform=Ze.prototype.transform;Ze.prototype.get=Ze.prototype.get;Ze.prototype.getKeys=Ze.prototype.O;Ze.prototype.getProperties=Ze.prototype.P;Ze.prototype.set=Ze.prototype.set;Ze.prototype.setProperties=Ze.prototype.I;Ze.prototype.unset=Ze.prototype.S;Ze.prototype.changed=Ze.prototype.s;Ze.prototype.getRevision=Ze.prototype.L; +Ze.prototype.on=Ze.prototype.D;Ze.prototype.once=Ze.prototype.M;Ze.prototype.un=Ze.prototype.J;Ze.prototype.unByKey=Ze.prototype.N;Zm.prototype.getFirstCoordinate=Zm.prototype.Bb;Zm.prototype.getLastCoordinate=Zm.prototype.Cb;Zm.prototype.getLayout=Zm.prototype.Db;Zm.prototype.applyTransform=Zm.prototype.va;Zm.prototype.translate=Zm.prototype.Ua;Zm.prototype.getClosestPoint=Zm.prototype.$a;Zm.prototype.getExtent=Zm.prototype.R;Zm.prototype.get=Zm.prototype.get;Zm.prototype.getKeys=Zm.prototype.O; +Zm.prototype.getProperties=Zm.prototype.P;Zm.prototype.set=Zm.prototype.set;Zm.prototype.setProperties=Zm.prototype.I;Zm.prototype.unset=Zm.prototype.S;Zm.prototype.changed=Zm.prototype.s;Zm.prototype.getRevision=Zm.prototype.L;Zm.prototype.on=Zm.prototype.D;Zm.prototype.once=Zm.prototype.M;Zm.prototype.un=Zm.prototype.J;Zm.prototype.unByKey=Zm.prototype.N;an.prototype.getClosestPoint=an.prototype.$a;an.prototype.getExtent=an.prototype.R;an.prototype.transform=an.prototype.transform; +an.prototype.get=an.prototype.get;an.prototype.getKeys=an.prototype.O;an.prototype.getProperties=an.prototype.P;an.prototype.set=an.prototype.set;an.prototype.setProperties=an.prototype.I;an.prototype.unset=an.prototype.S;an.prototype.changed=an.prototype.s;an.prototype.getRevision=an.prototype.L;an.prototype.on=an.prototype.D;an.prototype.once=an.prototype.M;an.prototype.un=an.prototype.J;an.prototype.unByKey=an.prototype.N;tf.prototype.getFirstCoordinate=tf.prototype.Bb; +tf.prototype.getLastCoordinate=tf.prototype.Cb;tf.prototype.getLayout=tf.prototype.Db;tf.prototype.applyTransform=tf.prototype.va;tf.prototype.translate=tf.prototype.Ua;tf.prototype.getClosestPoint=tf.prototype.$a;tf.prototype.getExtent=tf.prototype.R;tf.prototype.transform=tf.prototype.transform;tf.prototype.get=tf.prototype.get;tf.prototype.getKeys=tf.prototype.O;tf.prototype.getProperties=tf.prototype.P;tf.prototype.set=tf.prototype.set;tf.prototype.setProperties=tf.prototype.I; +tf.prototype.unset=tf.prototype.S;tf.prototype.changed=tf.prototype.s;tf.prototype.getRevision=tf.prototype.L;tf.prototype.on=tf.prototype.D;tf.prototype.once=tf.prototype.M;tf.prototype.un=tf.prototype.J;tf.prototype.unByKey=tf.prototype.N;N.prototype.getFirstCoordinate=N.prototype.Bb;N.prototype.getLastCoordinate=N.prototype.Cb;N.prototype.getLayout=N.prototype.Db;N.prototype.applyTransform=N.prototype.va;N.prototype.translate=N.prototype.Ua;N.prototype.getClosestPoint=N.prototype.$a; +N.prototype.getExtent=N.prototype.R;N.prototype.transform=N.prototype.transform;N.prototype.get=N.prototype.get;N.prototype.getKeys=N.prototype.O;N.prototype.getProperties=N.prototype.P;N.prototype.set=N.prototype.set;N.prototype.setProperties=N.prototype.I;N.prototype.unset=N.prototype.S;N.prototype.changed=N.prototype.s;N.prototype.getRevision=N.prototype.L;N.prototype.on=N.prototype.D;N.prototype.once=N.prototype.M;N.prototype.un=N.prototype.J;N.prototype.unByKey=N.prototype.N; +O.prototype.getFirstCoordinate=O.prototype.Bb;O.prototype.getLastCoordinate=O.prototype.Cb;O.prototype.getLayout=O.prototype.Db;O.prototype.applyTransform=O.prototype.va;O.prototype.translate=O.prototype.Ua;O.prototype.getClosestPoint=O.prototype.$a;O.prototype.getExtent=O.prototype.R;O.prototype.transform=O.prototype.transform;O.prototype.get=O.prototype.get;O.prototype.getKeys=O.prototype.O;O.prototype.getProperties=O.prototype.P;O.prototype.set=O.prototype.set;O.prototype.setProperties=O.prototype.I; +O.prototype.unset=O.prototype.S;O.prototype.changed=O.prototype.s;O.prototype.getRevision=O.prototype.L;O.prototype.on=O.prototype.D;O.prototype.once=O.prototype.M;O.prototype.un=O.prototype.J;O.prototype.unByKey=O.prototype.N;ln.prototype.getFirstCoordinate=ln.prototype.Bb;ln.prototype.getLastCoordinate=ln.prototype.Cb;ln.prototype.getLayout=ln.prototype.Db;ln.prototype.applyTransform=ln.prototype.va;ln.prototype.translate=ln.prototype.Ua;ln.prototype.getClosestPoint=ln.prototype.$a; +ln.prototype.getExtent=ln.prototype.R;ln.prototype.transform=ln.prototype.transform;ln.prototype.get=ln.prototype.get;ln.prototype.getKeys=ln.prototype.O;ln.prototype.getProperties=ln.prototype.P;ln.prototype.set=ln.prototype.set;ln.prototype.setProperties=ln.prototype.I;ln.prototype.unset=ln.prototype.S;ln.prototype.changed=ln.prototype.s;ln.prototype.getRevision=ln.prototype.L;ln.prototype.on=ln.prototype.D;ln.prototype.once=ln.prototype.M;ln.prototype.un=ln.prototype.J;ln.prototype.unByKey=ln.prototype.N; +P.prototype.getFirstCoordinate=P.prototype.Bb;P.prototype.getLastCoordinate=P.prototype.Cb;P.prototype.getLayout=P.prototype.Db;P.prototype.applyTransform=P.prototype.va;P.prototype.translate=P.prototype.Ua;P.prototype.getClosestPoint=P.prototype.$a;P.prototype.getExtent=P.prototype.R;P.prototype.transform=P.prototype.transform;P.prototype.get=P.prototype.get;P.prototype.getKeys=P.prototype.O;P.prototype.getProperties=P.prototype.P;P.prototype.set=P.prototype.set;P.prototype.setProperties=P.prototype.I; +P.prototype.unset=P.prototype.S;P.prototype.changed=P.prototype.s;P.prototype.getRevision=P.prototype.L;P.prototype.on=P.prototype.D;P.prototype.once=P.prototype.M;P.prototype.un=P.prototype.J;P.prototype.unByKey=P.prototype.N;D.prototype.getFirstCoordinate=D.prototype.Bb;D.prototype.getLastCoordinate=D.prototype.Cb;D.prototype.getLayout=D.prototype.Db;D.prototype.applyTransform=D.prototype.va;D.prototype.translate=D.prototype.Ua;D.prototype.getClosestPoint=D.prototype.$a;D.prototype.getExtent=D.prototype.R; +D.prototype.transform=D.prototype.transform;D.prototype.get=D.prototype.get;D.prototype.getKeys=D.prototype.O;D.prototype.getProperties=D.prototype.P;D.prototype.set=D.prototype.set;D.prototype.setProperties=D.prototype.I;D.prototype.unset=D.prototype.S;D.prototype.changed=D.prototype.s;D.prototype.getRevision=D.prototype.L;D.prototype.on=D.prototype.D;D.prototype.once=D.prototype.M;D.prototype.un=D.prototype.J;D.prototype.unByKey=D.prototype.N;F.prototype.getFirstCoordinate=F.prototype.Bb; +F.prototype.getLastCoordinate=F.prototype.Cb;F.prototype.getLayout=F.prototype.Db;F.prototype.applyTransform=F.prototype.va;F.prototype.translate=F.prototype.Ua;F.prototype.getClosestPoint=F.prototype.$a;F.prototype.getExtent=F.prototype.R;F.prototype.transform=F.prototype.transform;F.prototype.get=F.prototype.get;F.prototype.getKeys=F.prototype.O;F.prototype.getProperties=F.prototype.P;F.prototype.set=F.prototype.set;F.prototype.setProperties=F.prototype.I;F.prototype.unset=F.prototype.S; +F.prototype.changed=F.prototype.s;F.prototype.getRevision=F.prototype.L;F.prototype.on=F.prototype.D;F.prototype.once=F.prototype.M;F.prototype.un=F.prototype.J;F.prototype.unByKey=F.prototype.N;vs.prototype.readFeatures=vs.prototype.ra;ms.prototype.readFeatures=ms.prototype.ra;ms.prototype.readFeatures=ms.prototype.ra;wh.prototype.get=wh.prototype.get;wh.prototype.getKeys=wh.prototype.O;wh.prototype.getProperties=wh.prototype.P;wh.prototype.set=wh.prototype.set;wh.prototype.setProperties=wh.prototype.I; +wh.prototype.unset=wh.prototype.S;wh.prototype.changed=wh.prototype.s;wh.prototype.getRevision=wh.prototype.L;wh.prototype.on=wh.prototype.D;wh.prototype.once=wh.prototype.M;wh.prototype.un=wh.prototype.J;wh.prototype.unByKey=wh.prototype.N;Wh.prototype.getMap=Wh.prototype.g;Wh.prototype.setMap=Wh.prototype.setMap;Wh.prototype.setTarget=Wh.prototype.c;Wh.prototype.get=Wh.prototype.get;Wh.prototype.getKeys=Wh.prototype.O;Wh.prototype.getProperties=Wh.prototype.P;Wh.prototype.set=Wh.prototype.set; +Wh.prototype.setProperties=Wh.prototype.I;Wh.prototype.unset=Wh.prototype.S;Wh.prototype.changed=Wh.prototype.s;Wh.prototype.getRevision=Wh.prototype.L;Wh.prototype.on=Wh.prototype.D;Wh.prototype.once=Wh.prototype.M;Wh.prototype.un=Wh.prototype.J;Wh.prototype.unByKey=Wh.prototype.N;hi.prototype.getMap=hi.prototype.g;hi.prototype.setMap=hi.prototype.setMap;hi.prototype.setTarget=hi.prototype.c;hi.prototype.get=hi.prototype.get;hi.prototype.getKeys=hi.prototype.O;hi.prototype.getProperties=hi.prototype.P; +hi.prototype.set=hi.prototype.set;hi.prototype.setProperties=hi.prototype.I;hi.prototype.unset=hi.prototype.S;hi.prototype.changed=hi.prototype.s;hi.prototype.getRevision=hi.prototype.L;hi.prototype.on=hi.prototype.D;hi.prototype.once=hi.prototype.M;hi.prototype.un=hi.prototype.J;hi.prototype.unByKey=hi.prototype.N;ii.prototype.getMap=ii.prototype.g;ii.prototype.setTarget=ii.prototype.c;ii.prototype.get=ii.prototype.get;ii.prototype.getKeys=ii.prototype.O;ii.prototype.getProperties=ii.prototype.P; +ii.prototype.set=ii.prototype.set;ii.prototype.setProperties=ii.prototype.I;ii.prototype.unset=ii.prototype.S;ii.prototype.changed=ii.prototype.s;ii.prototype.getRevision=ii.prototype.L;ii.prototype.on=ii.prototype.D;ii.prototype.once=ii.prototype.M;ii.prototype.un=ii.prototype.J;ii.prototype.unByKey=ii.prototype.N;fr.prototype.getMap=fr.prototype.g;fr.prototype.setTarget=fr.prototype.c;fr.prototype.get=fr.prototype.get;fr.prototype.getKeys=fr.prototype.O;fr.prototype.getProperties=fr.prototype.P; +fr.prototype.set=fr.prototype.set;fr.prototype.setProperties=fr.prototype.I;fr.prototype.unset=fr.prototype.S;fr.prototype.changed=fr.prototype.s;fr.prototype.getRevision=fr.prototype.L;fr.prototype.on=fr.prototype.D;fr.prototype.once=fr.prototype.M;fr.prototype.un=fr.prototype.J;fr.prototype.unByKey=fr.prototype.N;Zh.prototype.getMap=Zh.prototype.g;Zh.prototype.setMap=Zh.prototype.setMap;Zh.prototype.setTarget=Zh.prototype.c;Zh.prototype.get=Zh.prototype.get;Zh.prototype.getKeys=Zh.prototype.O; +Zh.prototype.getProperties=Zh.prototype.P;Zh.prototype.set=Zh.prototype.set;Zh.prototype.setProperties=Zh.prototype.I;Zh.prototype.unset=Zh.prototype.S;Zh.prototype.changed=Zh.prototype.s;Zh.prototype.getRevision=Zh.prototype.L;Zh.prototype.on=Zh.prototype.D;Zh.prototype.once=Zh.prototype.M;Zh.prototype.un=Zh.prototype.J;Zh.prototype.unByKey=Zh.prototype.N;kr.prototype.getMap=kr.prototype.g;kr.prototype.setMap=kr.prototype.setMap;kr.prototype.setTarget=kr.prototype.c;kr.prototype.get=kr.prototype.get; +kr.prototype.getKeys=kr.prototype.O;kr.prototype.getProperties=kr.prototype.P;kr.prototype.set=kr.prototype.set;kr.prototype.setProperties=kr.prototype.I;kr.prototype.unset=kr.prototype.S;kr.prototype.changed=kr.prototype.s;kr.prototype.getRevision=kr.prototype.L;kr.prototype.on=kr.prototype.D;kr.prototype.once=kr.prototype.M;kr.prototype.un=kr.prototype.J;kr.prototype.unByKey=kr.prototype.N;bi.prototype.getMap=bi.prototype.g;bi.prototype.setMap=bi.prototype.setMap;bi.prototype.setTarget=bi.prototype.c; +bi.prototype.get=bi.prototype.get;bi.prototype.getKeys=bi.prototype.O;bi.prototype.getProperties=bi.prototype.P;bi.prototype.set=bi.prototype.set;bi.prototype.setProperties=bi.prototype.I;bi.prototype.unset=bi.prototype.S;bi.prototype.changed=bi.prototype.s;bi.prototype.getRevision=bi.prototype.L;bi.prototype.on=bi.prototype.D;bi.prototype.once=bi.prototype.M;bi.prototype.un=bi.prototype.J;bi.prototype.unByKey=bi.prototype.N;zr.prototype.getMap=zr.prototype.g;zr.prototype.setTarget=zr.prototype.c; +zr.prototype.get=zr.prototype.get;zr.prototype.getKeys=zr.prototype.O;zr.prototype.getProperties=zr.prototype.P;zr.prototype.set=zr.prototype.set;zr.prototype.setProperties=zr.prototype.I;zr.prototype.unset=zr.prototype.S;zr.prototype.changed=zr.prototype.s;zr.prototype.getRevision=zr.prototype.L;zr.prototype.on=zr.prototype.D;zr.prototype.once=zr.prototype.M;zr.prototype.un=zr.prototype.J;zr.prototype.unByKey=zr.prototype.N;Er.prototype.getMap=Er.prototype.g;Er.prototype.setMap=Er.prototype.setMap; +Er.prototype.setTarget=Er.prototype.c;Er.prototype.get=Er.prototype.get;Er.prototype.getKeys=Er.prototype.O;Er.prototype.getProperties=Er.prototype.P;Er.prototype.set=Er.prototype.set;Er.prototype.setProperties=Er.prototype.I;Er.prototype.unset=Er.prototype.S;Er.prototype.changed=Er.prototype.s;Er.prototype.getRevision=Er.prototype.L;Er.prototype.on=Er.prototype.D;Er.prototype.once=Er.prototype.M;Er.prototype.un=Er.prototype.J;Er.prototype.unByKey=Er.prototype.N; return OPENLAYERS.ol; })); diff --git a/VIPSWeb/static/js/forecastmap.js b/VIPSWeb/static/js/forecastmap.js index 7b912be2d1e3ad68b282e3aac50c052aab63ead6..cca02eac77d365551dd40c2d497a35bbda0cd4bb 100644 --- a/VIPSWeb/static/js/forecastmap.js +++ b/VIPSWeb/static/js/forecastmap.js @@ -64,12 +64,17 @@ function initForecastMap(lonLat, zoomLevel, mapAttribution) // Filtering with crop ids forecastLayer = new ol.layer.Vector({ - source: new ol.source.KML({ + /*source: new ol.source.KML({ url: "http://" + settings.vipslogicServerName + "/rest/forecastresults/aggregate/" + settings.vipsOrganizationId + "?" + buildPathParamString("cropOrganismId", getSelectedCropIds()), //url: "http://localhost:8000/static/test/20150428bih.kml", projection: ol.proj.get('EPSG:3857') - }) - }); + })*/ + source: new ol.source.Vector({ + url: "http://" + settings.vipslogicServerName + "/rest/forecastresults/aggregate/" + settings.vipsOrganizationId + "?" + buildPathParamString("cropOrganismId", getSelectedCropIds()), + format: new ol.format.KML(), + projection: ol.proj.get('EPSG:3857') + }) + }); // Layer for popup var popOverlay = new ol.Overlay({